BC AL Journey #11

Previously in BC AL Journey #9 we talked about using Triggers as a means to do work based on a user event. This works great for tables, pages, report, and many other things you create or extend, but what about performing an event based on the sales document posting process for example. To handle these situations there are Events.

Events were introduced to Business Central later in its life and, at the time that I’m writing this, still evolving. The number of events is ever increasing, and new types of events are being created to support the expansion of Business Central.

Many of the field triggers we talked about in BC AL Journey #9 have matching Events that perform the same actions. Which one to use comes down to how you want to manage your code.

Event types – Business Central | Microsoft Learn

The basic concept of an Event is that there is an Event Declaration that is published and later raised by some process. There are other code elements that are “subscribers” to that event. When the event is raised the subscriber procedures are run.

The exciting thing about Events is that anyone can publish an Event. Microsoft, Partners, VAR’s and ISV’s, can publish and subscribe to Events. Unlike Triggers where there are only specific trigger types available at limited points inside Business Central, Events can be included anywhere.

For this example, we are going to Subscribe to an Event. Subscribing to an Event inside Business Central is the most common task you will perform when customizing Business Central for a specific use case. We will publish and raise events later.

Let’s create a use case and write some code.

Use Case

User has requested that if a Sales Order is posted with a Warranty selected, but no Warranty Exp Date provided that the system default to the current date plus 2 years.

Implementation

  • Subscribe to the OnBeforePostSalesDoc Event
    • If the ARD_WarrantyNo > 0 and ARD_WarrantyExpDate is not provided, then populate ARD_WarrantyExpDate with the Current Date + 2 years.

Test Plan

  • Post a Sales Order with a Warranty selected and a blank Warranty Exp Date field. Validate that the Posted Sales Invoice displays the current date + 2 years in the Warranty Exp Date field.
  • Post a Sales Order with a Warranty selected and a populated Warranty Exp Date field. Validate that the date is not changed.
  • Post a Sales Order without a Warranty selected. Verify that a Warranty Exp Date is not provided.

We are going to take this opportunity to introduce a new object in Business Central, the Code Unit. A Code Unit is simply an object in Business Central that contains procedures. You can have procedures in Tables, Pages, Reports, all over Business Central, but Code Units provide a means to organize code that can be used by several different objects. This is the shared library concept inside Business Central.

Let’s start by added a file to the 8 – Codeunit folder of our Business Central AL project. I’ve named mine ARDWarrantyDateHander.Codeunit.al. Code units, like all other Business Central AL objects have a type, an ID number from our App.json number range, and a name.

Empty and ready for code, mine looks like this.

namespace AardvarkLabs;

codeunit 50000 "ARD_WarrantyDateHandler"
{

}

Like conjuring a demon, you must know the true name of the event you wish to subscribe to (D&D nerds roll for initiative). This can be a challenge, but Visual Studio Code has a tool to help.

First, ensure that your cursor is inside the { } scope of the Code Unit. Press CTRL + Shift + P to bring up the Command Bar and type “Find Event”.

Clicking “AL: Find Event” will bring up an Event Search. Here we can try different permutations of the name of the event we want and see what pops up. There is a pattern, OnBefore and OnAfter are common prefixes indicating when the event happens. Record types typically appear in the names as well. The string “OnBeforePostSales” will get you in the neighborhood. We should be able to find “OnBeforePostSalesDoc” for the event that happens Before a Sales Document is Posted.

Double Clicking on the Event will drop the subscription code right where you left your cursor.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforePostSalesDoc, '', false, false)]
local procedure "Sales-Post_OnBeforePostSalesDoc"(var Sender: Codeunit "Sales-Post"; var SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean; PreviewMode: Boolean; var HideProgressWindow: Boolean; var IsHandled: Boolean; var CalledBy: Integer)
begin
end;

The Event Subscriber is declared in the [] before the procedure. It describes where the event is, in this case inside a Code Unit. Which object the Event publisher is in, the Sales-Post Code Unit, and the name of the Event Publisher, OnBeforePostSalesDoc. There is space for an Element Name, which isn’t used in this Event Subscriber, as well as setting if it should be skipped on missing licenses or permissions, which are both set to false.

Under the Event Subscriber is a procedure. This is the first procedure we have had in this series, but don’t worry, it is like a method or a function in any other programming language. It is a named block of code that can take in variables and return values. In this case it is scoped local and named “Sales-Post_OnBeforePostSalesDoc”. If you don’t like the name, you can change it to anything you like.

The structure of the procedure is very much the same as the trigger we created in BC AL Journey #10. It has a name, a var section and is scoped with a begin/end;.

Procedures can be passed values, in this case, we are being passed the sending code unit, sales header record, if it was in preview mode and a few other values describing the current process.

A common value to have in an Event is the “IsHandled” Boolean value. This value is used to tell the calling procedure, as well as all other subscribers that the process has been handled by another procedure. If you wanted to completely override the Sales Document Posting process, you could subscribe to this event, do all the posting work, then set the IsHandled to true to indicate that the process that raised the event can stop any further processing.

Many of the variables that are passed to the procedure have a “var” in front of the name. For my traditional programmers, this means that the variables are passed by reference. For everyone else, this means that the editing the variables impacts the actual records being handed by the process, not copies of the variables. For example, if the SalesHeader variable was not passed by “var” then updates to the SalesHeader would not flow back to the procedure that raised the event.

Here is the code I wrote to meet the requirements:

namespace AardvarkLabs;

using Microsoft.Sales.Posting;
using Microsoft.Sales.Document;

codeunit 50000 "ARD_WarrantyDateHandler"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforePostSalesDoc, '', false, false)]
    local procedure "Sales-Post_OnBeforePostSalesDoc"(var Sender: Codeunit "Sales-Post"; var SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean; PreviewMode: Boolean; var HideProgressWindow: Boolean; var IsHandled: Boolean; var CalledBy: Integer)
    var
        WarrantyDateFormula: Text[30];
        WarrantyExpDate: Date;
    begin
        if PreviewMode then exit;
        
        If (SalesHeader.ARD_WarrantyNo > 0) AND (SalesHeader.ARD_WarrantyExpDate = 0D) then begin
            WarrantyDateFormula := '<+2Y>';
            WarrantyExpDate := CalcDate(WarrantyDateFormula, DT2Date(CurrentDateTime()));
            SalesHeader.ARD_WarrantyExpDate := WarrantyExpDate;
        end;
    end;
}

I started with variables in the var section to define the Warranty Date Formula and the Warranty Exp Date that I want to set. Between the begin/end; first I check if this is in Preview Mode, if it is, I can just exit the procedure with the “exit” command.

Next, I check if a Warranty has been selected by checking if the Sales Header ARD_WarrantyNo is greater than zero. Also, we check that the Sales Header ARD_WarrantyExpDate is 0D, which is AL for a blank date. If both of those conditions are true, then we go about setting the warranty exp date.

We first set the WarrantyDateFormula to ‘<+2Y>” which indicates that I want to add 2 years to a date. We then use the CalcDate procedure to apply the WarrantyDateFormula to the date portion of the CurrentDateTime. We extract the Date from a DateTime with the DT2Date procedure.

Lastly, we set the Sales Header ARD_WarrantyExpDate with the calculated WarrantyExpDate value. There are commands to modify a record, and we will see those later, but they are unnecessary here as the record was passed with the var flag and will be committed to the database during the posting process.

Note that in the programming language of AL the equal test condition is “=” and the value assignment operator is “:=”. In the definition language of AL the assignment operator is “=”. Visual Studio Code will try to help you remember which is which.

Here we can see that I’ve selected a Warranty, but no Warranty Exp Date.

When posted the date is populated

Completing the testing of the other conditions and we will see that this meets the requirements. We can even preview the posting without accidentally setting a date prematurely.

Events are an extremely important tool in the Business Central customization process. Combined with Triggers, they allow us to extend and even overwrite the data handling processes within Business Central. We will see more events as we move into process automation and system integrations.

3 responses to “A Call to Action; An Introduction to Business Central Events”

  1. […] example to poke around and look under the hood. Next, we will go back to some of the work we did in BC AL Journey #11 and create our own Business Event and a custom Power Automate […]

    Like

  2. […] a provided wizard. In this step of our journey, we are going to revisit some of the work we did in BC AL Journey #11 and create a custom Business Event and a new Power Automate […]

    Like

  3. […] you are interested, I’ve included a bonus test of BC AL Journey #11. In this example we validate that a Sales Order with a warranty, but no expiration date, gets a […]

    Like

Leave a comment

Trending