Microsoft AL App Extensions
repository·main·Indexed 21 days ago
https://github.com/microsoft/alappextensionsA repository for requesting extensibility enhancements for Microsoft Dynamics 365 Business Central, such as new integration events, visibility changes, and enum updates. It also contains source code and documentation for the Contoso Coffee Demo Data Set, the AI Development Toolkit (including Design and Evaluation apps), and the Microsoft Universal Print extension.
What's inside alappextensions
- This extension provides functionality to use Microsoft Universal Print. It allows users to print to any printer managed by their organization from any device using their Azure Active Directory (AAD) credentials.
Overview of the Contoso Coffee Demo Data Set
mainThe Contoso Coffee Demo Data Set provides fictional company data for Business Central, allowing users to learn and explore the platform's capabilities. Unlike the legacy rapidstart packages, the modern Demo Data App uses an extensibility-focused, modular approach. It allows for scenario-based demonstrations by selecting specific modules and their dependencies during company creation.Understand the Demo Data Layers
mainData within a demo data module is organized into four distinct layers to allow for progressive demonstration depth:
- Setup: Foundational elements used in production companies, such as GL accounts and VAT posting.
- Master: Primary records used for demonstrations, such as customers and vendors.
- Transactional: Open documents, such as sales and purchase invoices.
- Historical: Posted documents used to facilitate reporting and analytics demonstrations.
Manage module dependencies and execution order
mainThe Contoso Demo Tool supports dependencies between modules, allowing one module to leverage data generated by another.
Execution Order Pattern:
- Setup Data (e.g., Foundation)
- Setup Data (e.g., Finance)
- Master Data (e.g., Foundation)
- Master Data (e.g., Finance)
Critical Requirement: When referencing data from a previous module, always use
.Validate()instead of direct value assignment. This ensures that the referenced data is properly processed and prevents errors if the data does not yet exist.Implement GL Account localization patterns
mainGL Accounts follow a specific lifecycle to allow localization apps to modify account numbers. The process involves registering a base account for the W1 localization and then allowing subsequent localization apps to intercept and modify that account number via an integration event.
The GL Account Lifecycle
- Initialization: Call
AddGLAccountsForLocalization()at the start of your process. This procedure must add the base account for the W1 localization usingContosoGLAccount.AddAccountForLocalization(AccountName, AccountNo). This creates a key-value pair in the temporarytable 4769 "Contoso GL Account"(e.g.,'Employees Payable' -> '5850'). - Interception: Immediately after adding the W1 account, fire the
OnAfterAddGLAccountsForLocalization()integration event. This allows localization apps (e.g., a Canada localization app) to callAddAccountForLocalizationagain with the sameAccountNamebut a differentAccountNo(e.g.,'Employees Payable' -> '23850'). - Resolution: When the application needs the account number, call the descriptive procedure (e.g.,
EmployeesPayable()). This procedure should useContosoGLAccount.GetAccountNo(AccountName)to look up the current localized value from the temporary table.
Implementation Example
To implement this pattern, define a descriptive procedure for the account number and a name procedure, and manage the registration in a setup procedure.
// Example implementation of the GL Account pattern trigger OnRun() begin AddGLAccountsForLocalization(); // Use the descriptive procedure to get the (potentially localized) account number ContosoGLAccount.InsertGLAccount(EmployeesPayable(), EmployeesPayableName(), ...); end; local procedure AddGLAccountsForLocalization() begin // 1. Add the base W1 account ContosoGLAccount.AddAccountForLocalization(EmployeesPayableName(), '5850'); // 2. Fire event to allow localization apps to modify the AccountNo OnAfterAddGLAccountsForLocalization(); end; var ContosoGLAccount: Codeunit "Contoso GL Account"; EmployeesPayableLbl: Label 'Employees Payable', MaxLength = 100; // 3. The consumer calls this to get the localized AccountNo procedure EmployeesPayable(): Code[20] begin exit(ContosoGLAccount.GetAccountNo(EmployeesPayableName())); end; procedure EmployeesPayableName(): Text[100] begin exit(EmployeesPayableLbl); end; [IntegrationEvent(false, false)] local procedure OnAfterAddGLAccountsForLocalization() begin end;- Initialization: Call
Use descriptive methods for reusable labels
mainTo ensure data consistency and translation accuracy, avoid using raw label strings directly in business logic. Instead, define descriptive methods that return specific labels. This prevents typos across different tables and ensures that the same concept (e.g., 'Sneakers') is translated consistently across all documents.
Pattern:
- Define
Labelvariables with appropriateMaxLength. - Create a procedure that returns the specific label token.
procedure Sneakers(): Code[20] begin exit(SneakersTok); end; var SneakersTok: Label 'SNEAKERS', MaxLength = 20;- Define
Types of accepted extensibility requests
mainThe repository accepts specific categories of extensibility requests to unblock app development. Use the following classifications when submitting your request:
- Event-request: Requesting a new integration event.
- Request-for-external: Requesting that a function be made external or otherwise callable from an extension.
- Enum-request: Requesting that an option be replaced with an extensible enum.
- Extensibility-enhancement: Requesting broader changes that improve overall extensibility.
- Extensibility-bug: Requesting small fixes specifically to unblock an extensibility scenario.
Extensibility and Customization features
mainThe Contoso Demo Tool is designed for partners to customize and extend demo scenarios through several mechanisms:
- Helpers Codeunit: Provides a dedicated codeunit to simplify the insertion and updating of records in commonly used tables.
- Localization: Uses a localization extension approach where country-specific versions of Contoso are provided to localize data.
- Extensibility Points: Offers multiple hooks before running the tool and between each data layer, allowing partners to customize data flow.
- Translation: Uses a code-based approach to provide multiple translation apps, replacing the old file-based rapidstart method.
Where to report issues and contributions for Business Central
mainThis repository is strictly for extensibility requests. Do not use it for the following purposes:
Goal Correct Destination Code contributions and Pull Requests BCApps Product ideas BC Ideas Product defects or customer-impacting issues Business Central support Note: Issues filed here do not fall under SLAs, have no guaranteed mitigation time, and fixes are not guaranteed to be backported to all supported versions.
Use Helper Codeunits for data insertion
mainHelper codeunits (found in
DemoTool/Contoso Helpers/...) simplify inserting demo data by hiding complexity and handling cross-company logic (e.g., W1 vs NA).Key Features of Helpers:
- Company Type Abstraction: Automatically handles
Company Typelogic. - Conflict Management: Use the
ExistsandOverwriteDatavariables to control behavior when a record already exists. IfOverwriteDatais true, the helper will update the existing record instead of throwing an error.
// Example: Inserting a Product Posting Group using a helper procedure InsertGenProductPostingGroup(ProductGroupCode: Code[20]; Description: Text[100]; DefaultVATProdPostingGroup: Code[20]) var ContosoCoffeeDemoDataSetup: Record "Contoso Coffee Demo Data Setup"; GenProductPostingGroup: Record "Gen. Product Posting Group"; Exists: Boolean; begin ContosoCoffeeDemoDataSetup.Get(); if GenProductPostingGroup.Get(ProductGroupCode) then begin Exists := true; if not OverwriteData then exit; end; GenProductPostingGroup.Validate(Code, ProductGroupCode); GenProductPostingGroup.Validate(Description, Description); if ContosoCoffeeDemoDataSetup."Company Type" = ContosoCoffeeDemoDataSetup."Company Type"::VAT then GenProductPostingGroup.Validate("Def. VAT Prod. Posting Group", DefaultVATProdPostingGroup); if Exists then GenProductPostingGroup.Modify(true) else GenProductPostingGroup.Insert(true); end;- Company Type Abstraction: Automatically handles
How to create a new company with targeted demo data
mainThe new company creation wizard has replaced the old method of importing full rapidstart packages. Instead of loading all data, users can now select specific modules required for a particular demonstration. The wizard automatically identifies and executes the selected modules along with their necessary dependencies.Add Module Configuration (Optional)
mainTo allow users to configure your module, implement a setup table and a configuration page. Ensure your data generation logic uses the values stored in these configuration tables.
Setup Table Requirements:
- Define fields for configuration (e.g.,
StartDate). - Use
InherentPermissionsfor initialization logic. - Set
DataPerCompany = trueif configuration is company-specific.
Setup Page Requirements:
- Use
PageType = Card. - Set
SourceTableto your setup table. - Use
OnOpenPageto trigger record initialization (e.g.,Rec.InitRecord()).
table 5282 "Module Setup" { DataClassification = CustomerContent; InherentEntitlements = RMX; InherentPermissions = RMX; Extensible = false; DataPerCompany = true; ReplicateData = false; fields { field(1; "Primary Key"; Integer) { DataClassification = SystemMetadata; } field(2; StartDate; Date) { Caption = 'Start Date'; } } keys { key(Key1; "Primary Key") { Clustered = true; } } [InherentPermissions(PermissionObjectType::TableData, Database::"Module Setup", 'I')] internal procedure InitRecord() begin if Rec.Get() then exit; Rec.Insert(); end; } page 5281 "Module Setup" { PageType = Card; SourceTable = "Module Setup"; layout { area(Content) { group("Setup Data") { field(StartDate; Rec.StartDate) { } } } } trigger OnOpenPage() begin Rec.InitRecord(); end; }table 5282 "Module Setup" { DataClassification = CustomerContent; InherentEntitlements = RMX; InherentPermissions = RMX; Extensible = false; DataPerCompany = true; ReplicateData = false; fields { field(1; "Primary Key"; Integer) { DataClassification = SystemMetadata; Caption = 'Primary Key'; } field(2; StartDate; Date) { Caption = 'Start Date'; ToolTip = 'Specifies the start date for the scenario.'; } } keys { key(Key1; "Primary Key") { Clustered = true; } } [InherentPermissions(PermissionObjectType::TableData, Database::"Module Setup", 'I')] internal procedure InitRecord() begin if Rec.Get() then exit; Rec.Insert(); end; } page 5281 "Module Setup" { PageType = Card; Caption = 'Module Setup'; SourceTable = "Module Setup"; Extensible = false; DeleteAllowed = false; InsertAllowed = false; layout { area(Content) { group("Setup Data") { field(StartDate; Rec.StartDate) { } } } } trigger OnOpenPage() begin Rec.InitRecord(); end; }- Define fields for configuration (e.g.,