Fix three merge-critical items from Jesper's 2026-09-29 review

- Barcode: drop the false claim that '*value*' is mismatched with the
  IDAutomation Code 39 font; '*' is a documented start/stop form and
  '(' / ')' an accepted alternative. Cue and article now route only
  independently provable validation/checksum/font-binding defects.
- Dispatch good samples (and matching bad samples) now pass a
  Sales Invoice Header with the S.Invoice usage, matching the record
  the selected report (1306 "Standard Sales - Invoice") expects.
- custom-document-dispatch rule made disjunctive: a hardcoded report
  or a hand-built email is each a bypass on its own; scoped to
  customer/vendor-facing documents. Bad fixture shows the hardcoded
  report alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael Dieringer 2026-09-29 16:45:02 +02:00
parent 4cd41f08f7
commit aad3991d9f
7 changed files with 98 additions and 85 deletions

View file

@ -1,28 +1,20 @@
report 50102 "Sample Settlement Doc Bad" codeunit 50102 "Sample Posted Invoice Send"
{ {
UsageCategory = ReportsAndAnalysis; procedure SendPostedInvoice(SalesInvoiceHeader: Record "Sales Invoice Header")
ApplicationArea = All; var
Customer: Record Customer;
dataset
{
dataitem(Customer; Customer)
{
column(No_Customer; "No.") { }
}
}
}
codeunit 50102 "Sample Settlement Document Send"
{
procedure SendSettlementDocument(var Customer: Record Customer)
begin begin
Customer.Get(SalesInvoiceHeader."Bill-to Customer No.");
Customer.TestField("E-Mail"); Customer.TestField("E-Mail");
// WRONG: hardcoded report, no Report Selections row backing it. // WRONG: the report is hardcoded instead of resolved through the
// Works for the default case, but there is nowhere for an admin to // registered "S.Invoice" usage in Report Selections. This alone is
// change the report or layout for one specific customer - this // the defect - no hand-built email is needed for it: a Report
// document never shows up on "Document Layouts" at all, and the // Selections row or a per-customer "Document Layouts" override
// only way to change it is a code change and a new release. // that points this usage at a different report or layout is
Report.RunModal(Report::"Sample Settlement Doc Bad", false, false, Customer); // silently ignored, and the only way to change what this code
// prints is a code change and a new release.
SalesInvoiceHeader.SetRecFilter();
Report.RunModal(Report::"Standard Sales - Invoice", false, false, SalesInvoiceHeader);
end; end;
} }

View file

@ -1,22 +1,31 @@
codeunit 50102 "Sample Settlement Document Send" codeunit 50102 "Sample Posted Invoice Send"
{ {
procedure SendSettlementDocument(var Customer: Record Customer) procedure SendPostedInvoice(SalesInvoiceHeader: Record "Sales Invoice Header")
var var
ReportSelections: Record "Report Selections"; ReportSelections: Record "Report Selections";
ReportDistributionMgt: Codeunit "Report Distribution Management";
begin begin
// Custom validation specific to this document stays here... // Custom validation specific to this dispatch stays here...
CheckReadyToSend(Customer); CheckReadyToSend(SalesInvoiceHeader);
// ...but dispatch goes through the registered usage, so per-account // ...but dispatch goes through the registered usage. "S.Invoice"
// resolves to a report built on "Sales Invoice Header" (by default
// report 1306 "Standard Sales - Invoice"), so the record passed in
// matches what the selected report expects, and per-account
// report/layout overrides and email attachment/body configuration // report/layout overrides and email attachment/body configuration
// on Report Selections all apply automatically. // on Report Selections all apply automatically.
SalesInvoiceHeader.SetRecFilter();
ReportSelections.SendEmailToCust( ReportSelections.SendEmailToCust(
"Report Selection Usage"::"S.Invoice".AsInteger(), Customer, Customer."No.", "Report Selection Usage"::"S.Invoice".AsInteger(), SalesInvoiceHeader, SalesInvoiceHeader."No.",
Customer.Name, true, Customer."No."); ReportDistributionMgt.GetFullDocumentTypeText(SalesInvoiceHeader), true,
SalesInvoiceHeader."Bill-to Customer No.");
end; end;
local procedure CheckReadyToSend(var Customer: Record Customer) local procedure CheckReadyToSend(SalesInvoiceHeader: Record "Sales Invoice Header")
var
Customer: Record Customer;
begin begin
Customer.Get(SalesInvoiceHeader."Bill-to Customer No.");
Customer.TestField("E-Mail"); Customer.TestField("E-Mail");
end; end;
} }

View file

@ -11,11 +11,15 @@ application-area: [all]
## Description ## Description
A codeunit that hardcodes which report to run (`Report.RunModal(MyReportId, ...)`) A codeunit that hardcodes which report to run (`Report.RunModal(MyReportId, ...)`),
and builds its own email directly, instead of registering the document or builds its own email directly, instead of registering the document
through `table 77 "Report Selections"` and calling its own through `table 77 "Report Selections"` and calling its own
Print/Email procedures, works for the one case it was written for — and Print/Email procedures, works for the one case it was written for — and
loses everything the platform's registry provides for free. `Report loses everything the platform's registry provides for free. Either
bypass is a defect on its own: a hardcoded report ignores the registered
report and any per-account layout override even when no email is
involved, and a hand-built email ignores the registry's attachment and
email-body configuration even when the report itself came from it. `Report
Selections` carries its own attachment/email-body configuration per usage Selections` carries its own attachment/email-body configuration per usage
(`"Use for Email Attachment"`, `"Use for Email Body"`, `"Email Body Layout (`"Use for Email Attachment"`, `"Use for Email Body"`, `"Email Body Layout
Code"`, `"Email Body Layout Type"`), plus a separate per-usage layout Code"`, `"Email Body Layout Type"`), plus a separate per-usage layout
@ -44,9 +48,10 @@ See sample: [`custom-document-dispatch-must-not-bypass-report-selections.good.al
## Anti Pattern ## Anti Pattern
A codeunit that runs a hardcoded report ID and builds its own email A codeunit that runs a hardcoded report ID, or builds its own email
message directly, with no `Report Selections` row backing it. It works for message directly, for a document that has (or should have) a
the default case, but the report/layout cannot be changed per account `Report Selections` usage — each is independently a bypass, and the
sample shows the first on its own. It works for the default case, but the report/layout cannot be changed per account
without a code change and a new release, and the document is invisible to without a code change and a new release, and the document is invisible to
"Document Layouts" — the standard place every other document's "Document Layouts" — the standard place every other document's
distribution is configured. distribution is configured.

View file

@ -1,8 +1,9 @@
page 50101 "Sample Settlement Document Card" page 50101 "Sample Posted Invoice Card"
{ {
PageType = Card; PageType = Card;
SourceTable = Customer; SourceTable = "Sales Invoice Header";
ApplicationArea = All; ApplicationArea = All;
Editable = false;
actions actions
{ {
@ -16,7 +17,9 @@ page 50101 "Sample Settlement Document Card"
trigger OnAction() trigger OnAction()
var var
SalesInvoiceHeader: Record "Sales Invoice Header";
DocumentSendingProfile: Record "Document Sending Profile"; DocumentSendingProfile: Record "Document Sending Profile";
ReportDistributionMgt: Codeunit "Report Distribution Management";
begin begin
// WRONG: this is a plain, on-demand "Email" button, not // WRONG: this is a plain, on-demand "Email" button, not
// part of a combined Post-and-Send action - but this // part of a combined Post-and-Send action - but this
@ -28,10 +31,13 @@ page 50101 "Sample Settlement Document Card"
// Printer = Yes, "E-Mail" = No) turns this button into a // Printer = Yes, "E-Mail" = No) turns this button into a
// silent no-op, with no indication an unrelated setup // silent no-op, with no indication an unrelated setup
// field is why. // field is why.
DocumentSendingProfile.GetDefaultForCustomer(Rec."No.", DocumentSendingProfile); SalesInvoiceHeader := Rec;
CurrPage.SetSelectionFilter(SalesInvoiceHeader);
DocumentSendingProfile.GetDefaultForCustomer(Rec."Bill-to Customer No.", DocumentSendingProfile);
DocumentSendingProfile.Send( DocumentSendingProfile.Send(
"Report Selection Usage"::"S.Invoice".AsInteger(), Rec, Rec."No.", Rec."No.", "Report Selection Usage"::"S.Invoice".AsInteger(), SalesInvoiceHeader, Rec."No.",
Rec.Name, Rec.FieldNo("No."), Rec.FieldNo("No.")); Rec."Bill-to Customer No.", ReportDistributionMgt.GetFullDocumentTypeText(Rec),
SalesInvoiceHeader.FieldNo("Bill-to Customer No."), SalesInvoiceHeader.FieldNo("No."));
end; end;
} }
} }

View file

@ -1,8 +1,9 @@
page 50101 "Sample Settlement Document Card" page 50101 "Sample Posted Invoice Card"
{ {
PageType = Card; PageType = Card;
SourceTable = Customer; SourceTable = "Sales Invoice Header";
ApplicationArea = All; ApplicationArea = All;
Editable = false;
actions actions
{ {
@ -16,7 +17,9 @@ page 50101 "Sample Settlement Document Card"
trigger OnAction() trigger OnAction()
var var
SalesInvoiceHeader: Record "Sales Invoice Header";
ReportSelections: Record "Report Selections"; ReportSelections: Record "Report Selections";
ReportDistributionMgt: Codeunit "Report Distribution Management";
begin begin
// Calls Report Selections directly - the button's outcome // Calls Report Selections directly - the button's outcome
// depends only on this customer's registered report/layout, // depends only on this customer's registered report/layout,
@ -24,9 +27,13 @@ page 50101 "Sample Settlement Document Card"
// DocumentSendingProfile.TrySendToEMail(...) instead would // DocumentSendingProfile.TrySendToEMail(...) instead would
// be equally correct: it never Get's the customer's // be equally correct: it never Get's the customer's
// actually assigned profile, only a local, hardcoded one. // actually assigned profile, only a local, hardcoded one.
// "S.Invoice" resolves to a report on "Sales Invoice
// Header", which is the record passed here.
SalesInvoiceHeader := Rec;
CurrPage.SetSelectionFilter(SalesInvoiceHeader);
ReportSelections.SendEmailToCust( ReportSelections.SendEmailToCust(
"Report Selection Usage"::"S.Invoice".AsInteger(), Rec, Rec."No.", "Report Selection Usage"::"S.Invoice".AsInteger(), SalesInvoiceHeader, Rec."No.",
Rec.Name, true, Rec."No."); ReportDistributionMgt.GetFullDocumentTypeText(Rec), true, Rec."Bill-to Customer No.");
end; end;
} }
action(PrintDocument) action(PrintDocument)
@ -37,10 +44,14 @@ page 50101 "Sample Settlement Document Card"
trigger OnAction() trigger OnAction()
var var
SalesInvoiceHeader: Record "Sales Invoice Header";
ReportSelections: Record "Report Selections"; ReportSelections: Record "Report Selections";
begin begin
SalesInvoiceHeader := Rec;
CurrPage.SetSelectionFilter(SalesInvoiceHeader);
ReportSelections.PrintWithDialogForCust( ReportSelections.PrintWithDialogForCust(
"Report Selection Usage"::"S.Invoice", Rec, true, Rec.FieldNo("No.")); "Report Selection Usage"::"S.Invoice", SalesInvoiceHeader, true,
SalesInvoiceHeader.FieldNo("Bill-to Customer No."));
end; end;
} }
} }

View file

@ -56,34 +56,26 @@ See sample: [`report-barcodes-must-use-barcode-module-and-production-font-name.g
## Anti Pattern ## Anti Pattern
Constructing a barcode string by hand instead of using the module's Constructing a barcode string by hand where that construction has a
provider/encoder API — not because a manual delimiter is inherently concrete, independently provable defect: a source value that can contain
wrong (Code 39's own symbology does use `*` as start/stop; Microsoft characters outside the symbology's character set is never validated, a
Learn's font table says so), but because hand-rolled construction is checksum the symbology or setup requires is never applied, or there is
demonstrably mismatched with what the real encoder produces: it skips concrete evidence of an incompatible font binding.
`ValidateInput` (so a value outside the character set, or needing a
checksum/extended-charset setting never applied, reaches the font
unvalidated), and IDAutomation 1D Provider's own Code 39 output is
wrapped in `(`/`)`, not literal `*` (BCApps test:
`EncodeFont('1234', Code39) = '(1234)'`) — the paired font maps those
parentheses to the real start/stop glyph, so `'*' + value + '*'` is
simply the wrong characters, plus no checksum.
Flag demonstrably invalid or mismatched hand construction, not manual The delimiter itself is not the defect. `*value*` is a documented, valid
delimiter use as a category — a custom provider paired with a font that Code 39 form for IDAutomation fonts (Microsoft Learn's font table and
genuinely expects literal `*` delimiters is a different, legitimate case. IDAutomation's own manual both give `*` as start/stop); the `(`/`)` that
IDAutomation 1D Provider's encoder emits (BCApps test:
`EncodeFont('1234', Code39) = '(1234)'`) is an alternative start/stop
form the same fonts accept, used to keep `*` out of the human-readable
text. Never flag delimiter choice alone.
A second version of the same mistake: encoding correctly, but naming the The same validation gap exists when the module *is* used: a 1D path that
evaluation font instead of the purchased one. Both look complete in calls `EncodeFont` on `"Barcode Font Provider"` without `ValidateInput`
review and fail silently — the first because the data was never a real (IDAutomation 1D Provider's `EncodeFont` does not validate on its own).
barcode, the second because BC online refuses to render it. The sample shows this variant, visible in AL alone. A last version:
encoding correctly but naming the evaluation font, which BC online
The same gap exists even when the module *is* used: a 1D path that calls refuses to render.
`EncodeFont` on `"Barcode Font Provider"` without `ValidateInput`.
IDAutomation 1D Provider's `EncodeFont` does not validate on its own, so
a value outside the symbology's character set is never rejected — it
reaches the font as an unscannable barcode. The sample shows this
variant, because it is visible in AL alone without layout evidence.
See sample: [`report-barcodes-must-use-barcode-module-and-production-font-name.bad.al`](report-barcodes-must-use-barcode-module-and-production-font-name.bad.al). See sample: [`report-barcodes-must-use-barcode-module-and-production-font-name.bad.al`](report-barcodes-must-use-barcode-module-and-production-font-name.bad.al).
@ -93,17 +85,15 @@ BCApps (`src/System Application/App/Barcode/src/`):
`Barcode Provider/Font/BarcodeFontProvider.Interface.al` (1D: `Barcode Provider/Font/BarcodeFontProvider.Interface.al` (1D:
`ValidateInput` + `EncodeFont`); `IDAutomation 1D Provider/ `ValidateInput` + `EncodeFont`); `IDAutomation 1D Provider/
IDAutomation1DProvider.Codeunit.al` (`EncodeFont` goes straight to the IDAutomation1DProvider.Codeunit.al` (`EncodeFont` goes straight to the
symbology encoder; only `ValidateInput` calls `IsValidInput`); `Barcode Provider 2D/Font/ symbology encoder; only `ValidateInput` calls `IsValidInput`); `Barcode Provider 2D/Font/BarcodeFontProvider2D.Interface.al`
BarcodeFontProvider2D.Interface.al` (2D: only `EncodeFont`); both read (2D: only `EncodeFont`). `IDAutomation 1D Provider/Encoders/IDA1DCode39Encoder.Codeunit.al`
fresh from source. `IDAutomation 1D Provider/Encoders/ (`codeunit 9204`, regex accepts literal `*`; `EncodeFont` → `DotNet FontEncoder.Code39`).
IDA1DCode39Encoder.Codeunit.al` (`codeunit 9204`, regex accepts literal 1D/2D split: `.../Inventory/Item/ItemGTINLabel.Report.al` (`report 6625`,
`*` as plain input; `EncodeFont` → `DotNet FontEncoder.Code39`). Split validates+encodes 1D, only encodes 2D). Encoder output form: `IDA1DCode39Test.Codeunit.al`
and delimiter mismatch both confirmed live: `.../Inventory/Item/ (`codeunit 135044`): `EncodeFontSuccessTest('1234', Code39, '(1234)')`.
ItemGTINLabel.Report.al` (`report 6625`, validates+encodes 1D, only
encodes 2D, same value) and `.../Test/Barcode/.../IDA1DCode39Test.
Codeunit.al` (`codeunit 135044`): `EncodeFontSuccessTest('1234', Code39,
'(1234)')` — wrapped in `(`/`)`, never literal `*`.
Microsoft Learn "Adding Barcodes to Reports" and "Barcode Fonts with Microsoft Learn "Adding Barcodes to Reports" and "Barcode Fonts with
Business Central Online" — quoted above, incl. the Code39 row ("`*` is Business Central Online" — quoted above, incl. the Code39 row ("`*` is
used for both start and stop delimiters"). used for both start and stop delimiters"). IDAutomation, "Code 39 Font
User Manual" (https://idautomation.com/barcode-fonts/code-39/fontnames/):
`*` start/stop, or parentheses to keep `*` out of the human-readable text.

View file

@ -54,7 +54,7 @@ The following targeted checks cover every current `data-modeling` article. Treat
- 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 `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`. - 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`. - Directed `Round` calls use `'<'` as mathematical floor or `'>'` as mathematical ceiling, especially where negative amounts are possible — `round-direction-symbols-use-magnitude`.
- A codeunit dispatches a document by calling `Report.Run`/`Report.RunModal` with a hardcoded report ID and building its own email directly, with no accompanying `Report Selections` registration for that document — `custom-document-dispatch-must-not-bypass-report-selections`. A call that already goes through `Report Selections`' own Print/Email procedures is not this anti-pattern. - A codeunit dispatches a document by calling `Report.Run`/`Report.RunModal` with a hardcoded report ID, or by building its own email directly, instead of going through the `Report Selections` usage for that document — `custom-document-dispatch-must-not-bypass-report-selections`. Either bypass is a finding on its own; both need not be present. Scope this to customer/vendor-facing documents that have (or should have) a `Report Selection Usage` — a hardcoded `Report.Run` of an ordinary list/analysis report is not this anti-pattern. A call that already goes through `Report Selections`' own Print/Email procedures is not this anti-pattern.
- A document's own interactive Print/Email action routes through `Document Sending Profile` (`DocumentSendingProfile.Send`/`SendVendor`) instead of calling `Report Selections` (`PrintForCust`/`PrintWithDialogForCust`/`SendEmailToCust`/`PrintWithDialogForVend`/`SendEmailToVendor`) directly — `document-print-and-email-actions-call-report-selections-directly`. Do not flag `Document Sending Profile` usage that is genuinely part of a combined Post-and-Send action. - A document's own interactive Print/Email action routes through `Document Sending Profile` (`DocumentSendingProfile.Send`/`SendVendor`) instead of calling `Report Selections` (`PrintForCust`/`PrintWithDialogForCust`/`SendEmailToCust`/`PrintWithDialogForVend`/`SendEmailToVendor`) directly — `document-print-and-email-actions-call-report-selections-directly`. Do not flag `Document Sending Profile` usage that is genuinely part of a combined Post-and-Send action.
- An `EventSubscriber` is added for `Navigate::OnAfterFindRecords` (registering a custom table in Find Entries) without a matching `Navigate::OnBeforeShowRecords` subscriber for the same table, or vice versa — `extend-find-entries-navigate-for-new-document-types`. Both subscribers must be added together for the same table. - An `EventSubscriber` is added for `Navigate::OnAfterFindRecords` (registering a custom table in Find Entries) without a matching `Navigate::OnBeforeShowRecords` subscriber for the same table, or vice versa — `extend-find-entries-navigate-for-new-document-types`. Both subscribers must be added together for the same table.
- An `enumextension` extends `"Report Selection Usage"` and registers a report via `ReportSelections.InsertRecord`, without subscribing to the matching *single* counterparty's full triad — the filter event (`OnAfterFilterCustomerUsageReportSelections` on `page 9657` for a sales usage, `OnAfterFilterVendorUsageReportSelections` on `page 9658` for a purchase usage) AND the page-facing usage-enum map/validate events (`enumextension` on `"Custom Report Selection Sales"`/`"Report Selection Usage Vendor"` plus the matching map/validate subscribers) — `extend-report-selection-usage-for-new-document-types`. Requiring or wiring *both* counterparties by default for a one-sided document is also the anti-pattern (`ReportSelectionHandlerCZZ` partitions strictly by counterparty); only a genuinely two-sided usage (as `ReportSelectionHandlerCZC` demonstrates for Compensation) needs both. - An `enumextension` extends `"Report Selection Usage"` and registers a report via `ReportSelections.InsertRecord`, without subscribing to the matching *single* counterparty's full triad — the filter event (`OnAfterFilterCustomerUsageReportSelections` on `page 9657` for a sales usage, `OnAfterFilterVendorUsageReportSelections` on `page 9658` for a purchase usage) AND the page-facing usage-enum map/validate events (`enumextension` on `"Custom Report Selection Sales"`/`"Report Selection Usage Vendor"` plus the matching map/validate subscribers) — `extend-report-selection-usage-for-new-document-types`. Requiring or wiring *both* counterparties by default for a one-sided document is also the anti-pattern (`ReportSelectionHandlerCZZ` partitions strictly by counterparty); only a genuinely two-sided usage (as `ReportSelectionHandlerCZC` demonstrates for Compensation) needs both.
@ -62,7 +62,7 @@ The following targeted checks cover every current `data-modeling` article. Treat
- An `enumextension` extends `"Price Calculation Handler"` and implements the `Price Calculation` interface, without a matching `OnFindSupportedSetup` subscriber inserting a `Price Calculation Setup` record naming that implementation as the `Implementation` for a `Method`/`Type`/`Asset Type` — `activate-new-price-calculation-handler-via-onfindsupportedsetup`. `Default := true` is only required on that row when it is meant as the fallback for its `Method`/`Type`/`Asset Type` combination; a row meant to be selected only through an explicit, specific `"Dtld. Price Calculation Setup"` row does not need it, so do not flag a missing `Default := true` by itself — flag the missing setup row/subscriber entirely. - An `enumextension` extends `"Price Calculation Handler"` and implements the `Price Calculation` interface, without a matching `OnFindSupportedSetup` subscriber inserting a `Price Calculation Setup` record naming that implementation as the `Implementation` for a `Method`/`Type`/`Asset Type` — `activate-new-price-calculation-handler-via-onfindsupportedsetup`. `Default := true` is only required on that row when it is meant as the fallback for its `Method`/`Type`/`Asset Type` combination; a row meant to be selected only through an explicit, specific `"Dtld. Price Calculation Setup"` row does not need it, so do not flag a missing `Default := true` by itself — flag the missing setup row/subscriber entirely.
- An `enumextension` extends `"Price Source Type"` with a new value intended for a sales, purchase, or job price list, without extending the matching document subset enum (`"Sales Price Source Type"`, `"Purchase Price Source Type"`, `"Job Price Source Type"`) with a value at the same numeric ID — `extend-price-source-type-must-sync-document-subset-enum`. - An `enumextension` extends `"Price Source Type"` with a new value intended for a sales, purchase, or job price list, without extending the matching document subset enum (`"Sales Price Source Type"`, `"Purchase Price Source Type"`, `"Job Price Source Type"`) with a value at the same numeric ID — `extend-price-source-type-must-sync-document-subset-enum`.
- A codeunit subscribes to `"Sales Line - Price"`'s `OnAfterAddSources` to register a custom field as a price source via `PriceSourceList.Add`, but that field has no `OnValidate` (or matching `OnAfterValidate`) that triggers recalculation — either `SalesLine.UpdateUnitPrice(<field no.>)`, or the explicit `SalesLine.PlanPriceCalcByField(<field no.>)` followed by `SalesLine.UpdateUnitPriceByField(<same field no.>)`. A bare `UpdateUnitPriceByField` without a preceding `PlanPriceCalcByField` for the same field number does not count as recalculation (it exits without recalculating) — `new-price-source-must-add-candidate-and-trigger-recalculation`. - A codeunit subscribes to `"Sales Line - Price"`'s `OnAfterAddSources` to register a custom field as a price source via `PriceSourceList.Add`, but that field has no `OnValidate` (or matching `OnAfterValidate`) that triggers recalculation — either `SalesLine.UpdateUnitPrice(<field no.>)`, or the explicit `SalesLine.PlanPriceCalcByField(<field no.>)` followed by `SalesLine.UpdateUnitPriceByField(<same field no.>)`. A bare `UpdateUnitPriceByField` without a preceding `PlanPriceCalcByField` for the same field number does not count as recalculation (it exits without recalculating) — `new-price-source-must-add-candidate-and-trigger-recalculation`.
- A report hand-constructs a barcode string that is demonstrably invalid or mismatched with the font provider it is rendered with (e.g. `'*' + Value + '*'` rendered with the IDAutomation Code 39 font, whose paired IDAutomation 1D provider encoder emits `(`/`)` wrapping, not literal `*` — with no validation or checksum applied) instead of encoding through the Barcode module. Do not flag manual delimiter use as a category — a custom provider paired with a font that genuinely expects those literal delimiters is not a finding. Also flag module use that does not match the interface: a 1D `"Barcode Font Provider"` path must call both `ValidateInput` and `EncodeFont`; a 2D `"Barcode Font Provider 2D"` path calls `EncodeFont` only (the 2D interface has no `ValidateInput`, so its absence there is not a finding). Separately, flag an otherwise correctly encoded barcode whose report layout names an evaluation/demo font instead of the purchased production font name — `report-barcodes-must-use-barcode-module-and-production-font-name`. - A report hand-constructs a barcode string only where a concrete, independently provable defect is visible: the source value can contain characters outside the symbology's character set and is never validated, a checksum the symbology/setup requires is never applied, or there is concrete evidence of an incompatible font binding. Do not flag manual start/stop delimiters by themselves — `*value*` is a documented, valid Code 39 form for IDAutomation fonts (IDAutomation also accepts parentheses), so delimiter choice alone is never a finding. Also flag module use that does not match the interface: a 1D `"Barcode Font Provider"` path must call both `ValidateInput` and `EncodeFont`; a 2D `"Barcode Font Provider 2D"` path calls `EncodeFont` only (the 2D interface has no `ValidateInput`, so its absence there is not a finding). Separately, flag an otherwise correctly encoded barcode whose report layout names an evaluation/demo font instead of the purchased production font name — `report-barcodes-must-use-barcode-module-and-production-font-name`.
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`. 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`.