How To Create Custom Commands for Retail Express
  • 30 Jun 2023
  • 9 Minutes to read
  • Dark
    Light
  • PDF

How To Create Custom Commands for Retail Express

  • Dark
    Light
  • PDF

Article summary

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.

codeunit 50100 "POS Command-Hello World"
{
    trigger OnRun()
    begin
    end;
    var
        CommandCode: Label 'HELLOWORLD', Locked = true;
        CommandDescription: Label 'Display a message box saying Hello World';
        CommandName: Label 'Hello World';
        Text001: Label 'Hello World!';
    [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;
    [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;
}

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.

  1. 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.

  1. Finish configuring the button.

  1. Remember to update any relevant POS Permission Sets to include your new command so that the cashier can execute it.

  1. When you run the POS from a Terminal with the updated Interface Profile, your command will appear on a button.

  1. 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.

  1. The codeunit must define a unique Command Code and Description for itself.

  2. The codeunit must subscribe to the OnRegisterInputHandler event published by the “EFT POS Mgt. -CL-” codeunit and add itself to the command buffer.

  3. 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.

CommandCode: Label 'MYINPUT', Locked = true;
ProcessInputAs: Label 'My input handler';

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;
}


Was this article helpful?