- 30 Jun 2023
- 9 Minutes to read
- Print
- DarkLight
- PDF
How To Create Custom Commands for Retail Express
- Updated on 30 Jun 2023
- 9 Minutes to read
- Print
- DarkLight
- PDF
Overview
In ChargeLogic Retail Express, a “command” is a self-contained piece of business logic that executes when the cashier clicks a button at the POS. ChargeLogic Retail Express comes with many standard commands out of the box. As a Microsoft Dynamics 365 Business Central developer, you can add your own custom commands using the Extension model. Before you continue, this article assumes that you are an AL developer and have familiarity with the process of creating and deploying extensions for Business Central.
Types of Commands
Normal Commands
Normal commands can be placed on the POS screen using the designer and contain business logic that will execute when the related button is clicked.
Tender Commands
Tender commands are similar to normal commands, but they appear on the tendering screen of the POS instead of appearing as a regular POS button. Tender commands are responsible for applying funds to the sale and reducing the open balance. This is usually accomplished by calling the Retail Express Payment function.
Input Handlers
Input handlers are called when the cashier scans a barcode or types text into the scanbox. Examples of input handlers that come with ChargeLogic Retail Express are handlers to add items to the sale, select customers, and retrieve suspended sales. You can create your own input handler that responds to typed or scanned text.
Creating Your Extension
Dependencies
Your app.json file must reference the ChargeLogic Payments app as a dependency so that you will have access to the necessary symbols in Visual Studio Code. Here is an example of how to include ChargeLogic Payments as a dependency to your app.
"dependencies": [{
"appId": "0f30fcab-5ea0-4345-ba83-a32015759fbe",
"name": "ChargeLogic Payments",
"publisher": "ChargeLogic, LLC",
"version": "4.0.9.0"
}],
Create a Normal Command
Add an .al file to your project. Custom commands will be codeunits that encapsulate the business logic of the command you want to design. The codeunit must conform to a few requirements in order to function as a Retail Express command.
The codeunit must define a unique Command Code, Name, and Description for itself.
The codeunit must subscribe to the OnRegisterPOSCommand event published by the “EFT POS Command Mgt. -CL-” codeunit and add itself to the command buffer.
The codeunit must subscribe to the OnRunPOSCommand event published by the “EFT POS Command Mgt. -CL-” codeunit and exit if the current call stack does not match its Command Code.
Define the Command
In your codeunit, you should create a few Labels to define the command’s properties:
CommandCode: Label 'HELLOWORLD', Locked = true;
CommandDescription: Label 'Display a message box saying Hello World';
CommandName: Label 'Hello World';
Please Note
The CommandCode label should be Locked so that it does not get translated.
Create a Subscriber to OnRegisterPOSCommand
This subscriber is responsible for notifying the Retail Express system of your command’s presence. It is responsible for adding a new record to the passed Command Buffer that indicates your command’s Code, Name, and Description. The Editable property indicates if the command should be allowed to be added to an Interface Profile. It should normally be set to true, but if your command is intended to be called by another command and never be called directly, you can set it to false.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Command Mgt. -CL-", 'OnRegisterPOSCommand', '', false, false)]
procedure HandleOnRegisterPOSCommand(var POSCommandBuffer: Record "EFT POS Command Buffer -CL-" temporary)
begin
if not POSCommandBuffer.GET(CommandCode) then begin
POSCommandBuffer.INIT;
POSCommandBuffer.Code := CommandCode;
POSCommandBuffer.Description := CommandDescription;
POSCommandBuffer.Name := CommandName;
POSCommandBuffer.Editable := true;
POSCommandBuffer.INSERT;
end;
end;
Create a Subscriber to OnRunPOSCommand
This subscriber will be called when a button is clicked at the POS. It is responsible for executing the command’s business logic. The first thing it needs to do is establish if the current call is intended for it by checking to see if the CallStack.”Command Code” is equal to the command’s Code. If it isn’t, the subscriber should immediately exit. There are parameters passed to this subscriber that include the Sales Header, the Sales Line, and a Result boolean. The Sales Header will be pointing to the current POS document, the Sales Line will be pointing to the selected line (if any), and the Result boolean is for your command to return a success or failure value.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Command Mgt. -CL-", 'OnRunPOSCommand', '', false, false)]
local procedure HelloWorldCommand(var CallStack: Record "EFT POS Call Stack -CL-" temporary; var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line"; var Result: Boolean)
begin
if CallStack."Command Code" <> CommandCode then
exit;
Message(Text001);
end;
Putting it All Together
The following AL codeunit will register and handle a Hello World command with Retail Express.
Adding The Command To the POS
Once you have compiled, signed, published, and installed your extension, you can configure a Retail Express Interface Profile to display a button for your command.
From the Interface Profile, navigate to the POS Button Designer and create a new button for your command. If you have followed the procedure outlined above, your command will be displayed in the Button Command lookup.
Finish configuring the button.
Remember to update any relevant POS Permission Sets to include your new command so that the cashier can execute it.
When you run the POS from a Terminal with the updated Interface Profile, your command will appear on a button.
Clicking the button will execute your custom business logic.
Creating a Tendering Command
Tendering commands are just like Normal commands, but they are not usually added to an Interface Profile design. Instead, they are displayed on the tendering screen. You can indicate that your command is a tendering command by setting its Command Type to Tender in the OnRegisterPOSCommand subscriber.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Command Mgt. -CL-", 'OnRegisterPOSCommand', '', false, false)]
procedure HandleOnRegisterPOSCommand(var POSCommandBuffer: Record "EFT POS Command Buffer -CL-" temporary)
begin
if not POSCommandBuffer.GET(CommandCode) then begin
POSCommandBuffer.INIT;
POSCommandBuffer.Code := CommandCode;
POSCommandBuffer.Description := CommandDescription;
POSCommandBuffer.Name := CommandName;
POSCommandBuffer.Editable := true;
POSCommandBuffer."Command Type" := POSCommandBuffer."Command Type"::Tender;
POSCommandBuffer.INSERT;
end;
end;
Taking a Payment
Retail Express has native code for taking payments. You just need to call the Payment function.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Command Mgt. -CL-", 'OnRunPOSCommand', '', false, false)]
local procedure MyTenderCommand(var CallStack: Record "EFT POS Call Stack -CL-" temporary; var SalesHeader: Record "Sales Header"; var SalesLine: Record "Sales Line"; var Result: Boolean)
var
EFTPOSMgt: Codeunit "EFT POS Mgt. -CL-";
begin
if CallStack."Command Code" <> CommandCode then
exit;
EFTPOSMgt.Payment(SalesHeader, 'MYMETHOD', CallStack."Salesperson Code", CallStack);
end;
Please Note
In your own tendering command logic, you would provide the EFT Method Code of the payment method you want to call, like CCCHARGE for Credit Card Charge.
Once your extension has been installed, you can add your tendering command to an Interface Profile.
Handling Input
Add an .al file to your project. Input handlers will be codeunits that encapsulate the business logic of parsing and handling the cashier’s input. The codeunit must conform to a few requirements in order to function as a Retail Express input handler.
The codeunit must define a unique Command Code and Description for itself.
The codeunit must subscribe to the OnRegisterInputHandler event published by the “EFT POS Mgt. -CL-” codeunit and add itself to the command buffer.
The codeunit must subscribe to the OnProcessInput event published by the “EFT POS Mgt. -CL-” codeunit and exit if the passed CommandCode does not match its Command Code.
Defining the Input Handler
You should create a few Labels that describe the purpose of the input handler and give it a unique name.
Please Note
The CommandCode label should be Locked so that it does not get translated.
Create a Subscriber to OnRegisterInputHandler
This subscriber will be responsible for letting Retail Express know that this codeunit is an input handler for the provided InputText. You will need to evaluate whether the InputText makes sense for your codeunit to handle, rather than registering for all InputTexts. For example, if you are creating an input handler for barcodes that are 8 digits and InputText has a length of 5 and contains both letters and numbers, you should not register your codeunit.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Mgt. -CL-", 'OnRegisterInputHandler', '', false, false)]
procedure HandleOnRegisterInput(var POSCommandBuffer: Record "EFT POS Command Buffer -CL-" temporary; var SalesHeader: Record "Sales Header"; InputText: Text[1024]; SalespersonCode: Code[20])
begin
if MeetsSomeCondition(InputText) then begin
POSCommandBuffer.INIT;
POSCommandBuffer.Code := CommandCode;
POSCommandBuffer.Description := ProcessInputAs;
POSCommandBuffer.INSERT;
end;
end;
Create a Subscriber to OnProcessInput
This subscriber will be called when Retail Express is ready to process the input. Multiple input handlers may have registered themselves for a particular input, so it is important that you check the POSCommand parameter to verify that the call is for your codeunit. Usually, the OnProcessInput subscriber will call a POS Command to perform an action.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Mgt. -CL-", 'OnProcessInput', '', false, false)]
procedure HandleOnProcessInput(POSCommand: Code[20]; var SalesHeader: Record "Sales Header"; InputText: Text[1024]; SalespersonCode: Code[20])
var
CommandMgt: Codeunit "EFT POS Command Mgt. -CL-";
Parameter: Record "EFT POS Parameter -CL-" temporary;
CallStack: Record "EFT POS Call Stack -CL-" temporary;
NEWCOMMAND: Label 'NEWCOMMAND', Locked = true;
begin
if POSCommand <> CommandCode then
exit;
Parameter."Command Code" := NEWCOMMAND;
Parameter."Salesperson Code" := SalespersonCode;
Parameter.Parameter := InputText;
CommandMgt.RunCommand(Parameter, CallStack, SalesHeader);
end;
Putting It All Together
The following AL codeunit will register an input handler.
codeunit 50102 "POS Input-My Input Handler"
{
trigger OnRun()
begin
end;
var
CommandCode: Label 'MYINPUT', Locked = true;
ProcessInputAs: Label 'My input handler';
local procedure MeetsSomeCondition(InputText: Text): Boolean
begin
exit(true);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Mgt. -CL-", 'OnRegisterInputHandler', '', false, false)]
procedure HandleOnRegisterInput(var POSCommandBuffer: Record "EFT POS Command Buffer -CL-" temporary; var SalesHeader: Record "Sales Header"; InputText: Text[1024]; SalespersonCode: Code[20])
begin
if MeetsSomeCondition(InputText) then begin
POSCommandBuffer.INIT;
POSCommandBuffer.Code := CommandCode;
POSCommandBuffer.Description := ProcessInputAs;
POSCommandBuffer.INSERT;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"EFT POS Mgt. -CL-", 'OnProcessInput', '', false, false)]
procedure HandleOnProcessInput(POSCommand: Code[20]; var SalesHeader: Record "Sales Header"; InputText: Text[1024]; SalespersonCode: Code[20])
var
CommandMgt: Codeunit "EFT POS Command Mgt. -CL-";
Parameter: Record "EFT POS Parameter -CL-" temporary;
CallStack: Record "EFT POS Call Stack -CL-" temporary;
NEWCOMMAND: Label 'NEWCOMMAND', Locked = true;
begin
if POSCommand <> CommandCode then
exit;
Parameter."Command Code" := NEWCOMMAND;
Parameter."Salesperson Code" := SalespersonCode;
Parameter.Parameter := InputText;
CommandMgt.RunCommand(Parameter, CallStack, SalesHeader);
end;
}