From bcc01b3996f3e723e2304ede5a8904d6ff6425d4 Mon Sep 17 00:00:00 2001 From: Jesper Schulz-Wedde Date: Fri, 18 Sep 2026 15:42:44 +0200 Subject: [PATCH] Add foundational AL developer knowledge Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ize-document-defaults-in-initrecord.bad.al | 28 ++++++++++++ ...ze-document-defaults-in-initrecord.good.al | 45 +++++++++++++++++++ ...tialize-document-defaults-in-initrecord.md | 30 +++++++++++++ ...und-direction-symbols-use-magnitude.bad.al | 8 ++++ ...nd-direction-symbols-use-magnitude.good.al | 10 +++++ .../round-direction-symbols-use-magnitude.md | 30 +++++++++++++ .../guard-interface-casts-with-is.bad.al | 20 +++++++++ .../guard-interface-casts-with-is.good.al | 24 ++++++++++ .../guard-interface-casts-with-is.md | 30 +++++++++++++ ...oup-respects-lookup-page-visibility.bad.al | 9 ++++ ...up-respects-lookup-page-visibility.good.al | 20 +++++++++ ...ldgroup-respects-lookup-page-visibility.md | 30 +++++++++++++ ...ropagation-both-refreshes-main-page.bad.al | 26 +++++++++++ ...opagation-both-refreshes-main-page.good.al | 27 +++++++++++ ...atepropagation-both-refreshes-main-page.md | 30 +++++++++++++ ...on-meaning-depends-on-execution-context.md | 26 +++++++++++ ...and-upgrade-codeunits-have-no-order.bad.al | 28 ++++++++++++ ...nd-upgrade-codeunits-have-no-order.good.al | 18 ++++++++ ...all-and-upgrade-codeunits-have-no-order.md | 30 +++++++++++++ .../skills/review/al-data-modeling-review.md | 4 +- .../skills/review/al-interfaces-review.md | 3 +- microsoft/skills/review/al-ui-review.md | 7 ++- microsoft/skills/review/al-upgrade-review.md | 4 +- 23 files changed, 483 insertions(+), 4 deletions(-) create mode 100644 microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.bad.al create mode 100644 microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.good.al create mode 100644 microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.md create mode 100644 microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.bad.al create mode 100644 microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.good.al create mode 100644 microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.md create mode 100644 microsoft/knowledge/interfaces/guard-interface-casts-with-is.bad.al create mode 100644 microsoft/knowledge/interfaces/guard-interface-casts-with-is.good.al create mode 100644 microsoft/knowledge/interfaces/guard-interface-casts-with-is.md create mode 100644 microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.bad.al create mode 100644 microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.good.al create mode 100644 microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.md create mode 100644 microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.bad.al create mode 100644 microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.good.al create mode 100644 microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.md create mode 100644 microsoft/knowledge/upgrade/appversion-meaning-depends-on-execution-context.md create mode 100644 microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.bad.al create mode 100644 microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.good.al create mode 100644 microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.md diff --git a/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.bad.al b/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.bad.al new file mode 100644 index 00000000..0c87bb2a --- /dev/null +++ b/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.bad.al @@ -0,0 +1,28 @@ +table 50603 "Sample Order Header Bad" +{ + fields + { + field(1; "No."; Code[20]) + { + DataClassification = CustomerContent; + } + field(2; "Document Date"; Date) + { + DataClassification = CustomerContent; + } + } + + trigger OnInsert() + var + SalesSetup: Record "Sales & Receivables Setup"; + NoSeries: Codeunit "No. Series"; + begin + "Document Date" := WorkDate(); + + if "No." = '' then begin + SalesSetup.Get(); + SalesSetup.TestField("Order Nos."); + "No." := NoSeries.GetNextNo(SalesSetup."Order Nos."); + end; + end; +} diff --git a/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.good.al b/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.good.al new file mode 100644 index 00000000..355d3d54 --- /dev/null +++ b/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.good.al @@ -0,0 +1,45 @@ +table 50602 "Sample Order Header Good" +{ + fields + { + field(1; "No."; Code[20]) + { + DataClassification = CustomerContent; + } + field(2; "Document Date"; Date) + { + DataClassification = CustomerContent; + } + } + + trigger OnInsert() + var + SalesSetup: Record "Sales & Receivables Setup"; + NoSeries: Codeunit "No. Series"; + begin + if "No." = '' then begin + SalesSetup.Get(); + SalesSetup.TestField("Order Nos."); + "No." := NoSeries.GetNextNo(SalesSetup."Order Nos."); + end; + + InitRecord(); + end; + + procedure InitRecord() + begin + OnBeforeInitRecord(Rec); + "Document Date" := WorkDate(); + OnAfterInitRecord(Rec); + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeInitRecord(var SampleOrderHeader: Record "Sample Order Header Good") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterInitRecord(var SampleOrderHeader: Record "Sample Order Header Good") + begin + end; +} diff --git a/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.md b/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.md new file mode 100644 index 00000000..e83a6d34 --- /dev/null +++ b/microsoft/knowledge/data-modeling/initialize-document-defaults-in-initrecord.md @@ -0,0 +1,30 @@ +--- +bc-version: [all] +domain: data-modeling +keywords: [document-header, initrecord, number-series, default-values, oninsert, initialization] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Initialize document defaults in `InitRecord` after assigning the number + +## Description + +Business Central document headers assign their number series first and then call an `InitRecord` procedure that owns the remaining business defaults, such as posting and document dates. Keeping that sequence and extensibility point makes initialization consistent for every creation path and lets extensions subscribe around one documented operation. Defaults scattered across page triggers or unrelated helpers can differ between UI, API, test, and background creation. + +## Best Practice + +In the document table's insert path, assign the document number and then call `InitRecord`. Keep the default assignments in that procedure and expose narrow before/after events when other extensions must participate. + +See sample: [`initialize-document-defaults-in-initrecord.good.al`](initialize-document-defaults-in-initrecord.good.al). + +## Anti Pattern + +Assigning document defaults in a page trigger, or scattering them directly through `OnInsert` with no `InitRecord` boundary. Non-page creation paths can then miss the defaults, and extensions have no stable initialization hook. + +See sample: [`initialize-document-defaults-in-initrecord.bad.al`](initialize-document-defaults-in-initrecord.bad.al). + +## Reference + +[Use the InitRecord function](https://learn.microsoft.com/en-us/training/modules/use-document-standards-business-central/3-use-initrecord-function) diff --git a/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.bad.al b/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.bad.al new file mode 100644 index 00000000..f8e11cca --- /dev/null +++ b/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.bad.al @@ -0,0 +1,8 @@ +codeunit 50601 "Directed Rounding Bad" +{ + procedure FloorAmount(Value: Decimal; Precision: Decimal): Decimal + begin + // For negative values, '<' rounds toward zero rather than toward negative infinity. + exit(Round(Value, Precision, '<')); + end; +} diff --git a/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.good.al b/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.good.al new file mode 100644 index 00000000..cd8a80fc --- /dev/null +++ b/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.good.al @@ -0,0 +1,10 @@ +codeunit 50600 "Directed Rounding Good" +{ + procedure RoundAmount(Value: Decimal; Precision: Decimal; IncreaseMagnitude: Boolean): Decimal + begin + if IncreaseMagnitude then + exit(Round(Value, Precision, '>')); + + exit(Round(Value, Precision, '<')); + end; +} diff --git a/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.md b/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.md new file mode 100644 index 00000000..260905db --- /dev/null +++ b/microsoft/knowledge/data-modeling/round-direction-symbols-use-magnitude.md @@ -0,0 +1,30 @@ +--- +bc-version: [all] +domain: data-modeling +keywords: [round, rounding, direction, precision, negative-decimal, amount] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# `Round` direction symbols follow magnitude, not mathematical ordering + +## Description + +AL's `Round(Number, Precision, Direction)` uses `'>'` to round away from zero and `'<'` to round toward zero. For a negative value this reverses mathematical ordering: `Round(-1234.56789, 0.001, '<')` returns `-1234.567`, while direction `'>'` returns `-1234.568`. Code that treats the symbols as mathematical ceiling and floor produces sign-dependent amount errors, commonly on credit documents and negative adjustments. + +## Best Practice + +Choose the direction from the business meaning: `'>'` increases absolute magnitude and `'<'` decreases absolute magnitude for both positive and negative values. Include positive and negative cases whenever a directed rounding rule is tested. + +See sample: [`round-direction-symbols-use-magnitude.good.al`](round-direction-symbols-use-magnitude.good.al). + +## Anti Pattern + +Using `'<'` as a mathematical floor or `'>'` as a mathematical ceiling. The result looks correct for positive amounts but moves in the opposite mathematical direction for negative amounts. + +See sample: [`round-direction-symbols-use-magnitude.bad.al`](round-direction-symbols-use-magnitude.bad.al). + +## Reference + +[Use the Round function](https://learn.microsoft.com/en-us/training/modules/use-document-standards-business-central/4a-use-round-function) diff --git a/microsoft/knowledge/interfaces/guard-interface-casts-with-is.bad.al b/microsoft/knowledge/interfaces/guard-interface-casts-with-is.bad.al new file mode 100644 index 00000000..4fd98be0 --- /dev/null +++ b/microsoft/knowledge/interfaces/guard-interface-casts-with-is.bad.al @@ -0,0 +1,20 @@ +interface "I Quote Amount Bad" +{ + procedure GetAmount(): Decimal; +} + +interface "I Quote Date Bad" +{ + procedure GetDate(): Date; +} + +codeunit 50611 "Quote Reader Bad" +{ + procedure GetDate(Quote: Interface "I Quote Amount Bad"): Date + var + DatedQuote: Interface "I Quote Date Bad"; + begin + DatedQuote := Quote as "I Quote Date Bad"; + exit(DatedQuote.GetDate()); + end; +} diff --git a/microsoft/knowledge/interfaces/guard-interface-casts-with-is.good.al b/microsoft/knowledge/interfaces/guard-interface-casts-with-is.good.al new file mode 100644 index 00000000..f58a19d8 --- /dev/null +++ b/microsoft/knowledge/interfaces/guard-interface-casts-with-is.good.al @@ -0,0 +1,24 @@ +interface "I Quote Amount Good" +{ + procedure GetAmount(): Decimal; +} + +interface "I Quote Date Good" +{ + procedure GetDate(): Date; +} + +codeunit 50610 "Quote Reader Good" +{ + procedure TryGetDate(Quote: Interface "I Quote Amount Good"; var QuoteDate: Date): Boolean + var + DatedQuote: Interface "I Quote Date Good"; + begin + if not (Quote is "I Quote Date Good") then + exit(false); + + DatedQuote := Quote as "I Quote Date Good"; + QuoteDate := DatedQuote.GetDate(); + exit(true); + end; +} diff --git a/microsoft/knowledge/interfaces/guard-interface-casts-with-is.md b/microsoft/knowledge/interfaces/guard-interface-casts-with-is.md new file mode 100644 index 00000000..ad9c38e2 --- /dev/null +++ b/microsoft/knowledge/interfaces/guard-interface-casts-with-is.md @@ -0,0 +1,30 @@ +--- +bc-version: [25..] +domain: interfaces +keywords: [interface, is-operator, as-operator, type-test, cast, variant, runtime-error] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Guard optional interface casts with `is` + +## Description + +From runtime 15.0, AL can type-test an interface or `Variant` with `is` and cast it to another interface with `as`. The test is non-throwing, but `as` raises a runtime error when the underlying codeunit does not implement the target interface. This matters when an extended capability is optional or implementations can come from other extensions. + +## Best Practice + +Use `is` to establish that the value supports the target interface before using `as`. Cast directly only where the target implementation is an invariant guaranteed by the surrounding contract. + +See sample: [`guard-interface-casts-with-is.good.al`](guard-interface-casts-with-is.good.al). + +## Anti Pattern + +Using `as` unconditionally for an optional extended interface. An otherwise valid implementation of the base interface then fails at runtime merely because it does not implement the additional contract. + +See sample: [`guard-interface-casts-with-is.bad.al`](guard-interface-casts-with-is.bad.al). + +## Reference + +[Understand type testing and casting operators for interfaces](https://learn.microsoft.com/en-us/training/modules/business-central-interfaces/type-testing) diff --git a/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.bad.al b/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.bad.al new file mode 100644 index 00000000..78602b4d --- /dev/null +++ b/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.bad.al @@ -0,0 +1,9 @@ +tableextension 50622 "Ship-to Dropdown Bad" extends "Ship-to Address" +{ + fieldgroups + { + addlast(DropDown; "Address 2") + { + } + } +} diff --git a/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.good.al b/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.good.al new file mode 100644 index 00000000..c8a8c28c --- /dev/null +++ b/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.good.al @@ -0,0 +1,20 @@ +tableextension 50620 "Ship-to Dropdown Good" extends "Ship-to Address" +{ + fieldgroups + { + addlast(DropDown; "Address 2") + { + } + } +} + +pageextension 50621 "Ship-to Lookup Good" extends "Ship-to Address List" +{ + layout + { + modify("Address 2") + { + Visible = true; + } + } +} diff --git a/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.md b/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.md new file mode 100644 index 00000000..afa2c34d --- /dev/null +++ b/microsoft/knowledge/ui/dropdown-fieldgroup-respects-lookup-page-visibility.md @@ -0,0 +1,30 @@ +--- +bc-version: [all] +domain: ui +keywords: [fieldgroup, dropdown, addlast, lookup-page, visible, tableextension, pageextension] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# A `DropDown` field remains hidden when its lookup-page control is hidden + +## Description + +A tableextension can append a field to the `DropDown` field group with `addlast`, but the client still omits that field when its control on the underlying lookup page has `Visible = false`. Changing only the table field group therefore compiles while producing no visible UI change. The field-group name is case-sensitive and must be written as `DropDown`. + +## Best Practice + +When adding a hidden field to a `DropDown` field group, also extend the page used for the lookup and make that field control visible. Verify the actual lookup page rather than assuming the table definition alone controls the drop-down. + +See sample: [`dropdown-fieldgroup-respects-lookup-page-visibility.good.al`](dropdown-fieldgroup-respects-lookup-page-visibility.good.al). + +## Anti Pattern + +Adding the field with `addlast(DropDown; ...)` while leaving its lookup-page control hidden, then expecting the field to appear in the drop-down. + +See sample: [`dropdown-fieldgroup-respects-lookup-page-visibility.bad.al`](dropdown-fieldgroup-respects-lookup-page-visibility.bad.al). + +## Reference + +[Add a new FieldGroup to an existing table](https://learn.microsoft.com/en-us/training/modules/extend-modify-existing-table/add-field-group) diff --git a/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.bad.al b/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.bad.al new file mode 100644 index 00000000..f5d2b2be --- /dev/null +++ b/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.bad.al @@ -0,0 +1,26 @@ +page 50631 "Sample Order Bad" +{ + PageType = Document; + SourceTable = "Sales Header"; + + layout + { + area(Content) + { + group(General) + { + field(Amount; Rec.Amount) + { + ApplicationArea = All; + ToolTip = 'Specifies the total amount of the order.'; + } + } + part(Lines; "Sales Order Subform") + { + ApplicationArea = All; + SubPageLink = "Document Type" = field("Document Type"), + "Document No." = field("No."); + } + } + } +} diff --git a/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.good.al b/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.good.al new file mode 100644 index 00000000..34620981 --- /dev/null +++ b/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.good.al @@ -0,0 +1,27 @@ +page 50630 "Sample Order Good" +{ + PageType = Document; + SourceTable = "Sales Header"; + + layout + { + area(Content) + { + group(General) + { + field(Amount; Rec.Amount) + { + ApplicationArea = All; + ToolTip = 'Specifies the total amount of the order.'; + } + } + part(Lines; "Sales Order Subform") + { + ApplicationArea = All; + SubPageLink = "Document Type" = field("Document Type"), + "Document No." = field("No."); + UpdatePropagation = Both; + } + } + } +} diff --git a/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.md b/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.md new file mode 100644 index 00000000..3ce676b5 --- /dev/null +++ b/microsoft/knowledge/ui/updatepropagation-both-refreshes-main-page.md @@ -0,0 +1,30 @@ +--- +bc-version: [all] +domain: ui +keywords: [updatepropagation, page-part, subpage, main-page, refresh, flowfield, document-lines] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Use `UpdatePropagation = Both` when line edits must refresh the main page + +## Description + +A page part does not automatically refresh its parent page when the subpage changes. `UpdatePropagation = Subpage` updates only the part; `Both` also refreshes the main page. Without `Both`, header totals, FlowFields, and FactBoxes that depend on edited lines can remain stale until another user action refreshes the page. + +## Best Practice + +Set `UpdatePropagation = Both` on a part when edits in that subpage must immediately update values rendered by the main page. Leave propagation at `Subpage` when the parent has no dependent presentation to avoid unnecessary refreshes. + +See sample: [`updatepropagation-both-refreshes-main-page.good.al`](updatepropagation-both-refreshes-main-page.good.al). + +## Anti Pattern + +Displaying a line-dependent total on the main page while the editable lines part updates only itself. The persisted values can be correct while the parent page continues to show an old total. + +See sample: [`updatepropagation-both-refreshes-main-page.bad.al`](updatepropagation-both-refreshes-main-page.bad.al). + +## Reference + +[Set different control properties](https://learn.microsoft.com/en-us/training/modules/work-with-pages/8-controls) diff --git a/microsoft/knowledge/upgrade/appversion-meaning-depends-on-execution-context.md b/microsoft/knowledge/upgrade/appversion-meaning-depends-on-execution-context.md new file mode 100644 index 00000000..61e74226 --- /dev/null +++ b/microsoft/knowledge/upgrade/appversion-meaning-depends-on-execution-context.md @@ -0,0 +1,26 @@ +--- +bc-version: [all] +domain: upgrade +keywords: [appversion, dataversion, moduleinfo, install-codeunit, upgrade-codeunit, version-context] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# `ModuleInfo.AppVersion` changes meaning with execution context + +## Description + +`ModuleInfo.AppVersion()` is the installed version during normal operation, the version being installed inside install code, and the target version inside upgrade code. It is therefore not the source data version during an upgrade. In upgrade code, `DataVersion()` describes the version of the existing data, whether from the currently installed app or the version most recently uninstalled. + +## Best Practice + +Interpret `AppVersion()` as the code package entering the context and `DataVersion()` as the existing data state. Prefer upgrade tags for controlling individual migration steps; when version information is needed for diagnostics or preconditions, name variables so target app version and source data version cannot be confused. + +## Anti Pattern + +Reading `AppVersion()` from an upgrade codeunit and treating it as the version being upgraded from. The comparison actually observes the target package and can skip or misroute migration logic. + +## Reference + +[Create proper installation and upgrade codeunits](https://learn.microsoft.com/en-us/training/modules/easy-application-upgrade/3-installation-upgrade-codeunits) diff --git a/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.bad.al b/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.bad.al new file mode 100644 index 00000000..2a92d112 --- /dev/null +++ b/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.bad.al @@ -0,0 +1,28 @@ +codeunit 50641 "Sample Upgrade Part One" +{ + Subtype = Upgrade; + + trigger OnUpgradePerCompany() + begin + CreateUpgradeState(); + end; + + local procedure CreateUpgradeState() + begin + end; +} + +codeunit 50642 "Sample Upgrade Part Two" +{ + Subtype = Upgrade; + + trigger OnUpgradePerCompany() + begin + // This can run before Part One; object IDs do not sequence upgrade codeunits. + MigrateDataThatRequiresUpgradeState(); + end; + + local procedure MigrateDataThatRequiresUpgradeState() + begin + end; +} diff --git a/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.good.al b/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.good.al new file mode 100644 index 00000000..42346a88 --- /dev/null +++ b/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.good.al @@ -0,0 +1,18 @@ +codeunit 50640 "Sample Upgrade Good" +{ + Subtype = Upgrade; + + trigger OnUpgradePerCompany() + begin + CreateUpgradeState(); + MigrateDependentData(); + end; + + local procedure CreateUpgradeState() + begin + end; + + local procedure MigrateDependentData() + begin + end; +} diff --git a/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.md b/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.md new file mode 100644 index 00000000..b11a8425 --- /dev/null +++ b/microsoft/knowledge/upgrade/install-and-upgrade-codeunits-have-no-order.md @@ -0,0 +1,30 @@ +--- +bc-version: [all] +domain: upgrade +keywords: [install-codeunit, upgrade-codeunit, execution-order, subtype-install, subtype-upgrade, sequencing] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Separate install or upgrade codeunits have no execution order + +## Description + +An extension can contain multiple `Install` or `Upgrade` codeunits, but Business Central does not guarantee the order in which codeunits of the same subtype execute. Upgrade trigger phases are ordered globally, yet one codeunit's `OnUpgradePerCompany` must not assume another codeunit's same-phase trigger already ran. Object ID and source-file order do not provide sequencing. + +## Best Practice + +Keep separate install or upgrade codeunits independent. When two steps have a real dependency, coordinate them from one owning trigger in the required order; use upgrade tags to make each completed step idempotent. + +See sample: [`install-and-upgrade-codeunits-have-no-order.good.al`](install-and-upgrade-codeunits-have-no-order.good.al). + +## Anti Pattern + +Splitting dependent steps into separate codeunits and relying on names, object IDs, or declaration order. The dependent codeunit can run first and fail or observe partially migrated data. + +See sample: [`install-and-upgrade-codeunits-have-no-order.bad.al`](install-and-upgrade-codeunits-have-no-order.bad.al). + +## Reference + +[Create proper installation and upgrade codeunits](https://learn.microsoft.com/en-us/training/modules/easy-application-upgrade/3-installation-upgrade-codeunits) diff --git a/microsoft/skills/review/al-data-modeling-review.md b/microsoft/skills/review/al-data-modeling-review.md index 05dcd0ba..3fca8ecb 100644 --- a/microsoft/skills/review/al-data-modeling-review.md +++ b/microsoft/skills/review/al-data-modeling-review.md @@ -39,7 +39,7 @@ Narrow the relevant files to the subset that applies to the changes under review - The changed AL object names and types — especially `* Setup` singleton tables and Card pages, custom master tables, tableextensions that add master-data fields, and document or journal lines that reference a master. - The changed fields, keys, triggers, and procedures, weighted toward `Primary Key`, `No.`, `No. Series`, `Blocked`, `Last Date Modified`, `OnInsert`, `OnModify`, `OnRename`, reference-field `OnValidate`, and posting validation. -- Tokens extracted from the diff that relate to data modeling (`setup`, `master`, `Primary Key`, `Code[10]`, `Code[20]`, `AutoIncrement`, `SystemId`, `No.`, `No. Series`, `NoSeriesManagement`, `Codeunit "No. Series"`, `GetNextNo`, `IsManual`, `TestManual`, `Blocked`, `TestField`, `Last Date Modified`, `Today`, `WorkDate`, `InsertAllowed`, `DeleteAllowed`, `PageType = Card`, `OnOpenPage`, `GetRecordOnce`, `OnInsert`, `OnModify`, `OnRename`, `TableRelation`, `tableextension`, `enumextension`, `Media`, `MediaSet`, `Item`, `Count`). +- Tokens extracted from the diff that relate to data modeling (`setup`, `master`, `Primary Key`, `Code[10]`, `Code[20]`, `AutoIncrement`, `SystemId`, `No.`, `No. Series`, `NoSeriesManagement`, `Codeunit "No. Series"`, `GetNextNo`, `IsManual`, `TestManual`, `Blocked`, `TestField`, `Last Date Modified`, `Today`, `WorkDate`, `InsertAllowed`, `DeleteAllowed`, `PageType = Card`, `OnOpenPage`, `GetRecordOnce`, `OnInsert`, `OnModify`, `OnRename`, `InitRecord`, `Round`, `Precision`, `Direction`, `TableRelation`, `tableextension`, `enumextension`, `Media`, `MediaSet`, `Item`, `Count`). A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. When the diff contains no data-modeling changes by any of the above signals, return `outcome: "not-applicable"` without evaluating files. @@ -52,6 +52,8 @@ The following targeted checks cover every current `data-modeling` article. Treat - A master table adds or changes `Last Date Modified`, `OnModify`, or `OnRename`, but the non-editable field is not assigned `Today()` in both triggers — `set-last-date-modified-in-onmodify-and-onrename`. - A `tableextension` appends a conditional `TableRelation` as if it overrides an earlier unconditional relation, or relation branches are otherwise designed without accounting for additive top-down evaluation — `table-relation-extensions-are-additive-and-top-down`. - A `Media` or `MediaSet` field is assigned directly between different table types or different field IDs instead of registering each shared item with `MediaSet.Insert` — `share-mediaset-items-with-insert-not-field-assignment`. +- A custom document header assigns defaults outside an `InitRecord` boundary, calls `InitRecord` before assigning its number, or places UI-independent defaults only in a page trigger — `initialize-document-defaults-in-initrecord`. +- Directed `Round` calls use `'<'` as mathematical floor or `'>'` as mathematical ceiling, especially where negative amounts are possible — `round-direction-symbols-use-magnitude`. Once the candidate worklist is known, resolve layer-precedence conflicts per READ. Drop lower-precedence files whose normative guidance (`## Best Practice` or `## Anti Pattern`) directly contradicts a higher-precedence candidate, and record each dropped file in `suppressed` with `reason: "layer-precedence"`. Files that would have been candidates but are hidden because their layer is disabled in consumer configuration are recorded with `reason: "configuration"`. Files that never became candidates are NOT recorded in `suppressed`. diff --git a/microsoft/skills/review/al-interfaces-review.md b/microsoft/skills/review/al-interfaces-review.md index f5d65cd0..1c38be76 100644 --- a/microsoft/skills/review/al-interfaces-review.md +++ b/microsoft/skills/review/al-interfaces-review.md @@ -39,7 +39,7 @@ Narrow the relevant files to the subset that applies to the changes under review - The changed AL object names and types — especially `interface` objects, codeunits and enums declared with the `implements` keyword, and consumers that declare or assign an `Interface` variable. - The changed procedures and triggers, weighted toward factory or dispatch routines that resolve a variant to behaviour, setter-injection procedures that take an `Interface` parameter, and `case`-over-enum blocks that select between strategies. -- Tokens extracted from the diff that relate to interfaces and enum-backed implementation (`interface`, `extends`, `implements`, `Implementation`, `DefaultImplementation`, `UnknownValueImplementation`, `enum`, `Extensible`, `Interface`, `case`, and the `case of` anti-pattern signal — a `case` over an enum value whose branches choose between variant computations). +- Tokens extracted from the diff that relate to interfaces and enum-backed implementation (`interface`, `extends`, `implements`, `Implementation`, `DefaultImplementation`, `UnknownValueImplementation`, `enum`, `Extensible`, `Interface`, `Variant`, `is`, `as`, `case`, and the `case of` anti-pattern signal — a `case` over an enum value whose branches choose between variant computations). A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. @@ -54,6 +54,7 @@ The following targeted checks map diff signals to specific `interfaces` articles - `DefaultImplementation` used as the only fallback where a persisted ordinal may no longer match any declared enum value, or a persisted enum lacks `UnknownValueImplementation` on BC18 or later — `handle-unknown-enum-ordinals-with-unknownvalueimplementation`. - A method added directly to an interface that exists in the baseline, instead of adding a BC25+ interface that `extends` it or a versioned sibling for older targets — `extend-published-interfaces-dont-edit-them`. - A declared enum value with no `Implementation` and no enum-level `DefaultImplementation` — `set-defaultimplementation-on-enum`. +- An `Interface` or `Variant` is cast with `as` to an optional extended interface without first establishing support with `is` — `guard-interface-casts-with-is`. For `set-defaultimplementation-on-enum`, inspect the complete containing enum before emitting. An enum-level `DefaultImplementation = = ;` conclusively covers every declared value that omits its own `Implementation`; do not flag such a value and do not replace the intentional fallback with a per-value mapping. diff --git a/microsoft/skills/review/al-ui-review.md b/microsoft/skills/review/al-ui-review.md index 8ffd7319..8c35f498 100644 --- a/microsoft/skills/review/al-ui-review.md +++ b/microsoft/skills/review/al-ui-review.md @@ -41,10 +41,15 @@ Narrow the relevant files to the subset that applies to the changes under review - **UI-file filter.** UI review applies to files declaring `page`, `pageextension`, or `pagecustomization`, and to JavaScript/CSS/HTML that implements a control add-in's rendering or Business Central communication. When the diff contains no such files, return `outcome: "not-applicable"` without evaluating knowledge files. - For each relevant knowledge file, compute overlap against changed page declarations and control add-in files, weighted toward `Caption`, `ToolTip`, `AboutTitle`, `AboutText`, `OptionCaption`, `ShowCaption`, `InstructionalText`, `GridLayout`, `Style`, `StyleExpr`, promoted action definitions, field importance, page background tasks, DOM creation, ARIA attributes, keyboard/focus handlers, packaged-resource AJAX, and calls from JavaScript into AL. -- Tokens extracted from the diff (`Caption`, `ToolTip`, `AboutTitle`, `AboutText`, `PageType`, `ShowCaption`, `InstructionalText`, `grid`, `fixed`, `GridLayout`, `Style`, `StyleExpr`, `Importance`, `Promoted`, `Additional`, `area(Promoted)`, `actionref`, `PromotedCategory`, `PromotedOnly`, `PromotedIsBig`, `ShowAs`, `SplitButton`, `EnqueueBackgroundTask`, `OnAfterGetCurrRecord`, `OnAfterGetRecord`, `OnPageBackgroundTaskCompleted`, `OnPageBackgroundTaskError`, `RunPageBackgroundTask`, `Favorable`, `Unfavorable`, `Ambiguous`, `cuegroup`, `controladdin`, `control-add-in`, `usercontrol`, `aria-`, `tabindex`, `keydown`, `focus`, `innerHTML`, `createElement`, `packaged-resource`, `ajax`, `$.get`, `$.ajax`, `XMLHttpRequest`, `xhrFields`, `withCredentials`, `withcredentials`, `InvokeExtensibilityMethod`, `invokeextensibilitymethod`, `skipIfBusy`, `successCallback`, `success-callback`, `errorCallback`, `setInterval`, `JSON.stringify`, `payload`, `throttling`, `reduced-functionality`, `ClientServicesMaxUploadSize`, `&`, `Specifies`, `Message(`, `Confirm(`, `Error(` in a page context, `Disabled`, `Invalid`, `Whitelist`, `Blacklist`, trailing punctuation patterns on captions). +- Tokens extracted from the diff (`Caption`, `ToolTip`, `AboutTitle`, `AboutText`, `PageType`, `ShowCaption`, `InstructionalText`, `grid`, `fixed`, `GridLayout`, `Style`, `StyleExpr`, `Importance`, `Promoted`, `Additional`, `area(Promoted)`, `actionref`, `PromotedCategory`, `PromotedOnly`, `PromotedIsBig`, `ShowAs`, `SplitButton`, `fieldgroups`, `DropDown`, `UpdatePropagation`, `EnqueueBackgroundTask`, `OnAfterGetCurrRecord`, `OnAfterGetRecord`, `OnPageBackgroundTaskCompleted`, `OnPageBackgroundTaskError`, `RunPageBackgroundTask`, `Favorable`, `Unfavorable`, `Ambiguous`, `cuegroup`, `controladdin`, `control-add-in`, `usercontrol`, `aria-`, `tabindex`, `keydown`, `focus`, `innerHTML`, `createElement`, `packaged-resource`, `ajax`, `$.get`, `$.ajax`, `XMLHttpRequest`, `xhrFields`, `withCredentials`, `withcredentials`, `InvokeExtensibilityMethod`, `invokeextensibilitymethod`, `skipIfBusy`, `successCallback`, `success-callback`, `errorCallback`, `setInterval`, `JSON.stringify`, `payload`, `throttling`, `reduced-functionality`, `ClientServicesMaxUploadSize`, `&`, `Specifies`, `Message(`, `Confirm(`, `Error(` in a page context, `Disabled`, `Invalid`, `Whitelist`, `Blacklist`, trailing punctuation patterns on captions). A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed page element. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. +Apply these high-signal mappings before fuzzy topic ranking: + +- A tableextension adds a field to `DropDown` while the corresponding lookup-page control remains `Visible = false` — `dropdown-fieldgroup-respects-lookup-page-visibility`. +- An editable page part affects a total, FlowField, or FactBox on the parent but does not set `UpdatePropagation = Both` — `updatepropagation-both-refreshes-main-page`. + Once the candidate worklist is known, resolve layer-precedence conflicts per READ and record suppressions. When the post-conflict worklist is empty because no applicable UI knowledge exists, or because configuration suppressed every candidate, emit `outcome: "no-knowledge"`. When the worklist is empty because no applicable UI knowledge matched the page changes, emit `outcome: "completed"` with an empty `findings` array. diff --git a/microsoft/skills/review/al-upgrade-review.md b/microsoft/skills/review/al-upgrade-review.md index 94851a36..9ae61f8f 100644 --- a/microsoft/skills/review/al-upgrade-review.md +++ b/microsoft/skills/review/al-upgrade-review.md @@ -39,10 +39,12 @@ Narrow the relevant files to the subset that applies to the changes under review - The changed AL object names and types — especially codeunits with `Subtype = Upgrade` or `Subtype = Install`, tables and tableextensions adding or changing fields, enums and enumextensions, and objects under `Hybrid*`/`Migration`/`Upgrade` namespaces. - The changed triggers and procedures, weighted toward `OnCheckPreconditionsPerCompany`/`PerDatabase`, `OnUpgradePerCompany`/`PerDatabase`, `OnValidateUpgradePerCompany`/`PerDatabase`, `OnInstallAppPerCompany`/`PerDatabase`, the `OnGetPerCompanyUpgradeTags`/`OnGetPerDatabaseUpgradeTags` subscribers, and helper procedures transitively reachable from those entry points. -- Tokens extracted from the diff that relate to upgrade concerns (`Subtype = Upgrade`, `Subtype = Install`, `Upgrade Tag`, `HasUpgradeTag`, `SetUpgradeTag`, `OnCheckPreconditions`, `OnUpgrade`, `OnValidateUpgrade`, `OnInstallApp`, `DataTransfer`, `CopyFields`, `Insert`, `Modify`, `Delete`, `Rename`, `InitValue`, `ObsoleteState`, `ObsoleteReason`, `ObsoleteTag`, `DataVersion`, `ExecutionContext`, `PrimaryKey`, `key(`, `field(`, `value(`, `enum`, `enumextension`, `HybridSL`, `HybridGP`, `HybridBC`, `HybridBaseDeployment`). +- Tokens extracted from the diff that relate to upgrade concerns (`Subtype = Upgrade`, `Subtype = Install`, `Upgrade Tag`, `HasUpgradeTag`, `SetUpgradeTag`, `OnCheckPreconditions`, `OnUpgrade`, `OnValidateUpgrade`, `OnInstallApp`, `DataTransfer`, `CopyFields`, `Insert`, `Modify`, `Delete`, `Rename`, `InitValue`, `ObsoleteState`, `ObsoleteReason`, `ObsoleteTag`, `ModuleInfo`, `AppVersion`, `DataVersion`, `NavApp.GetCurrentModuleInfo`, `ExecutionContext`, `PrimaryKey`, `key(`, `field(`, `value(`, `enum`, `enumextension`, `HybridSL`, `HybridGP`, `HybridBC`, `HybridBaseDeployment`). - For each `OnCheckPreconditions...` and `OnValidateUpgrade...` trigger, build the best available call graph from surrounding unchanged source as well as changed hunks, tracing resolved calls through reachable local or internal helpers. Worklist the check-only rule when a database write occurs either directly in the trigger or in any helper procedure reachable from it. Writes include `Insert`, `Modify`, `ModifyAll`, `Delete`, `DeleteAll`, `Rename`, and `DataTransfer`. Also perform the reverse check when a PR changes a writing helper body: worklist the rule when that helper is invoked directly or transitively by an unchanged check or validation trigger. - Treat a direct write or a fully resolved call chain as high-confidence evidence. When cross-object dispatch, unavailable declarations, or an incomplete call graph prevents proving the complete chain, cap confidence at `medium`, name the unresolved edge in the finding, and do not claim a violation without a resolved path from a check or validation trigger to a write. - Worklist the install-versus-upgrade rule when migration helpers are reachable only from an install codeunit. +- Worklist `install-and-upgrade-codeunits-have-no-order.md` when a change adds multiple install or upgrade codeunits whose same-phase triggers share state or depend on one another. +- Worklist `appversion-meaning-depends-on-execution-context.md` when install or upgrade code branches on `ModuleInfo.AppVersion()` or confuses it with `DataVersion()`. A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. When the diff contains no upgrade-related changes by any of the above signals, return `outcome: "not-applicable"` without evaluating files.