mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 14:46:55 +01:00
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:
parent
4cd41f08f7
commit
aad3991d9f
7 changed files with 98 additions and 85 deletions
|
|
@ -1,28 +1,20 @@
|
|||
report 50102 "Sample Settlement Doc Bad"
|
||||
codeunit 50102 "Sample Posted Invoice Send"
|
||||
{
|
||||
UsageCategory = ReportsAndAnalysis;
|
||||
ApplicationArea = All;
|
||||
|
||||
dataset
|
||||
{
|
||||
dataitem(Customer; Customer)
|
||||
{
|
||||
column(No_Customer; "No.") { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codeunit 50102 "Sample Settlement Document Send"
|
||||
{
|
||||
procedure SendSettlementDocument(var Customer: Record Customer)
|
||||
procedure SendPostedInvoice(SalesInvoiceHeader: Record "Sales Invoice Header")
|
||||
var
|
||||
Customer: Record Customer;
|
||||
begin
|
||||
Customer.Get(SalesInvoiceHeader."Bill-to Customer No.");
|
||||
Customer.TestField("E-Mail");
|
||||
|
||||
// WRONG: hardcoded report, no Report Selections row backing it.
|
||||
// Works for the default case, but there is nowhere for an admin to
|
||||
// change the report or layout for one specific customer - this
|
||||
// document never shows up on "Document Layouts" at all, and the
|
||||
// only way to change it is a code change and a new release.
|
||||
Report.RunModal(Report::"Sample Settlement Doc Bad", false, false, Customer);
|
||||
// WRONG: the report is hardcoded instead of resolved through the
|
||||
// registered "S.Invoice" usage in Report Selections. This alone is
|
||||
// the defect - no hand-built email is needed for it: a Report
|
||||
// Selections row or a per-customer "Document Layouts" override
|
||||
// that points this usage at a different report or layout is
|
||||
// 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
ReportSelections: Record "Report Selections";
|
||||
ReportDistributionMgt: Codeunit "Report Distribution Management";
|
||||
begin
|
||||
// Custom validation specific to this document stays here...
|
||||
CheckReadyToSend(Customer);
|
||||
// Custom validation specific to this dispatch stays here...
|
||||
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
|
||||
// on Report Selections all apply automatically.
|
||||
SalesInvoiceHeader.SetRecFilter();
|
||||
ReportSelections.SendEmailToCust(
|
||||
"Report Selection Usage"::"S.Invoice".AsInteger(), Customer, Customer."No.",
|
||||
Customer.Name, true, Customer."No.");
|
||||
"Report Selection Usage"::"S.Invoice".AsInteger(), SalesInvoiceHeader, SalesInvoiceHeader."No.",
|
||||
ReportDistributionMgt.GetFullDocumentTypeText(SalesInvoiceHeader), true,
|
||||
SalesInvoiceHeader."Bill-to Customer No.");
|
||||
end;
|
||||
|
||||
local procedure CheckReadyToSend(var Customer: Record Customer)
|
||||
local procedure CheckReadyToSend(SalesInvoiceHeader: Record "Sales Invoice Header")
|
||||
var
|
||||
Customer: Record Customer;
|
||||
begin
|
||||
Customer.Get(SalesInvoiceHeader."Bill-to Customer No.");
|
||||
Customer.TestField("E-Mail");
|
||||
end;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,15 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
A codeunit that hardcodes which report to run (`Report.RunModal(MyReportId, ...)`)
|
||||
and builds its own email directly, instead of registering the document
|
||||
A codeunit that hardcodes which report to run (`Report.RunModal(MyReportId, ...)`),
|
||||
or builds its own email directly, instead of registering the document
|
||||
through `table 77 "Report Selections"` and calling its own
|
||||
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
|
||||
(`"Use for Email Attachment"`, `"Use for Email Body"`, `"Email Body 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
|
||||
|
||||
A codeunit that runs a hardcoded report ID and builds its own email
|
||||
message directly, with no `Report Selections` row backing it. It works for
|
||||
the default case, but the report/layout cannot be changed per account
|
||||
A codeunit that runs a hardcoded report ID, or builds its own email
|
||||
message directly, for a document that has (or should have) a
|
||||
`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
|
||||
"Document Layouts" — the standard place every other document's
|
||||
distribution is configured.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
page 50101 "Sample Settlement Document Card"
|
||||
page 50101 "Sample Posted Invoice Card"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
SourceTable = "Sales Invoice Header";
|
||||
ApplicationArea = All;
|
||||
Editable = false;
|
||||
|
||||
actions
|
||||
{
|
||||
|
|
@ -16,7 +17,9 @@ page 50101 "Sample Settlement Document Card"
|
|||
|
||||
trigger OnAction()
|
||||
var
|
||||
SalesInvoiceHeader: Record "Sales Invoice Header";
|
||||
DocumentSendingProfile: Record "Document Sending Profile";
|
||||
ReportDistributionMgt: Codeunit "Report Distribution Management";
|
||||
begin
|
||||
// WRONG: this is a plain, on-demand "Email" button, not
|
||||
// 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
|
||||
// silent no-op, with no indication an unrelated setup
|
||||
// field is why.
|
||||
DocumentSendingProfile.GetDefaultForCustomer(Rec."No.", DocumentSendingProfile);
|
||||
SalesInvoiceHeader := Rec;
|
||||
CurrPage.SetSelectionFilter(SalesInvoiceHeader);
|
||||
DocumentSendingProfile.GetDefaultForCustomer(Rec."Bill-to Customer No.", DocumentSendingProfile);
|
||||
DocumentSendingProfile.Send(
|
||||
"Report Selection Usage"::"S.Invoice".AsInteger(), Rec, Rec."No.", Rec."No.",
|
||||
Rec.Name, Rec.FieldNo("No."), Rec.FieldNo("No."));
|
||||
"Report Selection Usage"::"S.Invoice".AsInteger(), SalesInvoiceHeader, Rec."No.",
|
||||
Rec."Bill-to Customer No.", ReportDistributionMgt.GetFullDocumentTypeText(Rec),
|
||||
SalesInvoiceHeader.FieldNo("Bill-to Customer No."), SalesInvoiceHeader.FieldNo("No."));
|
||||
end;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
page 50101 "Sample Settlement Document Card"
|
||||
page 50101 "Sample Posted Invoice Card"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = Customer;
|
||||
SourceTable = "Sales Invoice Header";
|
||||
ApplicationArea = All;
|
||||
Editable = false;
|
||||
|
||||
actions
|
||||
{
|
||||
|
|
@ -16,7 +17,9 @@ page 50101 "Sample Settlement Document Card"
|
|||
|
||||
trigger OnAction()
|
||||
var
|
||||
SalesInvoiceHeader: Record "Sales Invoice Header";
|
||||
ReportSelections: Record "Report Selections";
|
||||
ReportDistributionMgt: Codeunit "Report Distribution Management";
|
||||
begin
|
||||
// Calls Report Selections directly - the button's outcome
|
||||
// depends only on this customer's registered report/layout,
|
||||
|
|
@ -24,9 +27,13 @@ page 50101 "Sample Settlement Document Card"
|
|||
// DocumentSendingProfile.TrySendToEMail(...) instead would
|
||||
// be equally correct: it never Get's the customer's
|
||||
// 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(
|
||||
"Report Selection Usage"::"S.Invoice".AsInteger(), Rec, Rec."No.",
|
||||
Rec.Name, true, Rec."No.");
|
||||
"Report Selection Usage"::"S.Invoice".AsInteger(), SalesInvoiceHeader, Rec."No.",
|
||||
ReportDistributionMgt.GetFullDocumentTypeText(Rec), true, Rec."Bill-to Customer No.");
|
||||
end;
|
||||
}
|
||||
action(PrintDocument)
|
||||
|
|
@ -37,10 +44,14 @@ page 50101 "Sample Settlement Document Card"
|
|||
|
||||
trigger OnAction()
|
||||
var
|
||||
SalesInvoiceHeader: Record "Sales Invoice Header";
|
||||
ReportSelections: Record "Report Selections";
|
||||
begin
|
||||
SalesInvoiceHeader := Rec;
|
||||
CurrPage.SetSelectionFilter(SalesInvoiceHeader);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,34 +56,26 @@ See sample: [`report-barcodes-must-use-barcode-module-and-production-font-name.g
|
|||
|
||||
## Anti Pattern
|
||||
|
||||
Constructing a barcode string by hand instead of using the module's
|
||||
provider/encoder API — not because a manual delimiter is inherently
|
||||
wrong (Code 39's own symbology does use `*` as start/stop; Microsoft
|
||||
Learn's font table says so), but because hand-rolled construction is
|
||||
demonstrably mismatched with what the real encoder produces: it skips
|
||||
`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.
|
||||
Constructing a barcode string by hand where that construction has a
|
||||
concrete, independently provable defect: a source value that can contain
|
||||
characters outside the symbology's character set is never validated, a
|
||||
checksum the symbology or setup requires is never applied, or there is
|
||||
concrete evidence of an incompatible font binding.
|
||||
|
||||
Flag demonstrably invalid or mismatched hand construction, not manual
|
||||
delimiter use as a category — a custom provider paired with a font that
|
||||
genuinely expects literal `*` delimiters is a different, legitimate case.
|
||||
The delimiter itself is not the defect. `*value*` is a documented, valid
|
||||
Code 39 form for IDAutomation fonts (Microsoft Learn's font table and
|
||||
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
|
||||
evaluation font instead of the purchased one. Both look complete in
|
||||
review and fail silently — the first because the data was never a real
|
||||
barcode, the second because BC online refuses to render it.
|
||||
|
||||
The same gap exists even when the module *is* used: a 1D path that calls
|
||||
`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.
|
||||
The same validation gap exists when the module *is* used: a 1D path that
|
||||
calls `EncodeFont` on `"Barcode Font Provider"` without `ValidateInput`
|
||||
(IDAutomation 1D Provider's `EncodeFont` does not validate on its own).
|
||||
The sample shows this variant, visible in AL alone. A last version:
|
||||
encoding correctly but naming the evaluation font, which BC online
|
||||
refuses to render.
|
||||
|
||||
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:
|
||||
`ValidateInput` + `EncodeFont`); `IDAutomation 1D Provider/
|
||||
IDAutomation1DProvider.Codeunit.al` (`EncodeFont` goes straight to the
|
||||
symbology encoder; only `ValidateInput` calls `IsValidInput`); `Barcode Provider 2D/Font/
|
||||
BarcodeFontProvider2D.Interface.al` (2D: only `EncodeFont`); both read
|
||||
fresh from source. `IDAutomation 1D Provider/Encoders/
|
||||
IDA1DCode39Encoder.Codeunit.al` (`codeunit 9204`, regex accepts literal
|
||||
`*` as plain input; `EncodeFont` → `DotNet FontEncoder.Code39`). Split
|
||||
and delimiter mismatch both confirmed live: `.../Inventory/Item/
|
||||
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 `*`.
|
||||
symbology encoder; only `ValidateInput` calls `IsValidInput`); `Barcode Provider 2D/Font/BarcodeFontProvider2D.Interface.al`
|
||||
(2D: only `EncodeFont`). `IDAutomation 1D Provider/Encoders/IDA1DCode39Encoder.Codeunit.al`
|
||||
(`codeunit 9204`, regex accepts literal `*`; `EncodeFont` → `DotNet FontEncoder.Code39`).
|
||||
1D/2D split: `.../Inventory/Item/ItemGTINLabel.Report.al` (`report 6625`,
|
||||
validates+encodes 1D, only encodes 2D). Encoder output form: `IDA1DCode39Test.Codeunit.al`
|
||||
(`codeunit 135044`): `EncodeFontSuccessTest('1234', Code39, '(1234)')`.
|
||||
|
||||
Microsoft Learn "Adding Barcodes to Reports" and "Barcode Fonts with
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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 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`.
|
||||
- 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.
|
||||
- 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.
|
||||
|
|
@ -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 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 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`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue