Building an advanced sample extension
It's required to submit tests with your extension in order to pass validation. This walkthrough builds an advanced sample extension, which is used as the foundation for writing a test, which you can read about in the Test the advanced sample extension article. If you're new to building extensions, we suggest that you get familiar with Building your first sample extension that uses new objects and extension objects.
For information about submitting your app to AppSource, see Checklist for Submitting Your App.
This walkthrough will guide you through all the steps that you must follow to create the sample extension in AL. The final result can be published, installed, and tested on your sandbox. After you have built your extension, you must write the test for it.
About this walkthrough
This walkthrough illustrates the following tasks:
Developing a sample extension that uses codeunits, tables, card pages, list pages, navigate page (Assisted Setup) actions, and events and it includes tooltips and links to context-sensitive Help.
Creating extension objects that can be used to modify page and table objects.
Initializing the database during the installation of the extension.
Developing a sample test that tests external calls to a service, events, permissions, actions, navigate page (Assisted Setup), and other modified pages.
Running the sample test using the Test Tool.
Prerequisites
To complete this walkthrough, you'll need:
The Dynamics 365 Business Central tenant
Visual Studio Code
The AL Language extension for Microsoft Dynamics 365 Business Central for Visual Studio Code
For more information on how to get started with your first extension for Dynamics 365 Business Central, see Getting Started. It's recommended to try out simpler examples, before starting this walkthrough.
Customer Rewards extension overview
This sample extension enables the ability to set up any number of reward levels and the minimum number of rewards points required to attain that level. When the sample extension is installed, customers begin to accrue one reward point per sales order. When no reward levels are set up, the customer's reward level is set to 'NONE' even though the customer may have reward points. To begin using the sample extension, the user must accept the extension terms and activate the extension by entering a valid activation code using the Customer Rewards Assisted Setup Wizard. Following all the steps of this walkthrough allows you to publish the extension on your tenant and create a possible new feature for your customers.
Developing the sample Customer Rewards extension
In the following section, you'll be adding the objects that are needed for the Customer Rewards extension.
Customer Rewards table objects
First, we'll get started with the table objects that store the data.
Reward Level table object
The following code adds a new table 50100 Reward Level for storing reward level information set up by the user. The table consists of two fields: Level and Minimum Reward Points.
table 50100 "Reward Level"
{
fields
{
field(1; Level; Text[20]) { }
field(2; "Minimum Reward Points"; Integer)
{
MinValue = 0;
NotBlank = true;
trigger OnValidate();
var
tempPoints: Integer;
RewardLevel: Record "Reward Level";
begin
tempPoints := "Minimum Reward Points";
RewardLevel.SetRange("Minimum Reward Points", tempPoints);
if RewardLevel.FindFirst then
Error('Minimum Reward Points must be unique');
end;
}
}
keys
{
key(PK; Level)
{
Clustered = true;
}
key("Minimum Reward Points"; "Minimum Reward Points") { }
}
trigger OnInsert();
begin
Validate("Minimum Reward Points");
end;
trigger OnModify();
begin
Validate("Minimum Reward Points");
end;
}
Activation Code Information table object
The following code adds a new table 50101 Activation Code Information for storing activation information for the extension. The table consists of three fields: ActivationCode, Date Activated, and Expiration Date.
table 50101 "Activation Code Information"
{
fields
{
field(1; ActivationCode; Text[14])
{
Description = 'Activation code used to activate Customer Rewards';
}
field(2; "Date Activated"; Date)
{
Description = 'Date Customer Rewards was activated';
}
field(3; "Expiration Date"; Date)
{
Description = 'Date Customer Rewards activation expires';
}
}
keys
{
key(PK; ActivationCode)
{
Clustered = true;
}
}
}
Customer Rewards Mgt. Setup table object
The following code adds a new table 50102 Customer Rewards Mgt. Setup for storing information about the codeunit that should be used to handle events in the extension. This enables us to mock events in our sample test. The table consists of two fields: Primary Key and Customer Rewards Ext. Mgt. Codeunit ID.
table 50102 "Customer Rewards Mgt. Setup"
{
fields
{
field(1; "Primary Key"; Code[10])
{
}
field(2; "Customer Rewards Ext. Mgt. Codeunit ID"; Integer)
{
TableRelation = "CodeUnit Metadata".ID;
}
}
keys
{
key(PK; "Primary Key")
{
Clustered = true;
}
}
}
Customer Rewards table extension objects
Customer table extension object
The Customer table, like many other tables, is part of the Dynamics 365 Business Central service and it can't be modified directly by developers. To add more fields or to change properties on this table, developers must create a new type of object; a table extension. The following code creates a table extension for the Customer table and adds the RewardPoints field.
tableextension 50100 "CustomerTable Ext." extends Customer
{
fields
{
field(10001; RewardPoints; Integer)
{
MinValue = 0;
}
}
}
Customer Rewards page objects
For each page object, you can specify the target Help page that describes the feature that the page object is part of. The ContextSensitiveHelpPage
property on the page object works together with the link that is specified in the app.json file. For more information, see Configure Context-Sensitive Help.
Customer Rewards Wizard page object
The following code adds the 50100 Customer Rewards Wizard page that enables the user to accept the terms for using the extension and activating the extension. The page consists of a welcome step, an activation step, and a finish step. The welcome step has a checkbox for the Terms of Use that must be enabled. The activation step has a text box where the activation code must be entered for validation. A valid activation code for this sample extension is any 14 character alphanumeric code.
page 50100 "Customer Rewards Wizard"
{
// Specifies that this page will be a navigate page.
PageType = NavigatePage;
Caption = 'Customer Rewards assisted setup guide';
ContextSensitiveHelpPage = 'sales-rewards';
layout
{
area(content)
{
group(MediaStandard)
{
Caption = '';
Editable = false;
Visible = TopBannerVisible;
field("MediaResourcesStandard.""Media Reference"""; MediaResourcesStandard."Media Reference")
{
ApplicationArea = All;
Editable = false;
ShowCaption = false;
}
}
group(FirstPage)
{
Caption = '';
Visible = FirstPageVisible;
group("Welcome")
{
Caption = 'Welcome';
Visible = FirstPageVisible;
group(Introduction)
{
Caption = '';
InstructionalText = 'This Customer Rewards extension is a sample extension. It adds rewards tiers support for Customers.';
Visible = FirstPageVisible;
field(Spacer1; '')
{
ApplicationArea = All;
ShowCaption = false;
Editable = false;
MultiLine = true;
}
}
group("Terms")
{
Caption = 'Terms of Use';
Visible = FirstPageVisible;
group(Terms1)
{
Caption = '';
InstructionalText = 'By enabling the Customer Rewards extension...';
Visible = FirstPageVisible;
}
}
group(Terms2)
{
Caption = '';
field(EnableFeature; EnableCustomerRewards)
{
ApplicationArea = All;
MultiLine = true;
Editable = true;
Caption = 'I understand and accept these terms.';
trigger OnValidate();
begin
ShowFirstPage;
end;
}
}
}
}
group(SecondPage)
{
Caption = '';
Visible = SecondPageVisible;
group("Activation")
{
Caption = 'Activation';
Visible = SecondPageVisible;
field(Spacer2; '')
{
ApplicationArea = All;
ShowCaption = false;
Editable = false;
MultiLine = true;
}
group(ActivationMessage)
{
Caption = '';
InstructionalText = 'Enter your 14 digit activation code to continue';
Visible = SecondPageVisible;
field(Activationcode; ActivationCode)
{
ApplicationArea = All;
ShowCaption = false;
Editable = true;
}
}
}
}
group(FinalPage)
{
Caption = '';
Visible = FinalPageVisible;
group("ActivationDone")
{
Caption = 'You''re done!';
Visible = FinalPageVisible;
group(DoneMessage)
{
Caption = '';
InstructionalText = 'Click Finish to setup your rewards level and start using Customer Rewards.';
Visible = FinalPageVisible;
}
}
}
}
}
actions
{
area(Processing)
{
action(ActionBack)
{
ApplicationArea = All;
Caption = 'Back';
Enabled = BackEnabled;
Visible = BackEnabled;
Image = PreviousRecord;
InFooterBar = true;
trigger OnAction();
begin
NextStep(true);
end;
}
action(ActionNext)
{
ApplicationArea = All;
Caption = 'Next';
Enabled = NextEnabled;
Visible = NextEnabled;
Image = NextRecord;
InFooterBar = true;
trigger OnAction();
begin
NextStep(false);
end;
}
action(ActionActivate)
{
ApplicationArea = All;
Caption = 'Activate';
Enabled = ActivateEnabled;
Visible = ActivateEnabled;
Image = NextRecord;
InFooterBar = true;
trigger OnAction();
var
CustomerRewardsExtMgt: Codeunit "Customer Rewards Ext. Mgt.";
begin
if ActivationCode = '' then
Error('Activation code cannot be blank.');
if Text.StrLen(ActivationCode) <> 14 then
Error('Activation code must have 14 digits.');
if CustomerRewardsExtMgt.ActivateCustomerRewards(ActivationCode) then
NextStep(false)
else
Error('Activation failed. Please check the activtion code you entered.');
end;
}
action(ActionFinish)
{
ApplicationArea = All;
Caption = 'Finish';
Enabled = FinalPageVisible;
Image = Approve;
InFooterBar = true;
trigger OnAction();
begin
FinishAndEnableCustomerRewards
end;
}
}
}
trigger OnInit();
begin
LoadTopBanners;
end;
trigger OnOpenPage();
begin
Step := Step::First;
EnableControls;
end;
local procedure EnableControls()
begin
ResetControls;
case Step of
Step::First :
ShowFirstPage;
Step::Second :
ShowSecondPage;
Step::Finish :
ShowFinalPage;
end;
end;
local procedure NextStep(Backwards: Boolean)
begin
if Backwards then
Step := Step - 1
ELSE
Step := Step + 1;
EnableControls;
end;
local procedure FinishAndEnableCustomerRewards()
var
CustomerRewardsExtMgt: Codeunit "Customer Rewards Ext. Mgt.";
begin
CurrPage.Close;
CustomerRewardsExtMgt.OpenRewardsLevelPage;
end;
local procedure ShowFirstPage()
begin
FirstPageVisible := true;
SecondPageVisible := false;
FinishEnabled := false;
BackEnabled := false;
ActivateEnabled := false;
NextEnabled := EnableCustomerRewards;
end;
local procedure ShowSecondPage()
begin
FirstPageVisible := false;
SecondPageVisible := true;
FinishEnabled := false;
BackEnabled := true;
NextEnabled := false;
ActivateEnabled := true;
end;
local procedure ShowFinalPage()
begin
FinalPageVisible := true;
BackEnabled := true;
NextEnabled := false;
ActivateEnabled := false;
end;
local procedure ResetControls()
begin
FinishEnabled := true;
BackEnabled := true;
NextEnabled := true;
ActivateEnabled := true;
FirstPageVisible := false;
SecondPageVisible := false;
FinalPageVisible := false;
end;
local procedure LoadTopBanners()
begin
if MediaRepositoryStandard.GET('AssistedSetup-NoText-400px.png', FORMAT(CURRENTCLIENTTYPE))
then
if MediaResourcesStandard.GET(MediaRepositoryStandard."Media Resources Ref")
then
TopBannerVisible := MediaResourcesStandard."Media Reference".HASVALUE;
end;
var
MediaRepositoryStandard: Record 9400;
MediaResourcesStandard: Record 2000000182;
Step: Option First, Second, Finish;
ActivationCode: Text;
TopBannerVisible: Boolean;
FirstPageVisible: Boolean;
SecondPageVisible: Boolean;
FinalPageVisible: Boolean;
FinishEnabled: Boolean;
BackEnabled: Boolean;
NextEnabled: Boolean;
ActivateEnabled: Boolean;
EnableCustomerRewards: Boolean;
}
Rewards Level List page object
The following code adds the 50101 Rewards Level List page that enables the user to view, edit, or add new reward levels and their corresponding minimum required points. The code example includes tooltips for controls and a relative link to context-sensitive Help.
page 50101 "Rewards Level List"
{
PageType = List;
ContextSensitiveHelpPage = 'sales-rewards';
SourceTable = "Reward Level";
SourceTableView = sorting ("Minimum Reward Points") order(ascending);
layout
{
area(content)
{
repeater(Group)
{
field(Level; Rec.Level)
{
ApplicationArea = All;
Tooltip = 'Specifies the level of reward that the customer has at this point.';
}
field("Minimum Reward Points"; Rec."Minimum Reward Points")
{
ApplicationArea = All;
Tooltip = 'Specifies the number of points that customers must have to reach this level.';
}
}
}
}
trigger OnOpenPage();
begin
if(not CustomerRewardsExtMgt.IsCustomerRewardsActivated) then
Error(NotActivatedTxt);
end;
var
CustomerRewardsExtMgt: Codeunit "Customer Rewards Ext. Mgt.";
NotActivatedTxt: Label 'Customer Rewards is not activated';
}
Customer Rewards page extension objects
Customer card page extension object
A page extension object can be used to add new functionality to pages that are part of the Dynamics 365 Business Central service. The following page extension object extends the Customer Card page object by adding two field controls: RewardLevel and RewardPoints after the Name field control on the page. The fields are added in the layout section.
pageextension 50100 "Customer Card Ext." extends "Customer Card"
{
layout
{
addafter(Name)
{
field(RewardLevel; RewardLevel)
{
ApplicationArea = All;
Caption = 'Reward Level';
Description = 'Reward level of the customer.';
ToolTip = 'Specifies the level of reward that the customer has at this point.';
Editable = false;
}
field(RewardPoints; RewardPoints)
{
ApplicationArea = All;
Caption = 'Reward Points';
Description = 'Reward points accrued by customer';
ToolTip = 'Specifies the total number of points that the customer has at this point.';
Editable = false;
}
}
}
trigger OnAfterGetRecord();
var
CustomerRewardsMgtExt: Codeunit "Customer Rewards Ext. Mgt.";
begin
// Get the reward level associated with reward points
RewardLevel := CustomerRewardsMgtExt.GetRewardLevel(RewardPoints);
end;
var
RewardLevel: Text;
}
Customer list page extension object
A page extension object can be used to add new functionality to pages that are part of the Dynamics 365 Business Central service. The following page extension object extends the Customer List page object by adding one action control; Reward Levels to the Customer group on the page.
pageextension 50101 "Customer List Ext." extends "Customer List"
{
actions
{
addfirst("&Customer")
{
action("Reward Levels")
{
ApplicationArea = All;
Image = CustomerRating;
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
ToolTip = 'Open the list of reward levels.';
trigger OnAction();
begin
if CustomerRewardsExtMgt.IsCustomerRewardsActivated then
CustomerRewardsExtMgt.OpenRewardsLevelPage
else
CustomerRewardsExtMgt.OpenCustomerRewardsWizard;
end;
}
}
}
var
CustomerRewardsExtMgt: Codeunit "Customer Rewards Ext. Mgt.";
}
Customer Rewards codeunit objects
Customer Rewards Install Logic codeunit object
The following code adds the 50100 Customer Rewards Install Logic codeunit that initializes the default codeunit that will be used for handling events. Because this is an install codeunit, it has its Subtype property set to Install. The OnInstallAppPerCompany trigger is run when the extension is installed for the first time and the same version is reinstalled.
codeunit 50100 "Customer Rewards Install Logic"
{
// Customer Rewards Install Logic
Subtype = Install;
trigger OnInstallAppPerCompany();
begin
SetDefaultCustomerRewardsExtMgtCodeunit;
end;
procedure SetDefaultCustomerRewardsExtMgtCodeunit()
var
CustomerRewardsExtMgtSetup: Record "Customer Rewards Mgt. Setup";
begin
CustomerRewardsExtMgtSetup.DeleteAll;
CustomerRewardsExtMgtSetup.Init;
// Default Customer Rewards Ext. Mgt codeunit to use for handling events
CustomerRewardsExtMgtSetup."Customer Rewards Ext. Mgt. Codeunit ID" := Codeunit::"Customer Rewards Ext. Mgt.";
CustomerRewardsExtMgtSetup.Insert;
end;
}
Customer Rewards Ext. Mgt. codeunit object
The 50101 Customer Rewards Ext. Mgt. codeunit encapsulates most of the logic and functionality required for the Customer Rewards extension. This codeunit contains examples of how we can use events to react to specific actions or behavior that occurs within our extension. In this sample extension, there's the need to make a call to an external service or API to validate activation codes entered by the user. Typically, you may do this by defining procedures that take in the activation code and then make calls to the API. Instead of using that approach, we use events in AL. Let us look at the following code from the codeunit.
// Activates Customer Rewards if activation code is validated successfully
procedure ActivateCustomerRewards(ActivationCode: Text): Boolean
var
ActivationCodeInfo: Record "Activation Code Information";
begin
// raise event
OnGetActivationCodeStatusFromServer(ActivationCode);
exit(ActivationCodeInfo.Get(ActivationCode));
end;
// publishes event
[IntegrationEvent(false, false)]
procedure OnGetActivationCodeStatusFromServer(ActivationCode: Text)
begin
end;
// Subscribes to OnGetActivationCodeStatusFromServer event and handles it when the event is raised
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Customer Rewards Ext. Mgt.", 'OnGetActivationCodeStatusFromServer', '', false, false)]
local procedure OnGetActivationCodeStatusFromServerSubscriber(ActivationCode: Text)
var
ActivationCodeInfo: Record "Activation Code Information";
ResponseText: Text;
Result: JsonToken;
JsonRepsonse: JsonToken;
begin
if not CanHandle then
exit; // use the mock
// Get response from external service and update activation code information if successful
if(GetHttpResponse(ActivationCode, ResponseText)) then begin
JsonRepsonse.ReadFrom(ResponseText);
if(JsonRepsonse.SelectToken('ActivationResponse', Result)) then begin
if(Result.AsValue().AsText() = 'Success') then begin
if(ActivationCodeInfo.FindFirst()) then
ActivationCodeInfo.Delete;
ActivationCodeInfo.Init;
ActivationCodeInfo.ActivationCode := ActivationCode;
ActivationCodeInfo."Date Activated" := Today;
ActivationCodeInfo."Expiration Date" := CALCDATE('<1Y>', Today);
ActivationCodeInfo.Insert;
end;
end;
end;
end;
// Helper method to make calls to a service to validate activation code
local procedure GetHttpResponse(ActivationCode: Text; var ResponseText: Text): Boolean
begin
// You will typically make external calls / http requests to your service to validate the activation code
// here but for the sample extension we simply return a successful dummy response
if ActivationCode = '' then
exit(false);
ResponseText := DummySuccessResponseTxt;
exit(true);
end;
We define an event publisher method OnGetActivationCodeStatusFromServer that accepts the activation code entered by the user as a parameter, and, a subscriber method OnGetActivationCodeStatusFromServerSubscriber to listen for and handle the event. When the ActivateCustomerRewards procedure is run, the OnGetActivationCodeStatusFromServer event is raised. Because the EventSubscriberInstance property for the codeunit is set to Static-Automatic by default, the OnGetActivationCodeStatusFromServerSubscriber procedure is called. In this procedure, we handle the raised event by first checking if the current codeunit has been defined for handling this event. If the codeunit can handle the event, the GetHttpResponse helper procedure is called to validate the activation code. Depending on the response, Customer Rewards is activated or not.
By using events when the extension makes external calls to a service, we're able to mock the behavior of what happens when events are raised. This becomes useful when writing tests for the extension.
For more information about events, see Events in Microsoft Dynamics 365 Business Central.
Below is the full code for this codeunit.
codeunit 50101 "Customer Rewards Ext. Mgt."
{
var
DummySuccessResponseTxt: Label '{"ActivationResponse": "Success"}', Locked = true;
NoRewardlevelTxt: Label 'NONE';
// Determines if the extension is activated
procedure IsCustomerRewardsActivated(): Boolean
var
ActivationCodeInfo: Record "Activation Code Information";
begin
if not ActivationCodeInfo.FindFirst then
exit(false);
if(ActivationCodeInfo."Date Activated" <= Today) and(Today <= ActivationCodeInfo."Expiration Date") then
exit(true);
exit(false);
end;
// Opens the Customer Rewards Assisted Setup Guide
procedure OpenCustomerRewardsWizard()
var
CustomerRewardsWizard: Page "Customer Rewards Wizard";
begin
CustomerRewardsWizard.RunModal;
end;
// Opens the Reward Level page
procedure OpenRewardsLevelPage()
var
RewardsLevelPage: Page "Rewards Level List";
begin
RewardsLevelPage.Run;
end;
// Determines the correponding reward level and returns it
procedure GetRewardLevel(RewardPoints: Integer) RewardLevelTxt: Text
var
RewardLevelRec: Record "Reward Level";
MinRewardLevelPoints: Integer;
begin
RewardLevelTxt := NoRewardlevelTxt;
if RewardLevelRec.IsEmpty then
exit;
RewardLevelRec.SetRange("Minimum Reward Points", 0, RewardPoints);
RewardLevelRec.SetCurrentKey("Minimum Reward Points"); // sorted in ascending order
if not RewardLevelRec.FindFirst then
exit;
MinRewardLevelPoints := RewardLevelRec."Minimum Reward Points";
if RewardPoints >= MinRewardLevelPoints then begin
RewardLevelRec.Reset;
RewardLevelRec.SetRange("Minimum Reward Points", MinRewardLevelPoints, RewardPoints);
RewardLevelRec.SetCurrentKey("Minimum Reward Points"); // sorted in ascending order
RewardLevelRec.FindLast;
RewardLevelTxt := RewardLevelRec.Level;
end;
end;
// Activates Customer Rewards if activation code is validated successfully
procedure ActivateCustomerRewards(ActivationCode: Text): Boolean
var
ActivationCodeInfo: Record "Activation Code Information";
begin
// raise event
OnGetActivationCodeStatusFromServer(ActivationCode);
exit(ActivationCodeInfo.Get(ActivationCode));
end;
// publishes event
[IntegrationEvent(false, false)]
procedure OnGetActivationCodeStatusFromServer(ActivationCode: Text)
begin
end;
// Subscribes to OnGetActivationCodeStatusFromServer event and handles it when the event is raised
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Customer Rewards Ext. Mgt.", 'OnGetActivationCodeStatusFromServer', '', false, false)]
local procedure OnGetActivationCodeStatusFromServerSubscriber(ActivationCode: Text)
var
ActivationCodeInfo: Record "Activation Code Information";
ResponseText: Text;
Result: JsonToken;
JsonRepsonse: JsonToken;
begin
if not CanHandle then
exit; // use the mock
// Get response from external service and update activation code information if successful
if(GetHttpResponse(ActivationCode, ResponseText)) then begin
JsonRepsonse.ReadFrom(ResponseText);
if(JsonRepsonse.SelectToken('ActivationResponse', Result)) then begin
if(Result.AsValue().AsText() = 'Success') then begin
if(ActivationCodeInfo.FindFirst()) then
ActivationCodeInfo.Delete;
ActivationCodeInfo.Init;
ActivationCodeInfo.ActivationCode := ActivationCode;
ActivationCodeInfo."Date Activated" := Today;
ActivationCodeInfo."Expiration Date" := CALCDATE('<1Y>', Today);
ActivationCodeInfo.Insert;
end;
end;
end;
end;
// Helper method to make calls to a service to validate activation code
local procedure GetHttpResponse(ActivationCode: Text; var ResponseText: Text): Boolean
begin
// You will typically make external calls / http requests to your service to validate the activation code
// here but for the sample extension we simply return a successful dummy response
if ActivationCode = '' then
exit(false);
ResponseText := DummySuccessResponseTxt;
exit(true);
end;
// Subcribes to the OnAfterReleaseSalesDoc event and increases reward points for the sell to customer in posted sales order
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Sales Document", 'OnAfterReleaseSalesDoc', '', false, false)]
local procedure OnAfterReleaseSalesDocSubscriber(VAR SalesHeader: Record "Sales Header"; PreviewMode: Boolean; LinesWereModified: Boolean)
var
Customer: Record Customer;
begin
if SalesHeader.Status <> SalesHeader.Status::Released then
exit;
Customer.Get(SalesHeader."Sell-to Customer No.");
Customer.RewardPoints += 1; // Add a point for each new sales order
Customer.Modify;
end;
// Checks if the current codeunit is allowed to handle Customer Rewards Activation requests rather than a mock.
local procedure CanHandle(): Boolean
var
CustomerRewardsExtMgtSetup: Record "Customer Rewards Mgt. Setup";
begin
if CustomerRewardsExtMgtSetup.Get then
exit(CustomerRewardsExtMgtSetup."Customer Rewards Ext. Mgt. Codeunit ID" = CODEUNIT::"Customer Rewards Ext. Mgt.");
exit(false);
end;
}
Conclusion
At this point, the Customer Rewards sample extension can be published and installed on your sandbox. To continue writing tests for the sample extension, see Test the advanced sample extension.
See Also
Developing Extensions
Get Started with AL
How to: Publish and Install an Extension
Converting Extensions V1 to Extensions V2
Configure Context-Sensitive Help