mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 14:46:55 +01:00
- 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>
98 lines
5.4 KiB
Markdown
98 lines
5.4 KiB
Markdown
---
|
|
bc-version: [all]
|
|
domain: data-modeling
|
|
keywords: [price-calculation, price-calculation-handler, price-calculation-setup, integration-event, pricing]
|
|
technologies: [al]
|
|
countries: [w1]
|
|
application-area: [all]
|
|
---
|
|
|
|
# Activate a new Price Calculation Handler through OnFindSupportedSetup, not just by implementing it
|
|
|
|
## Description
|
|
|
|
`enum 7011 "Price Calculation Handler"` (`implements "Price
|
|
Calculation"`) is how a new pricing engine plugs into Business Central —
|
|
extend the enum with a value pointing at a codeunit that implements the
|
|
`Price Calculation` interface. That alone does not make the new handler
|
|
usable on any document. `codeunit 7001 "Price Calculation Mgt."` decides
|
|
which handler applies to a given line by looking up `table 7006 "Price
|
|
Calculation Setup"`, a table of `(Code, Method, Type, Asset Type,
|
|
Implementation, Enabled, Default)` rows populated at startup by its own
|
|
`OnFindSupportedSetup` event — every implementation codeunit is expected
|
|
to subscribe to that event and insert its own setup row(s). A handler
|
|
enum value with no matching setup row is real and selectable in the enum
|
|
itself, but never chosen for any actual sale, purchase, or job line,
|
|
because `Price Calculation Mgt.` has no setup row that names it.
|
|
|
|
`FindSetup` resolves a handler in two stages, and only the second one
|
|
looks at `Default`. It first asks `codeunit 7004 "Price Calculation Dtld.
|
|
Setup"` to match the line against `table 7008 "Dtld. Price Calculation
|
|
Setup"` ("Detailed Price Calculation Setup", keyed to an exact
|
|
`Method`/`Type`/`Asset Type`/`Source`/`Asset No.` combination via its own
|
|
`"Setup Code"`); on a match it does `PriceCalculationSetup.Get(...
|
|
"Setup Code")` directly, with no `Default` filter. Only when no detailed
|
|
row matches does it fall back to `SetRange(Default, true)` plus
|
|
`SetRange(Method, ...)` to pick the one catch-all row for that
|
|
combination. A row without `Default := true` is invisible to *that*
|
|
fallback, but not invisible outright — a detailed-setup row can still
|
|
select it by naming its `Code`. A row whose `Method` matches neither path
|
|
is invisible either way — same symptom, different cause.
|
|
|
|
## Best Practice
|
|
|
|
Ship a new `Price Calculation Handler` value together with an
|
|
`OnFindSupportedSetup` subscriber that inserts at least one `Price
|
|
Calculation Setup` record naming it as the `Implementation`, for the
|
|
relevant `Method` (e.g. `"Lowest Price"`), `Type` (`Sale`/`Purchase`), and
|
|
`Asset Type`. `Default := true` is required only when this row is the
|
|
*fallback* for that combination — the row `FindSetup`'s own
|
|
`SetRange(Default, true)` branch selects when no more specific setup
|
|
applies. A handler meant to be selected only for specific customers or
|
|
items should instead be reachable through a matching `"Dtld. Price
|
|
Calculation Setup"` row; `FindSetup` resolves that before it ever checks
|
|
`Default`, so it needs no `Default := true`.
|
|
|
|
See sample: [`activate-new-price-calculation-handler-via-onfindsupportedsetup.good.al`](activate-new-price-calculation-handler-via-onfindsupportedsetup.good.al).
|
|
|
|
## Anti Pattern
|
|
|
|
Extending `Price Calculation Handler` and implementing the `Price
|
|
Calculation` interface, without subscribing to `OnFindSupportedSetup` to
|
|
insert a setup record. The new handler exists, compiles, and can even be
|
|
selected manually if a user creates their own `Price Calculation Setup`
|
|
row through the UI — but ships with no default row, so it's never active
|
|
for anyone until someone notices it's missing and configures it by hand.
|
|
|
|
See sample: [`activate-new-price-calculation-handler-via-onfindsupportedsetup.bad.al`](activate-new-price-calculation-handler-via-onfindsupportedsetup.bad.al).
|
|
|
|
## Source
|
|
|
|
BCApps (`src/Layers/W1/BaseApp/Pricing/Calculation/`):
|
|
`PriceCalculationHandler.Enum.al` (`enum 7011 "Price Calculation Handler"
|
|
implements "Price Calculation"`); `PriceCalculationMgt.Codeunit.al`
|
|
(`OnFindSupportedSetup(var TempPriceCalculationSetup: Record "Price
|
|
Calculation Setup" temporary)`, and `FindSetup(...): Boolean`, which
|
|
first calls `PriceCalculationDtldSetup.FindSetup(DtldPriceCalcSetup)` and
|
|
on a match does `PriceCalculationSetup.Get(... "Setup Code")` with no
|
|
`Default` filter — only on failure does it fall back to
|
|
`SetRange(Enabled, true)`, `SetRange(Default, true)`, `SetRange(Method,
|
|
...)`); `PriceCalculationSetup.Table.al` (`table 7006 "Price Calculation
|
|
Setup"`: `Code`, `Method`, `Type`, `"Asset Type"`, `Implementation`,
|
|
`Enabled`, `Default`); `PriceCalculationDtldSetup.Codeunit.al` (`codeunit
|
|
7004 "Price Calculation Dtld. Setup"`, `FindSetup(var DtldPriceCalcSetup:
|
|
Record "Dtld. Price Calculation Setup"): Boolean`, matching progressively
|
|
looser `Source Group`/`Source No.`/`Asset Type`/`Asset No.` combinations —
|
|
never `Default`); `DtldPriceCalculationSetup.Table.al` (`table 7008 "Dtld.
|
|
Price Calculation Setup"`, Caption "Detailed Price Calculation Setup",
|
|
`"Setup Code"` relates to `"Price Calculation Setup".Code where(Enabled =
|
|
const(true))` — no `Default` condition).
|
|
|
|
Microsoft Learn, "Extending Price Calculations": "Each codeunit that
|
|
implements the Price Calculation interface must subscribe to the
|
|
OnFindSupportedSetup() event... to fill the price calculation setup
|
|
table." Same article: "You can enter detailed setup records for
|
|
non-default setup lines... If a matching setup is found its
|
|
implementation is used... If there is no matching setup exception, we
|
|
use the default implementation."
|
|
(https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-extending-best-price-calculations)
|