Skip to content

MSlenejennum/647452/new header and footer layouts and report themes - #10489

Open
MSlenejennum wants to merge 9 commits into
mainfrom
MSlenejennum/647452/New-Header-and-Footer-Layouts-and-Report-Themes
Open

MSlenejennum/647452/new header and footer layouts and report themes#10489
MSlenejennum wants to merge 9 commits into
mainfrom
MSlenejennum/647452/New-Header-and-Footer-Layouts-and-Report-Themes

Conversation

@MSlenejennum

@MSlenejennum MSlenejennum commented Aug 21, 2026

Copy link
Copy Markdown

What & why

Composite Layouts compose a report from a theme + header/footer + body, but nothing shipped to
compose from — every tenant had to author its own parts first. This ships the parts and seeds them.

  • 11 header/footer parts (.resources/ReportParts/HeaderFooterDesign/) — 6 external, 5 internal.
  • 3 themes (.resources/ReportParts/ReportTheme/) — Default, Calm, Playful (styling-only .dotx).
  • Composite Report Parts Mgt. (9667) — upserts each part into Tenant Report Layout under
    the Tenant Report Defaults report ID.
  • Install + upgrade call the same entry point: BaseApp Install.OnInstallAppPerDatabase and
    Upgrade Composite Report Parts (104064), guarded by a new per-database upgrade tag.

Three things that look odd but are deliberate (commented in source):

  • Parts are deleted and re-inserted, not modified — the platform won't let an existing layout's
    type or content change. Same delete also cleans up the empty-App-ID copy an earlier version seeded.
  • Resource read is in a [TryFunction], the write is outside it — the platform rejects DB writes
    inside a try function while install/upgrade holds a write transaction.
  • Seeding is best-effort per part; the upgrade tag is recorded only if all parts seeded, so a
    partial pass is retried next upgrade instead of being gated out forever.

Linked work

AB#647452

How I validated this

  • I read the full diff and it contains only changes I intended.
  • I built the affected app(s) locally with no new analyzer warnings.
  • I ran the change in Business Central and confirmed it behaves as expected.
  • I added or updated tests for the new behavior.

What I tested and the outcome

  • Published to a local NST: all 14 parts appear in the shared pool and a Composite Layout
    assignment picks them up and renders.
  • 7 new tests in CompositeLayoutTests.Codeunit.al, all passing — covering seeding (subtype and
    .dotx MIME type), storage under the System App ID, removal of the empty-App-ID copy,
    idempotent re-seed, tag registration and gating, and the failure path (missing resource is
    reported not thrown, tag left unset, next run retries and then records it).

Risk & compatibility

  • Upgrade: new per-database tag MS-643211-CompositeReportPartsUpgradeTag-20260820 (registered
    in W1/BE/IT/RU); writes 14 rows to Tenant Report Layout. Delete-and-reinsert means a tenant
    part hand-authored under a reserved name with the System App ID would be replaced.
  • Install: BaseApp Install gains OnInstallAppPerDatabase and InherentEntitlements/Permissions = X;
    it stamps the tag so a fresh install doesn't re-seed on first upgrade.
  • Size: Playful.dotx is ~5.5 MB (embedded assets); the rest are 50–75 KB.
  • Telemetry: 0000V42 (all-scope) and 0000V43 (publisher-scoped — carries platform error text).
  • Otherwise purely additive; no existing object signature or layout changed.

MSlenejennum and others added 2 commits August 21, 2026 09:53
…upgrade

Adds the reusable Composite Layout parts that ship with the Base Application -
11 header/footer designs and 3 report themes - and the code that writes them
into the shared pool under Tenant Report Defaults, so an administrator can
assign them to any report from the report themes and header/footer setup page.

- .resources/ReportParts: the shipped layout files
- Composite Report Parts Mgt. (9667): seeds the pool. Safe to call repeatedly,
  and each part is written in isolation so one that cannot be written is
  reported to telemetry and skipped rather than failing install or upgrade
- BaseApp Install: seeds per database on install
- Upgrade Composite Report Parts: seeds per database on upgrade, guarded by an
  upgrade tag that is only recorded when every part was written, so a partial
  seed is retried by the next upgrade
- Upgrade Tag Definitions in W1, BE, IT and RU: the tag and its per-database
  registration

Assigning the parts to report layouts is not part of this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MSlenejennum MSlenejennum changed the title M slenejennum/647452/new header and footer layouts and report themes MSlenejennum/647452/new header and footer layouts and report themes Aug 21, 2026
@github-actions github-actions Bot added the Integration GitHub request for Integration area label Aug 21, 2026
…ejennum/647452/New-Header-and-Footer-Layouts-and-Report-Themes
@github-actions github-actions Bot added this to the Version 29.0 milestone Aug 21, 2026
…ejennum/647452/New-Header-and-Footer-Layouts-and-Report-Themes

@MSlenejennum MSlenejennum left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look through

@MSlenejennum
MSlenejennum marked this pull request as ready for review August 21, 2026 10:53
@MSlenejennum
MSlenejennum requested review from a team August 21, 2026 10:53
@MSlenejennum
MSlenejennum requested a review from a team as a code owner August 21, 2026 10:53
…ejennum/647452/New-Header-and-Footer-Layouts-and-Report-Themes
Comment on lines +14 to +18
codeunit 104064 "Upgrade Composite Report Parts"
{
Subtype = Upgrade;
InherentEntitlements = X;
InherentPermissions = X;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Breaking\ Changes}$

Codeunit 104064 "Upgrade Composite Report Parts" is a new upgrade implementation detail but is left at the default public object access. That publishes the object identity to dependent extensions unnecessarily; set Access = Internal unless other apps are expected to bind to this codeunit.

Suggested change
codeunit 104064 "Upgrade Composite Report Parts"
{
Subtype = Upgrade;
InherentEntitlements = X;
InherentPermissions = X;
codeunit 104064 "Upgrade Composite Report Parts"
{
Access = Internal;
Subtype = Upgrade;
InherentEntitlements = X;
InherentPermissions = X;

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

CopyStream(PartLayoutOutStream, ResourceInStream);
end;

local procedure UpsertPart(PartName: Text[250]; var PartLayout: Codeunit "Temp Blob"; Subtype: Enum "Report Layout Subtype"; Description: Text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Error\ Handling}$

SeedPart only converts TryGetPartLayout read failures into false. The write path still calls RemovePart/Delete(true) and TenantReportLayout.Insert(true) outside any handled try path, so a write-time failure will raise an uncaught error and abort install or upgrade instead of logging the part, returning false, and leaving the upgrade tag unset for a later retry. Wrap the write phase in handled error-catching too and make SeedPart log and return false for write failures, not just missing-resource failures.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

@github-actions

Copy link
Copy Markdown
Contributor

$\textbf{🟡\ Medium\ Severity\ —\ Telemetry}$

The publisher-only detail event logs raw ErrorText after explicitly noting that platform error text can echo customer content, but the event is still classified as DataClassification::SystemMetadata. Reclassify that payload to a customer-data classification that matches the possible content, or stop emitting the raw error text and keep only sanitized metadata.

Line mapping was unavailable, so this was posted as an issue comment.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

ReportLayoutList.SetRange(Name, CopyStr(PartName, 1, MaxStrLen(ReportLayoutList.Name)));
exit(ReportLayoutList.Count());
end;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Testing}$

The new upgrade-tag scenarios mutate a per-database upgrade tag, but this non-isolated suite only resets layout rows and the shipped-part pool in Initialize(). That leaves CompositeReportPartsUpgradeTag in whatever state the previous test produced, so later tests or suites sharing the same database can become order-dependent. Restore or clear the tag as part of suite setup/cleanup, the same way the shared part pool is restored.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

exit('MS-629001-ProdDefinitionDisplaySetupUpgradeTag-20260723');
end;
} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Upgrade}$

The new upgrade-tag getter GetCompositeReportPartsUpgradeTag is duplicated across the BE, IT, RU, and W1 layer copies of "Upgrade Tag Definitions" (codeunit 9998), each hard-coding its own literal tag string. BE, IT, and RU all return the identical literal MS-643211-CompositeReportPartsUpgradeTag-20260820, but the W1 layer returns a different literal, MS-647452-CompositeReportPartsUpgradeTag-20260820. The referenced guidance requires keeping a tag definition shared so every path uses the exact same value; a divergent literal in one of several otherwise-identical per-layer copies is a strong signal of a copy-paste/PR-number mismatch rather than an intentional difference, and makes the tag's provenance (which work item it corresponds to) inconsistent across layers that are meant to mirror each other.

Suggested fix (apply manually — could not be anchored as a one-click suggestion):

    internal procedure GetCompositeReportPartsUpgradeTag(): Code[250]
    begin
        exit('MS-643211-CompositeReportPartsUpgradeTag-20260820');
    end;

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

{
Access = Internal;

procedure SeedDefaultParts() AllPartsSeeded: Boolean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Breaking\ Changes}$

SeedDefaultParts is declared with no access modifier (implicitly public) in the new codeunit "Composite Report Parts Mgt.". Although the codeunit itself is Access = Internal (so it is not currently reachable from other extensions), the procedure's own modifier should be set deliberately (internal) rather than left at the implicit public default, per the cited guidance on choosing access modifiers intentionally rather than accepting AL's public-by-default behavior.

Suggested fix (apply manually — could not be anchored as a one-click suggestion):

    internal procedure SeedDefaultParts() AllPartsSeeded: Boolean

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

TenantReportLayout.Description := CopyStr(Description, 1, MaxStrLen(TenantReportLayout.Description));
TenantReportLayout."Layout Status" := TenantReportLayout."Layout Status"::Approved;
TenantReportLayout."MIME Type" := PartMimeType(Subtype);
PartLayout.CreateInStream(LayoutInStream);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Error\ Handling}$

SeedPart reads the shipped .docx/.dotx resource inside a TryFunction, but the subsequent TenantReportLayout.Layout.ImportStream(...)/Insert(true) write path in UpsertPart runs outside any try wrapper. A single malformed or unsupported shipped layout resource can therefore raise an unhandled error during OnInstallAppPerDatabase or the upgrade codeunit, aborting the whole install/upgrade operation instead of being logged, skipped, and retried on a later run the way the rest of this best-effort seeding flow is designed to behave. Consider isolating the write/import step so one failing part cannot abort seeding of the remaining parts.

Agent judgement — not directly backed by a BCQuality knowledge article.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

PerDatabaseUpgradeTags.Add(GetBCUserGroupUpgradeTag());
PerDatabaseUpgradeTags.Add(GetRenderWordReportsInPlatformFeatureKeyUpgradeTag());
PerDatabaseUpgradeTags.Add(GetRegisterBankAccRecCopilotCapabilityUpgradeTag());
PerDatabaseUpgradeTags.Add(GetCompositeReportPartsUpgradeTag());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Upgrade}$

Registering GetCompositeReportPartsUpgradeTag() in the OnGetPerDatabaseUpgradeTags subscriber (added identically in the W1, BE, IT, and RU Upgrade Tag Definitions codeunits) breaks the retry contract this feature depends on. SetAllUpgradeTags (invoked by Company Initialize whenever any new company is created) calls OnGetPerDatabaseUpgradeTags and marks every returned tag complete if missing, regardless of whether SeedDefaultParts actually succeeded. If BaseApp Install's SeedDefaultReportParts() or the Upgrade Composite Report Parts upgrade codeunit fails to seed all parts (SeedDefaultParts returns false, so the tag is never explicitly set), the very next company creation on that database will mark the tag complete anyway via EnsurePerDatabaseUpgradeTagsExist. From that point on, RunUpgrade() short-circuits on HasDatabaseUpgradeTag(...) and the missing shipped report parts can never be retried by the upgrade codeunit. Keep the explicit SetDatabaseUpgradeTag() call after a successful seed, but do not also register this tag for OnGetPerDatabaseUpgradeTags while failed seeding is meant to remain retryable.

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Integration GitHub request for Integration area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant