Module CAP Logistics - WMS
The solution that connects your warehouses and your business application.
Properties
| Name | Value |
|---|---|
| Version | 26.202601.41333.0 |
| Publisher | CAP Vision |
| Brief | The solution that connects your warehouses and your business application. |
Namespace
| Name | Summary |
|---|---|
| CapVision |
Concept
Le concept de développement repose sur l'usage du client web de Business Central en mode téléphone ou tablette. Chaque page comprend un unique composant nommé Liquid Page CWCAP qui se charge de l'affichage des données.
La mise en forme des données se fait en Liquid.
Modèle d'une page
L'exemple ci-dessous présente une page typique de l'application.
page 50000 "MyFirstLiquidPage"
{
PageType = Card;
ApplicationArea = All;
UsageCategory = None;
Caption = 'MyFirstLiquidPage';
layout
{
area(Content)
{
usercontrol(PageCtrl; "Liquid Page CWCAP")
{
trigger OnControlReady()
begin
InitializeComponents();
LoadPage();
end;
trigger OnAction(Sender: JsonObject; SenderId: Text; EventArgs: JsonObject)
var
IsHandled: Boolean;
begin
OnBeforeOnAction(Sender, SenderId, EventArgs, IsHandled);
if IsHandled then
exit;
case SenderId of
'close':
CurrPage.Close();
'ok': begin
RecallLabelCaption := '***' + Liquid.GetTextboxText(EventArgs, 'inputValue') + '***';
LoadPage();
end;
end;
end;
}
}
}
local procedure InitializeComponents()
var
Template: Text;
begin
Template := '{% render "title", id: "title", caption: title %}'
+ '{% render "textBox", id: "inputValue", caption: inputValueCaption, value: inputValueText, placeholder: inputValuePlaceHolder %}'
+ '{% render "label", caption: recallLabelCaption %}'
+ '{% render "actionBar", buttons: actionBarButtons %}';
OnInitializeComponents(Template);
CurrPage.PageCtrl.Create(Template);
end;
protected procedure LoadPage()
var
PageTitleLbl: Label 'Hello World';
TypeSomethingLbl:Label 'Type something';
begin
Clear(Payload);
Liquid.SetPayload(Payload);
Liquid.Set('title', PageTitleLbl);
Liquid.Set('inputValueCaption', TypeSomethingLbl);
Liquid.Set('inputValueText', '');
Liquid.Set('inputValuePlaceHolder', TypeSomethingLbl);
Liquid.Set('recallLabelCaption', RecallLabelCaption);
Liquid.SetActionBarButtons('actionBarButtons', 'close', 'Close', 'ok', 'OK');
OnLoadPage(Payload);
CurrPage.PageCtrl.Update(Payload);
end;
protected var
Payload: JsonObject;
var
Symbology: Codeunit "Symbology CWCAP";
Liquid: Codeunit "Liquid Helper CWCAP";
RecallLabelCaption:Text;
[IntegrationEvent(false, false)]
local procedure OnInitializeComponents(var Template: Text)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnLoadPage(var Payload: JsonObject)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeOnAction(Sender: JsonObject; SenderId: Text; EventArgs: JsonObject; var IsHandled: Boolean)
begin
end;
}
Une fois affichée en mode téléphone, la page ressemble à ceci :

Bibliothèques
Codeunits
| Nom de l'objet | Description |
|---|---|
| Data Search | Fournit un ensemble de fonctionnalités de recherche de données. |
| Liquid Helper | Fournit des fonctions utilitaires pour manipuler les templates et données Liquid. |
| Session Settings | Utilisé pour stocker les informations de session de l'utilisateur. Notamment le magasin en cours. |
| Symbology | Contient des symboles à réutiliser dans vos applications. |
Pages
| Nom de l'objet | Description |
|---|---|
| WMS Bin Content | Affiche le contenu de l'emplacement spécifié. |
| WMS Pick Location | Permet de sélectionner un magasin parmi ceux pour lequel l'utilisateur est magasinier. |
Templates disponibles
Lorsque vous invoquez la méthode Create du composant Liquid Page CWCAP, vous devez lui passer un template Liquid. Ce template est une chaîne de caractères qui contient des instructions Liquid et du code HTML.
L'application fournit un certain nombre de templates que vous pouvez utiliser dans vos développements.
actionBar

Propriétés
| Propriété | Optionnel | Description |
|---|---|---|
| id | oui | |
| buttons | oui | Il s'agit du JsonArry qui doit être saisit avec liquid.Set(...), les 2 propriétés suivantes sont renseignées via la fonction Liquid.SetActionBarButtons(...) |
| button.Id | oui | Permet de s'abonner à l'évènement du click |
| button.Text | oui |
Exemples d'utilisation
InitializeComponents
{% render "actionBar", buttons: ButtonsList %}
LoadPage
Liquid.SetActionBarButtons('buttonsList', Liquid.CloseId(), Liquid.CloseCaption(), Liquid.MenuId(), Liquid.MenuCaption(), Liquid.PostId(), Liquid.SaveCaption());
button
Propriétés
| Propriété | Optionnel | Description |
|---|---|---|
| id | oui | Permet de s'abonner à l'évènement du click |
| buttonValue | oui |
Exemples d'utilisation
InitializeComponents
{% render "button", id: buttonMinusId, buttonValue: buttonMinusValue %}
LoadPage
Liquid.Set('buttonMinusId', ButtonMinusLbl);
Liquid.Set('buttonMinusValue', '-');
OnAction
Pour récupérer l'évènement du clic sur le bouton, il faut utiliser l'évènement OnAction du ControlAddIn.
trigger OnAction(Sender: JsonObject; SenderId: Text; EventArgs: JsonObject)
begin
case SenderId of
ButtonMinusLbl:
ButtonMinus_OnAction();
end;
end;
input
L'input est l'équivalent d'une textBox mais sans étiquette (caption).
Propriétés
| Propriété | Optionnel | Description |
|---|---|---|
| id | oui | Définit l'identifiant du composant. Vous pouvez réutiliser cet identifiant dans votre code pour faire référence au composant. |
| caption | oui | Permet de renseigner un placeholder |
Exemples d'utilisation
InitializeComponents
{% render "input", id: InputId, caption: InputCaption %}
LoadPage
Liquid.Set('InputId', 'aed6505c-05c6-45b4-8cf4-7aae25898c88');
Liquid.Set('InputCaption', 'Write something...');
OnAction
L'exemple suivant permet de lire la valeur saisie dans le composnant input.
trigger OnAction(Sender: JsonObject; SenderId: Text; EventArgs: JsonObject)
begin
case focusedControlId of
InputIdLbl:
InputValue := Liquid.GetTextboxText(EventArgs, InputIdLbl);
end;
LoadPage();
end;
label

Propriétés
Générales
| Propriété | Optionnel | Valeur défaut | Description |
|---|---|---|---|
| id | oui | Définit l'identifiant du composant. Vous pouvez réutiliser cet identifiant dans votre code pour faire référence au composant. | |
| caption | oui | Si cette valeur est vide aucun code HTML ne sera généré sur la page | |
| linkLabel | oui | Si ce tag est ajouté le label devient cliquable |
Style
| Propriété | Optionnel | Valeur défaut | Description |
|---|---|---|---|
| fontSize | oui | 1em | |
| fontColor | oui | #000000 | |
| backgroundColor | oui | #f6f6f6 | |
| textAlign | oui | left | Voir les valeurs possibles |
| padding | oui | 1em | Voir les valeurs possibles |
| margin | oui | 1em 0 | Voir les valeurs possibles |
Exemples d'utilisation
InitializeComponents
{% render "label",
id: LabelId,
caption: LabelCaption,
fontSize: LabelFontSize,
fontColor: LabelFontColor,
padding: LabelPadding,
margin: LabelPadding,
linkLabel
%}
LoadPage
Liquid.Set('LabelId', 'aed6505c-05c6-45b4-8cf4-7aae25898c88');
Liquid.Set('LabelCaption', 'Write something...');
Liquid.Set('LabelFontColor', '#ffffff');
Liquid.Set('LabelFontSize', '1.5em');
Liquid.Set('LabelPadding', '1px 2px 3px 4px');
Liquid.Set('LabelMargin', '0');
listLabel
Ce composant permet d'afficher une liste d'éléments dans la page.

Propriétés
| Propriété | Optionnel | Description |
|---|---|---|
| id | oui | Définit l'identifiant du composant. Vous pouvez réutiliser cet identifiant dans votre code pour faire référence au composant. |
| labelValues | oui | Spécifie un tableau d'éléments de type LabelValue à afficher dans la liste. |
| swipe | oui | Permet d'activer un menu d'actions sur chaque élement de la liste. |
| swipeNumber | Si swipe = false | Le nombre d'actions souhaitées. Les valeurs supportées sont one et two. |
| swipeRight | Si swipe = false | Nom du bouton de droite. |
| swipeLeft | Si swipe = false | Nom du bouton de gauche. |
| selectionMode | oui | Permet de spécifier le mode de sélection des éléments de la liste. Les valeurs supportées sont single (sélection unique) et multiple (sélection multiple). |
Propriétés d'un LabelValue
| Propriété | Optionnel | Description |
|---|---|---|
| listItemId | oui | Spécifie l'identifiant de l'élément de la liste. Par exemple, si cet élément fait référence à un enregistrement de Business Central, utilisez le System ID de l'enregistrement comme référence. |
| title | oui | Spécifie le texte affiché en haut à gauche de l'élément. |
| description | oui | Spécifie le texte affiché en bas à gauche de l'élément. |
| value | oui | Spécifie le texte affiché en haut à droite de l'élément. |
| subtitle | oui | Spécifie le texte affiché en bas à droite de l'élément. |
| backgroundColor | oui | Spécifie la couleur de fond de l'élément. |
Exemples d'utilisation
InitializeComponents
{% render "listLabel", id: LabelArrayId, labelValues: LabelArrayValue %}
LoadPage
Liquid.Set('LabelArrayId', ListIdLbl);
Liquid.Set('LabelArrayValue', CreateBinContents());
local procedure CreateBinContents() Payload: JsonArray
var
BinContent: Record "Bin Content";
begin
BinContent.SetAutoCalcFields(Quantity);
if BinContent.FindSet() then
repeat
Liquid.AddListItem(
Payload,
BinContent."Bin Code",
BinContent."Item No.",
'',
BinContent.Quantity,
BinContent.SystemId,
'#ff000040');
until BinContent.Next() = 0;
end;
OnAction
L'exemple suivant présente comment récupérer l'élément sélectionné dans la liste quand l'utilisateur clique dessus.
trigger OnAction(Sender: JsonObject; SenderId: Text; EventArgs: JsonObject)
begin
case SenderId of
LabelArrayId:
begin
BinContent.GetBySystemId(Liquid.GetSelectedListItemId(EventArgs));
CurrBinCode := BinContent."Bin Code";
end;
end;
textBox
Une textBox est un composant permettant à l'utilisateur de saisir une valeur. Elle peut être utilisée avec un étiquette (caption) ou sans. Elle est typiquement utilisée pour avoir une zone de scannage dans la page :

Elle peut également être utilisée pour afficher une information, telle que le numéro d'article scanné dans l'exemple ci-dessous :

Vous noterez que dans l'exemple ci-dessus, la textBox est en lecture seule.
Propriétés
| Propriété | Optionnel | Description |
|---|---|---|
| id | oui | Définit l'identifiant du composant. Vous pouvez réutiliser cet identifiant dans votre code pour faire référence au composant. |
| caption | oui | Spécifie l'étiquette du composant. L'étiquette est affichée à gauche ou au-dessus de la zone de saisie. |
| value | oui | Spécifie la valeur du composant. Si readOnly = false, c'est cette valeur que l'utilisateur peut modifier. |
| placeholder | oui | Spécifie le texte affiché à la place de la valeur lorsque la valeur est vide. |
| hidden | oui | Spécifie que le composant ne doit pas être affiché dans la page. |
| captionHidden | oui | Spécifie que le caption doit être masqué. |
| readOnly | oui | Spécifie que le composant est en lecture seule. |
| inputMode | oui | Le type de clavier virtuel affiché. none par défaut. Valeurs possibles |
Exemples d'utilisation
InitializeComponents
{% render "textBox", id: TextboxItemDescriptionId, value: TextboxItemDescriptionValue, hidden: TextboxItemDescriptionHidden, readOnly, captionHidden %}
LoadPage
Liquid.Set('TextboxItemDescriptionHidden', true);
if Item.Get(ItemNo) then begin
Liquid.Set('TextboxItemDescriptionId', TextBoxItemDescriptionIdLbl);
Liquid.Set('TextboxItemDescriptionValue', Item.Description + ' ' + Item."Description 2");
Liquid.Set('TextboxItemDescriptionHidden', false);
end;
title
Le template title permet d'afficher un titre en haut de la page. Son usage est normalement réservé pour être le premier élément d'une page.

Propriétés
Générales
| Propriété | Optionnel | Valeur défaut | Description |
|---|---|---|---|
| id | oui | ||
| caption | oui | ||
| actionId | oui | Si défini, alors un bouton est créé à gauche du titre ayant pour action l'identifiant associé. | |
| actionText | oui |  | actionId doit être également défini. Par défaut, il s'agit d'une flèche pointant vers la gauche. |
Style
| Propriété | Optionnel | Valeur défaut |
|---|---|---|
| fontSize | oui | 1.5em |
| fontColor | oui | #ffffff |
Exemples d'utilisation
InitializeComponents
{% render "title", id: titleId, Caption: titleCaption, fontSize: titleFontSize, fontColor: titleFontColor, actionId: titleActionId %}
LoadPage
Liquid.Set('titleId', 'aed6505c-05c6-45b4-8cf4-7aae25898c88');
Liquid.Set('titleCaption', 'Write something...');
Liquid.Set('titleFontSize', '2em');
Liquid.Set('titleFontColor', '#dddddd');
Liquid.Set('titleActionId, Liquid.CloseId());
Boîtes de dialogue

Exemples d'utilisation
Les boites de dialogue fonctionnent différement des autres templates, elles ne sont pas intégrées directement dans le template Liquid avec le reste de la page. Elles permettent d'afficher du texte et des boutons.
Pour invoquer une boîte de dialogue, utilisez les fonctions ShowDialog et HideDialog.
Liquid.ShowDialog(CurrPage.PageCtrl, DialogIdLbl, 'Dialog title', DetailsContent.ToText(), ButtonResetDetailsIdLbl, ButtonResetDetailsLbl, ButtonCloseDetailsIdLbl, Liquid.CloseCaption());
CurrPage.PageCtrl.HideDialog(DialogIdLbl);
Donner le focus à un élément
Pour mettre le focus sur une zone particulière utilisez la fonction SetFocus avec l'id de l'élément auquel donner le focus.
Exemples d'utilisation
SetFocus peut être utilisé dans l'évènement OnUpdateCompleted pour définir une zone par défaut à focus, sinon elle peut être utilisée dans le trigger OnAction.
trigger OnUpdateCompleted()
begin
CurrPage.PageCtrl.SetFocus(TextboxScanIdLbl);
end;
trigger OnAction(Sender: JsonObject; SenderId: Text; EventArgs: JsonObject)
begin
case SenderId of
LabelArrayIdLbl:
ItemList_OnClick(EventArgs);
end;
end;
local procedure ItemList_OnClick(EventArgs: JsonObject)
begin
//Do Something
CurrPage.PageCtrl.SetFocus(TextboxScanIdLbl);
end;
Extensibilité
Etendre les pages existantes
Utiliser les événements OnInitializeComponents pour ajouter un élément au template.
Par exemple, dans la page contenant le template suivant :
{% render "label", id: "LabelHello", caption: "Hello" %}
{% render "listLabel", id: "ListLabel1", labelValues: LabelArrayValue %}
En vous abonnant à l'événement OnInitializeComponents
[EventSubscriber(ObjectType::Page, Page::"Base Page", OnInitializeComponents, '', false, false)]
local procedure BasePage_OnInitializeComponents(var Template: Text)
begin
Liquid.AddComponentsAfter(Template, 'LabelHello', '{% render "label", caption: "World" %}')
end;
Les fonctions suivantes sont disponibles :
- AddComponentsAfter(var LiquidTemplate: Text; ControlId: Text; TemplateToAdd: Text)
- AddComponentsBefore(var LiquidTemplate: Text; ControlId: Text; TemplateToAdd: Text)
- RemoveComponent(var LiquidTemplate: Text; ControlId: Text): Text
| Argument | Description |
|---|---|
| LiquidTemplate | |
| ControlId | Il s'agit du nom de l'Id du render. |
| TemplateToAdd | Il peut s'agir d'un ou plusieurs nouveaux render ou même de code HTML. |
Utilisez ensuite les événements OnLoadPage pour fournir les variables de votre template.
Et enfin utilisez les évenements OnBeforeOnAction pour capter les actions, implémenter les vôtres ou surcharger les actions prédéfinies dans la page.
Ajouter du code HTML
Il est possible d'ajouter du code HTML dans le template créé en AL.
Par exemple, en partant de la page affichant ces éléments :

Vous souhaitez ajouter un champ Quantité (PCS) avec ses deux boutons +/- pour incrémenter ou décrémenter la valeur.
Template +=
'{% unless TextboxQuantityHidden %}'
+ '<div class="container">'
+ '<div class="row">'
+ '<div class="col">'
+ '{% render "textBox", id: TextboxQuantityId, caption: TextboxQuantityCaption, value: TextboxQuantityValue, hidden: TextboxQuantityHidden, readOnly: QuantityReadOnly %}'
+ '</div>'
+ '<div class="col-md-auto">'
+ '{% render "button", id: buttonMinusId, buttonValue: buttonMinusValue %}'
+ '</div>'
+ '<div class="col-md-auto">'
+ '{% render "button", id: buttonPlusId, buttonValue: buttonPlusValue %}'
+ '</div>'
+ '</div>'
+ '</div>'
+ '{% endunless %}';

Le template {% unless TextboxQuantityHidden %}...{% endunless %} permet de cacher ou d'afficher tout le code HTML en fonction de la variable TextboxQuantityHidden qui est attendue dans le Payload.
Il existe aussi {% if TextboxQuantityHidden %}...{% endif %}.
Dans cet exemple, le code complet est caché mais on peut aussi l'utiliser pour afficher ou masquer des propriétés dans les balises HTML.
Etendre la recherche article
Lorsqu'un code-barres est scanné on utilise la fonction TrySearchItem du codeunit Data Search CWCAP pour trouver des résultats. Par défaut, elle cherche dans les tables Item, Item Reference et Item Ledger Entry sur les champs Lot No., Serial No. et Package No. et renvoie uniquement le premier résultat.
Il est possible de modifier certains paramètres de cette recherche.
Contexte
var
Context: JsonObject;
begin
Context.Add('itemNo', 'BT00002');
DataSearch.TrySearchItem(CurrScanValue, SearchResult, Context);
end;
| Article | Lot |
|---|---|
| BT00001 | LOT001 |
| BT00001 | LOT002 |
| BT00002 | LOT002 |
Le but du contexte est de spécifier l'article qui doit être trouvé. Dans cet exemple si on scanne le LOT002 le résultat retourné peut être BT00001 ou BT00002. Si on se trouve par exemple sur une ligne de prélèvement pour l'article BT00002, on peut préciser ce numéro article dans le contexte pour rechercher les lots de cet article uniquement.
Priorités
var
SearchPriorities: List of [Enum "Searchable CWCAP"];
begin
SearchPriorities.Add(Enum::"Searchable CWCAP"::"Item");
SearchPriorities.Add(Enum::"Searchable CWCAP"::"Item Ledger Entry");
SearchPriorities.Add(Enum::"Searchable CWCAP"::"Item Reference");
DataSearch.TrySearchItem(CurrScanValue, SearchResult, SearchPriorities);
end;
Par défaut la recherche est faite sur les tables Item, Item Reference et Item Ledger Entry, SearchPriorities permet de choisir l'ordre de recherche ou alors de ne chercher que dans une seule table. Si on sait que l'utilisateur doit scanner un N° de lot, alors Enum::"Searchable CWCAP"::"Item Reference" sera suffisant.
Evénements
L'événement OnBeforeSearchItem permet de retraiter la chaîne envoyée par le terminal avant d'être envoyée à la fonction de recherche.
Etendre l'incrément automatique
Lorsqu'un code-barres est scanné deux options existent :
- incrémenter la quantité de 1 OU
- ne rien faire
Il est possible d'étendre ce fonctionnement pour ajouter une méthode supplémentaire qui pourra être choisie dans l'écran de paramétrage ou pour simplement forcer la valeur d'incrément via un événement. Les deux exemples ci-dessous illustrent comme surcharger le fonctionnement standard.
Pour vos pages spécifiques, si vous souhaitez exploiter la personnalisation de l'incrément automatique,
- Vous devez invoquer la fonction
AddQuantitydans votre page, par exemple au moment où un article est scanné :
var
IncrementQuantity: Codeunit "Increment Quantity CWCAP";
begin
//Do something before...
IncrementQuantity.AddQuantity(Quantity, '', Page::"MyPage 1 PTE");
//Do something after...
end;
- Créez une tableextension de l'objet
Solution Setup CWCAPet ajoutez un champ permettant à l'utilisateur de configurer ce qu'il préfère pour l'incrément automatique de votre page.
tableextension 70345287 "Solution Setup CWCAP"
{
fields
{
field(11; "MyPage 1 Increment PTE"; Enum "Increment Quantity CWCAP")
{
Caption = 'MyPage 1 Increment';
}
}
}
- Abonnez-vous à l'événement
OnBeforeAddQuantitydu codeunitIncrement Quantity CWCAPet implémentez un code de ce type :
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Increment Quantity CWCAP", OnBeforeAddQuantity, '', false, false)]
local procedure OnBeforeAddQuantity(var Quantity: Decimal; Rec: Variant; WMSPage: Integer; var IsHandled: Boolean);
begin
case WMSPage of
Page::"MyPage 1 PTE":
Quantity += GetSolutionSetupValue(SolutionSetup."MyPage 1 Increment PTE", Rec);
end;
end;
Forcer via un événement
Vous pouvez vous abonner à l'événement OnBeforeAddQuantity et modifier la valeur de la quantité dans votre code.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Increment Quantity CWCAP", OnBeforeAddQuantity, '', false, false)]
local procedure OnBeforeAddQuantity(var Quantity: Decimal; Rec: Variant; WMSPage: Integer; var IsHandled: Boolean);
begin
end;
Nouvelle méthode d'incrément
Vous pouvez créer une nouvelle méthode d'incrément selon vos règles. Pour cela, suivez l'étapes ci-dessous :
- Etendez l'énumération
Increment Quantity CWCAP - Abonnez-vous à l'événement
OnBeforeGetSolutionSetupValueet retournez la quantité à incrémenter dans la variableIncrementQuantity.