- activate-new-price-calculation-handler-via-onfindsupportedsetup: Default := true is required only for the fallback branch of PriceCalculationMgt's two-stage FindSetup - a handler reachable via a specific Dtld. Price Calculation Setup row needs no Default. Softened the article and its worklist cue accordingly. Also fixed an undefined "Sample Price Calc - Special" codeunit referenced but never declared in the eval fixtures - added a real implementation of interface "Price Calculation" with stub methods. - new-price-source-must-add-candidate-and-trigger-recalculation: the good fixture called UpdateUnitPriceByField directly, which is a silent no-op without a prior PlanPriceCalcByField call (FieldCausedPriceCalculation gating, verified against SalesLine.Table.al). Switched to the public UpdateUnitPrice wrapper, matching real BCApps usage in ItemReferenceManagement.Codeunit.al. - report-barcodes-must-use-barcode-module-and-production-font-name: split the 1D (ValidateInput + EncodeFont) and 2D (EncodeFont only) Barcode Font Provider interfaces, which the article previously conflated. Reframed the Code 39 anti-pattern around demonstrable encoding/checksum mismatch (verified against IDA1DCode39Encoder.Codeunit.al's real '(value)' output) rather than rejecting all manual delimiter use, since '*' is a legitimate Code 39 start/stop character. Also fixed extend-find-entries-navigate- for-new-document-types' eval fixtures, which referenced an undefined "Sample Posted Document Header" table/page - declared both. All claims re-verified against live microsoft/BCApps source. Validators: frontmatter 0/0, review-fixtures 126/20 domains PASSED, knowledge-index 342/575 PASSED, skill-index 19 leaves PASSED. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.3 KiB
| bc-version | domain | keywords | technologies | countries | application-area | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
data-modeling |
|
|
|
|
A new price source needs both a calculation candidate and a recalculation trigger
Description
Making a custom field usable as a price source on a sales line is two
separate, independent pieces of wiring, and doing only one produces a
line that looks like it's using the new source without ever actually
being priced by it. codeunit "Sales Line - Price" publishes
OnAfterAddSources(SalesHeader: Record "Sales Header"; SalesLine: Record "Sales Line"; PriceType: Enum "Price Type"; var PriceSourceList: Codeunit "Price Source List") — subscribing here and calling
PriceSourceList.Add(SourceType, SourceNo) makes the source a candidate
the calculation considers. But nothing about that subscription causes
the price to be recalculated when the source field's value changes on
an existing line. That's the second, separate piece, and it needs to be
wired correctly: Sales Line's procedure UpdateUnitPriceByField(CalledByFieldNo: Integer) only recalculates if
the field was already planned — internally it exits immediately unless
procedure PlanPriceCalcByField(CurrPriceFieldNo: Integer) was already
called for that same field number. Calling UpdateUnitPriceByField on
its own, without a matching PlanPriceCalcByField call first, compiles
fine and looks correct, but silently recalculates nothing. Sales Line
also exposes procedure UpdateUnitPrice(CalledByFieldNo: Integer), a
convenience wrapper that does both steps in the right order (plan, then
update) in one call — this is the method the base app itself calls from
outside Sales Line to trigger recalculation for a field it just
changed (see Inventory/Item/Catalog/ItemReferenceManagement.Codeunit.al:
SalesLine.UpdateUnitPrice(SalesLine.FieldNo("Item Reference No."))), and
it's what a custom price source field's own trigger should call too — the
same way Microsoft's own Location example is wired from a Sales Line
validation event, not from the price source registration itself.
Add the source without wiring recalculation, and the failure hides easily: a new line still prices correctly, because the field already holds its value when calculation first runs on insert. The gap only shows up when someone changes the source field's value on an existing line — the price silently keeps its old value until something unrelated happens to trigger recalculation.
Best Practice
Wire both halves together whenever a field becomes a price source: an
OnAfterAddSources subscriber that adds it via PriceSourceList.Add, and
a trigger on the field itself (its own OnValidate, or a matching
OnAfterValidate integration event) that calls
SalesLine.UpdateUnitPrice(SalesLine.FieldNo(<TheField>)). Calling
UpdateUnitPriceByField directly, without first calling
PlanPriceCalcByField for that same field number, is not equivalent —
it exits immediately and recalculates nothing. UpdateUnitPrice does
both calls, in the correct order, in one step.
See sample: new-price-source-must-add-candidate-and-trigger-recalculation.good.al.
Anti Pattern
Subscribing to OnAfterAddSources to register a custom field as a price
source, without also triggering recalculation (via UpdateUnitPrice, or
the PlanPriceCalcByField + UpdateUnitPriceByField pair) from that
field's own validation. The field is a genuine, working calculation
candidate — new lines price correctly — but editing the field on an
existing line leaves the unit price stale, with nothing to indicate why.
See sample: new-price-source-must-add-candidate-and-trigger-recalculation.bad.al.
Source
BCApps (src/Layers/W1/BaseApp/): Sales/Pricing/SalesLinePrice.Codeunit.al
(local procedure OnAfterAddSources(SalesHeader: Record "Sales Header"; SalesLine: Record "Sales Line"; PriceType: Enum "Price Type"; var PriceSourceList: Codeunit "Price Source List")); Pricing/Source/PriceSourceList.Codeunit.al
(procedure Add(SourceType: Enum "Price Source Type"; SourceNo: Code[20]));
Sales/Document/SalesLine.Table.al (procedure PlanPriceCalcByField(CurrPriceFieldNo: Integer); procedure UpdateUnitPrice(CalledByFieldNo: Integer); procedure UpdateUnitPriceByField(CalledByFieldNo: Integer), which exits immediately
unless FieldCausedPriceCalculation already equals CalledByFieldNo —
the state PlanPriceCalcByField sets). External, idiomatic use of the
one-call form: Inventory/Item/Catalog/ItemReferenceManagement.Codeunit.al
(SalesLine.UpdateUnitPrice(SalesLine.FieldNo("Item Reference No."))).
Microsoft Learn, "Extending Price Calculations" (Location example): "To recalculate the price, we can subscribe to events that pass the sales line by reference... We'll call the UpdateUnitPriceByLocationCode() method, which is a simplified version of the UpdateUnitPriceByField() method... To add the location in the source list for price calculations, we'll subscribe to the OnAfterAddSources event of Codeunit 'Sales Line - Price,' and add the Location Code as a source." (https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-extending-best-price-calculations)