Why do you need this change?
1. Event Request: OnCalcBestDirectUnitCostOnAfterIsInMinQty
Please add a new integration event in the CalcBestDirectUnitCost procedure of codeunit 7010 "Purch. Price Calc. Mgt.", immediately after the IsInMinQty(...) call inside the price iteration loop, exposing the result as a var parameter that subscribers can override.
IsInMinQty checks whether the current Purchase Price record's minimum quantity condition is met before it is considered as a price candidate. The standard check compares MinQty against either QtyPerUOM * Qty or plain Qty depending on whether a unit of measure code is set.
In some business setups, the quantity that should be compared against the minimum is not the individual purchase line quantity but the total quantity across all lines in the document. Whether to use the line quantity or the order total depends on a field stored on the Purchase Price record itself.
The existing OnBeforeIsInMinQty event does not pass the PurchPrice record - it only receives UnitofMeasureCode and MinQty. Because of this, a subscriber cannot read fields on the price record and cannot decide which quantity to compare against. The new event needs to fire inside the CalcBestDirectUnitCost loop where the full PurchPrice record is available, and must expose the result of IsInMinQty as a var Boolean so subscribers can override it.
Alternatives Evaluated
The following existing events were evaluated:
-
OnBeforeIsInMinQty(Item, UnitofMeasureCode, MinQty, Result, IsHandled): Does not pass the PurchPrice record. A subscriber cannot read per-price-line fields and therefore cannot decide which quantity reference to use. This event cannot be used.
-
OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item): Fires once before the loop starts. A subscriber setting IsHandled := true would need to re-implement the entire price selection loop. This event cannot be used.
-
OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT(PurchPrice): Fires after IsInMinQty has already returned true and execution is already inside the if IsInMinQty then begin block. It cannot intercept a false result and cannot affect which records enter the candidate set. This event cannot be used.
-
SetUoM(Qty2, QtyPerUoM2): A subscriber could call SetUoM before CalcBestDirectUnitCost runs (for example via OnBeforeCalcBestDirectUnitCost) to set a different quantity - such as the order total - that IsInMinQty would then use. This works when all price records for the item should use the same quantity reference. It cannot be used when different price records in the same loop require different quantity references, because SetUoM sets a single global value that applies to every iteration. This event cannot be used.
None of the existing approaches allow a subscriber to change the quantity reference individually per price record based on a field on the PurchPrice row.
Performance Considerations
The event fires once per record in the Purchase Price temp table inside CalcBestDirectUnitCost. The number of price records per item/vendor/date is typically small. The overhead is the same as the existing OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT event, which already fires in the same loop. When no subscriber changes InMinQty, execution follows the standard IsInMinQty result unchanged.
Data Sensitivity Review
The event parameters expose:
var PurchPrice: Record "Purchase Price" - the record currently being evaluated; already in scope in the same loop iteration.
QtyPerUOM: Decimal - already used internally by IsInMinQty.
Qty: Decimal - already used internally by IsInMinQty.
var InMinQty: Boolean - the result of the IsInMinQty call; a Boolean with no sensitive data. Passed as var so subscribers can change it.
No data is exposed beyond what is already available inside CalcBestDirectUnitCost at this point. No IsHandled parameter is needed because IsInMinQty is a small local function with no side effects - there is no cost to always calling it first and letting the event override the result afterward.
Multi-Extension Interaction
Multiple extensions can subscribe to this event at the same time. If multiple subscribers change InMinQty, the last subscriber to run determines the final value. When no subscriber is active, InMinQty keeps the value returned by IsInMinQty and behaviour is unchanged.
2. Event Request: OnCalcBestDirectUnitCostOnBeforeCaseStatement
Please add a new integration event with an IsHandled parameter in the CalcBestDirectUnitCost procedure of codeunit 7010 "Purch. Price Calc. Mgt.", immediately after the three price conversions (ConvertPriceToVAT, ConvertPriceToUoM, ConvertPriceLCYToFCY) and before the case true of block that decides whether the current price replaces the current best price.
The case true of block applies two rules:
- Specificity preference: a price with a currency code or variant code always takes priority over a generic one.
- Cost comparison: among equally specific prices, pick the one with the lowest effective amount.
Some business setups need a different rule for the cost comparison step. For example, selecting the price from the highest applicable minimum-quantity tier rather than the lowest cost, or preferring the most recently started price as a tiebreaker. These strategies cannot be implemented by subscribing to any existing event because no event exposes both the current candidate price (PurchPrice, already converted) and the current best price (BestPurchPrice) simultaneously - at a point where the selection decision can still be changed.
Alternatives Evaluated
The following existing events were evaluated:
-
OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT(PurchPrice): Fires before the three price conversions. The prices have not yet been normalised to the same VAT, unit of measure and currency basis, so comparing them is not meaningful. BestPurchPrice is also not accessible here. This event cannot be used.
-
OnAfterCalcBestDirectUnitCostFound(PurchPrice, BestPurchPriceFound, IsHandled): Fires after the entire loop has finished. The selection is already final and BestPurchPrice is not passed, so it is not possible to change which price was chosen. This event cannot be used.
-
OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item): A subscriber setting IsHandled := true must re-implement the complete loop including all conversion calls and selection logic. This event cannot be used.
-
An event after the case true of block: Considered as a simpler alternative - the standard rules would always run and the subscriber could override the result afterward. However, this has a critical edge case: when the standard second rule determines the current candidate is better and sets BestPurchPrice := PurchPrice, the event fires with BestPurchPrice and PurchPrice pointing to the same record. The subscriber has lost the previous best price and cannot restore it. For use cases that need to replace the cost comparison with a different criterion (e.g. pick by minimum-quantity tier), the subscriber must compare the old best against the current candidate - which is only possible before the case true of runs.
None of the existing events allow a subscriber to compare the unconverted current candidate against the current best price before the selection is made.
Performance Considerations
The event fires once per price record that passes the min-quantity check, inside the CalcBestDirectUnitCost loop. The overhead is the same as the existing OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT event, which already fires at the same frequency in the same loop. When no subscriber sets IsHandled := true, the standard case true of logic runs unchanged.
Data Sensitivity Review
The event parameters expose:
var PurchPrice: Record "Purchase Price" - the current candidate record, already converted; already in scope at this point.
var BestPurchPrice: Record "Purchase Price" - the current best price found so far; a temporary record local to CalcBestDirectUnitCost. Passed as var so subscribers can assign to it when they select a different winner.
var BestPurchPriceFound: Boolean - must be var so a subscriber that replaces the case true of block can also mark that a selection was made.
var IsHandled: Boolean - standard skip flag.
LineDiscPerCent: Decimal - the line discount percentage set via SetLineDisc before the loop began. Required so subscribers can replicate the effective-amount calculation used by the standard second rule: "Direct Unit Cost" * (1 - LineDiscPerCent / 100). The local CalcLineAmount procedure that performs this calculation is not accessible to subscribers.
All values are already in scope within the procedure at this point. BestPurchPrice is a temporary record that is not persisted. No sensitive data is introduced.
Multi-Extension Interaction
Multiple extensions can subscribe at the same time. The IsHandled pattern is standard across codeunit 7010. If multiple subscribers set IsHandled := true, each one runs in sequence and the last value written to BestPurchPrice is used. Extensions that only need to read BestPurchPrice as context without replacing the standard logic should leave IsHandled := false.
3. Event Request: Extend OnCalcBestDirectUnitCostOnBeforeNoPriceFound with PriceInSKU and SKU Parameters
Please extend the existing integration event OnCalcBestDirectUnitCostOnBeforeNoPriceFound in codeunit 7010 "Purch. Price Calc. Mgt." to include var PriceInSKU: Boolean and var SKU: Record "Stockkeeping Unit" as additional parameters.
When no purchase price is found for an item/vendor combination, CalcBestDirectUnitCost falls back to either SKU."Last Direct Cost" or Item."Last Direct Cost", depending on whether PriceInSKU evaluates to true. Some setups need to intercept this fallback - for example, to set the cost to 0 instead of using the item's last direct cost when a setup flag says that falling back to item cost is not allowed.
The existing event fires before PriceInSKU is recalculated on the line PriceInSKU := PriceInSKU and (SKU."Last Direct Cost" <> 0). A subscriber that sets IsHandled := true needs to make the same PriceInSKU evaluation itself to decide whether to use the SKU cost or apply custom logic. Without the current value of PriceInSKU and access to SKU."Last Direct Cost", the subscriber cannot make this decision correctly and is forced to duplicate internal logic or hardcode assumptions.
Adding PriceInSKU and SKU as var parameters gives subscribers the information they need without having to duplicate the internal state.
Alternatives Evaluated
The following existing events were evaluated:
-
OnCalcBestDirectUnitCostOnAfterSetUnitCost(var BestPurchPrice: Record "Purchase Price"): Fires after the fallback cost has been assigned and all three conversion procedures (ConvertPriceToVAT, ConvertPriceToUoM, ConvertPriceLCYToFCY) have already run against it. Changing the cost here does not undo those conversions. A subscriber cannot cleanly set the cost to 0 at this point. This event cannot be used.
-
OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item): A subscriber setting IsHandled := true must re-implement the complete price selection loop, all min-quantity checks, all conversions, and the fallback logic. This is not a viable targeted intercept for a change that affects only the fallback branch.
-
Adding a new second event after the PriceInSKU update line: This would work technically, but adds an extra event for a case that the existing OnCalcBestDirectUnitCostOnBeforeNoPriceFound already targets. Extending the existing event's parameters is simpler and less disruptive.
None of the existing events allow a subscriber to intercept the fallback cost assignment with full knowledge of what PriceInSKU will be.
Performance Considerations
The event fires at most once per CalcBestDirectUnitCost call, and only when no price was found (not BestPurchPriceFound). When purchase prices are configured for the item/vendor, this branch is never reached. Passing two additional parameters (Boolean and Record) to an existing event has no measurable impact. When a price is found, the if not BestPurchPriceFound then block is never entered and the event never fires..
Data Sensitivity Review
The two new parameters expose:
var PriceInSKU: Boolean - a computed flag indicating whether a stockkeeping unit with a non-zero last direct cost was matched. No sensitive data. Passed as var so a subscriber can update it if needed before the standard if not IsHandled then block runs.
var SKU: Record "Stockkeeping Unit" - the matched SKU record (if any). Already exposed by OnBeforeCalcBestDirectUnitCost in the same codeunit, setting the same precedent. Contains standard SKU fields including "Last Direct Cost".
Both values are already in scope within CalcBestDirectUnitCost at the event's insertion point and are used in the lines immediately following the event call. No sensitive data is introduced beyond what the procedure already handles.
Multi-Extension Interaction
The IsHandled pattern is unchanged. If multiple subscribers set IsHandled := true, each runs in sequence and the last value written to BestPurchPrice."Direct Unit Cost" is used. Because PriceInSKU is var, one subscriber's update to it is visible to subsequent subscribers in the same event firing, which is consistent with how var parameters work for integration events in Business Central.
Describe the request
Requested changes in CalcBestDirectUnitCost:
- Event Request:
OnCalcBestDirectUnitCostOnAfterIsInMinQty
procedure CalcBestDirectUnitCost(var PurchPrice: Record "Purchase Price")
var
BestPurchPrice: Record "Purchase Price";
BestPurchPriceFound: Boolean;
IsHandled: Boolean;
InMinQty: Boolean; //>> new local variable
begin
IsHandled := false;
OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item);
if IsHandled then
exit;
FoundPurchPrice := PurchPrice.Find('-');
if FoundPurchPrice then
repeat
//>> code change start
InMinQty := IsInMinQty(PurchPrice."Unit of Measure Code", PurchPrice."Minimum Quantity");
OnCalcBestDirectUnitCostOnAfterIsInMinQty(PurchPrice, QtyPerUOM, Qty, InMinQty);
if InMinQty then begin
//<< code change end
OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT(PurchPrice);
ConvertPriceToVAT(
Vend."Prices Including VAT", Item."VAT Prod. Posting Group",
Vend."VAT Bus. Posting Group", PurchPrice."Direct Unit Cost");
ConvertPriceToUoM(PurchPrice."Unit of Measure Code", PurchPrice."Direct Unit Cost");
ConvertPriceLCYToFCY(PurchPrice."Currency Code", PurchPrice."Direct Unit Cost");
case true of
((BestPurchPrice."Currency Code" = '') and (PurchPrice."Currency Code" <> '')) or
((BestPurchPrice."Variant Code" = '') and (PurchPrice."Variant Code" <> '')):
begin
BestPurchPrice := PurchPrice;
BestPurchPriceFound := true;
end;
((BestPurchPrice."Currency Code" = '') or (PurchPrice."Currency Code" <> '')) and
((BestPurchPrice."Variant Code" = '') or (PurchPrice."Variant Code" <> '')):
if (BestPurchPrice."Direct Unit Cost" = 0) or
(CalcLineAmount(BestPurchPrice) > CalcLineAmount(PurchPrice))
then begin
BestPurchPrice := PurchPrice;
BestPurchPriceFound := true;
end;
end;
end;
until PurchPrice.Next() = 0;
// ... remainder of procedure unchanged
end;
New event declaration:
[IntegrationEvent(false, false)]
local procedure OnCalcBestDirectUnitCostOnAfterIsInMinQty(var PurchPrice: Record "Purchase Price"; QtyPerUOM: Decimal; Qty: Decimal; var InMinQty: Boolean)
begin
end;
Subscriber example:
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch. Price Calc. Mgt.", 'OnCalcBestDirectUnitCostOnAfterIsInMinQty', '', false, false)]
local procedure OnAfterIsInMinQtyHandler(var PurchPrice: Record "Purchase Price"; QtyPerUOM: Decimal; Qty: Decimal; var InMinQty: Boolean)
var
OrderQty: Decimal;
begin
// "EXT Qty. Reference" is a custom field on Purchase Price that controls
// whether the minimum quantity is checked against the individual line
// quantity or the total order quantity.
case PurchPrice."EXT Qty. Reference" of
PurchPrice."EXT Qty. Reference"::Order:
begin
OrderQty := GetEXTOrderTotalQty(PurchPrice."Item No.", PurchPrice."Unit of Measure Code");
if PurchPrice."Unit of Measure Code" = '' then
InMinQty := PurchPrice."Minimum Quantity" <= QtyPerUOM * OrderQty
else
InMinQty := PurchPrice."Minimum Quantity" <= OrderQty;
end;
PurchPrice."EXT Qty. Reference"::Line:
; // keep the result from the standard IsInMinQty call
end;
end;
2. Event Request: OnCalcBestDirectUnitCostOnBeforeCaseStatement
procedure CalcBestDirectUnitCost(var PurchPrice: Record "Purchase Price")
var
BestPurchPrice: Record "Purchase Price";
BestPurchPriceFound: Boolean;
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item);
if IsHandled then
exit;
FoundPurchPrice := PurchPrice.Find('-');
if FoundPurchPrice then
repeat
if IsInMinQty(PurchPrice."Unit of Measure Code", PurchPrice."Minimum Quantity") then begin
OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT(PurchPrice);
ConvertPriceToVAT(
Vend."Prices Including VAT", Item."VAT Prod. Posting Group",
Vend."VAT Bus. Posting Group", PurchPrice."Direct Unit Cost");
ConvertPriceToUoM(PurchPrice."Unit of Measure Code", PurchPrice."Direct Unit Cost");
ConvertPriceLCYToFCY(PurchPrice."Currency Code", PurchPrice."Direct Unit Cost");
//>> code change start
IsHandled := false;
OnCalcBestDirectUnitCostOnBeforeCaseStatement(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, LineDiscPerCent);
if not IsHandled then
//<< code change end
case true of
((BestPurchPrice."Currency Code" = '') and (PurchPrice."Currency Code" <> '')) or
((BestPurchPrice."Variant Code" = '') and (PurchPrice."Variant Code" <> '')):
begin
BestPurchPrice := PurchPrice;
BestPurchPriceFound := true;
end;
((BestPurchPrice."Currency Code" = '') or (PurchPrice."Currency Code" <> '')) and
((BestPurchPrice."Variant Code" = '') or (PurchPrice."Variant Code" <> '')):
if (BestPurchPrice."Direct Unit Cost" = 0) or
(CalcLineAmount(BestPurchPrice) > CalcLineAmount(PurchPrice))
then begin
BestPurchPrice := PurchPrice;
BestPurchPriceFound := true;
end;
end;
end;
until PurchPrice.Next() = 0;
// ... remainder of procedure unchanged
end;
New event declaration:
[IntegrationEvent(false, false)]
local procedure OnCalcBestDirectUnitCostOnBeforeCaseStatement(var PurchPrice: Record "Purchase Price"; var BestPurchPrice: Record "Purchase Price"; var BestPurchPriceFound: Boolean; var IsHandled: Boolean; LineDiscPerCent: Decimal)
begin
end;
Subscriber example:
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch. Price Calc. Mgt.", 'OnCalcBestDirectUnitCostOnBeforeCaseStatement', '', false, false)]
local procedure OnBeforeCaseStatementHandler(var PurchPrice: Record "Purchase Price"; var BestPurchPrice: Record "Purchase Price"; var BestPurchPriceFound: Boolean; var IsHandled: Boolean; LineDiscPerCent: Decimal)
var
BestEffectiveAmt: Decimal;
CurrEffectiveAmt: Decimal;
begin
// "EXT Best Price" is a custom setup flag. When off, select by
// minimum-quantity tier and starting date instead of lowest cost.
// When on, replicate the standard cost comparison using LineDiscPerCent
// since CalcLineAmount is local and not accessible here.
if not GetEXTBestPriceActive() then begin
if (BestPurchPrice."Direct Unit Cost" = 0) or
(BestPurchPrice."Minimum Quantity" <= PurchPrice."Minimum Quantity") or
(BestPurchPrice."Starting Date" <= PurchPrice."Starting Date")
then begin
BestPurchPrice := PurchPrice;
BestPurchPriceFound := true;
end;
IsHandled := true;
end else begin
BestEffectiveAmt := BestPurchPrice."Direct Unit Cost" * (1 - LineDiscPerCent / 100);
CurrEffectiveAmt := PurchPrice."Direct Unit Cost" * (1 - LineDiscPerCent / 100);
if (BestPurchPrice."Direct Unit Cost" = 0) or (BestEffectiveAmt > CurrEffectiveAmt) then begin
BestPurchPrice := PurchPrice;
BestPurchPriceFound := true;
end;
IsHandled := true;
end;
end;
3. Event Request: Extend OnCalcBestDirectUnitCostOnBeforeNoPriceFound with PriceInSKU and SKU Parameters
[IntegrationEvent(false, false)]
local procedure OnCalcBestDirectUnitCostOnBeforeNoPriceFound(var BestPurchPrice: Record "Purchase Price"; Item: Record Item; var IsHandled: Boolean)
begin
end;
Updated event declaration:
[IntegrationEvent(false, false)]
local procedure OnCalcBestDirectUnitCostOnBeforeNoPriceFound(var BestPurchPrice: Record "Purchase Price"; Item: Record Item; var IsHandled: Boolean; var PriceInSKU: Boolean; var SKU: Record "Stockkeeping Unit") //>> PriceInSKU and SKU are new parameters
begin
end;
Requested change in CalcBestDirectUnitCost (call site only - no structural change):
// No price found in agreement
if not BestPurchPriceFound then begin
IsHandled := false;
//>> code change start
OnCalcBestDirectUnitCostOnBeforeNoPriceFound(BestPurchPrice, Item, IsHandled, PriceInSKU, SKU);
//<< code change end
if not IsHandled then begin
PriceInSKU := PriceInSKU and (SKU."Last Direct Cost" <> 0);
if PriceInSKU then
BestPurchPrice."Direct Unit Cost" := SKU."Last Direct Cost"
else
BestPurchPrice."Direct Unit Cost" := Item."Last Direct Cost";
end;
ConvertPriceToVAT(false, Item."VAT Prod. Posting Group", '', BestPurchPrice."Direct Unit Cost");
ConvertPriceToUoM('', BestPurchPrice."Direct Unit Cost");
ConvertPriceLCYToFCY('', BestPurchPrice."Direct Unit Cost");
OnCalcBestDirectUnitCostOnAfterSetUnitCost(BestPurchPrice);
end;
Subscriber example:
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch. Price Calc. Mgt.", 'OnCalcBestDirectUnitCostOnBeforeNoPriceFound', '', false, false)]
local procedure OnBeforeNoPriceFoundHandler(var BestPurchPrice: Record "Purchase Price"; Item: Record Item; var IsHandled: Boolean; var PriceInSKU: Boolean; var SKU: Record "Stockkeeping Unit")
var
EXTPurchSetup: Record "EXT Purchases Setup";
begin
PriceInSKU := PriceInSKU and (SKU."Last Direct Cost" <> 0);
if PriceInSKU then
// SKU has a last direct cost - use it as usual.
BestPurchPrice."Direct Unit Cost" := SKU."Last Direct Cost"
else begin
EXTPurchSetup.Get();
if EXTPurchSetup."Use Item Last Direct Cost" then
// Setup allows falling back to the item's last direct cost.
BestPurchPrice."Direct Unit Cost" := Item."Last Direct Cost"
else
// Setup does not allow the fallback - force cost to 0 so
// the buyer is required to enter the price manually.
BestPurchPrice."Direct Unit Cost" := 0;
end;
IsHandled := true;
end;
Why do you need this change?
1. Event Request:
OnCalcBestDirectUnitCostOnAfterIsInMinQtyPlease add a new integration event in the
CalcBestDirectUnitCostprocedure of codeunit 7010 "Purch. Price Calc. Mgt.", immediately after theIsInMinQty(...)call inside the price iteration loop, exposing the result as avarparameter that subscribers can override.IsInMinQtychecks whether the currentPurchase Pricerecord's minimum quantity condition is met before it is considered as a price candidate. The standard check comparesMinQtyagainst eitherQtyPerUOM * Qtyor plainQtydepending on whether a unit of measure code is set.In some business setups, the quantity that should be compared against the minimum is not the individual purchase line quantity but the total quantity across all lines in the document. Whether to use the line quantity or the order total depends on a field stored on the
Purchase Pricerecord itself.The existing
OnBeforeIsInMinQtyevent does not pass thePurchPricerecord - it only receivesUnitofMeasureCodeandMinQty. Because of this, a subscriber cannot read fields on the price record and cannot decide which quantity to compare against. The new event needs to fire inside theCalcBestDirectUnitCostloop where the fullPurchPricerecord is available, and must expose the result ofIsInMinQtyas avar Booleanso subscribers can override it.Alternatives Evaluated
The following existing events were evaluated:
OnBeforeIsInMinQty(Item, UnitofMeasureCode, MinQty, Result, IsHandled): Does not pass thePurchPricerecord. A subscriber cannot read per-price-line fields and therefore cannot decide which quantity reference to use. This event cannot be used.OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item): Fires once before the loop starts. A subscriber settingIsHandled := truewould need to re-implement the entire price selection loop. This event cannot be used.OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT(PurchPrice): Fires afterIsInMinQtyhas already returnedtrueand execution is already inside theif IsInMinQty then beginblock. It cannot intercept afalseresult and cannot affect which records enter the candidate set. This event cannot be used.SetUoM(Qty2, QtyPerUoM2): A subscriber could callSetUoMbeforeCalcBestDirectUnitCostruns (for example viaOnBeforeCalcBestDirectUnitCost) to set a different quantity - such as the order total - thatIsInMinQtywould then use. This works when all price records for the item should use the same quantity reference. It cannot be used when different price records in the same loop require different quantity references, becauseSetUoMsets a single global value that applies to every iteration. This event cannot be used.None of the existing approaches allow a subscriber to change the quantity reference individually per price record based on a field on the
PurchPricerow.Performance Considerations
The event fires once per record in the
Purchase Pricetemp table insideCalcBestDirectUnitCost. The number of price records per item/vendor/date is typically small. The overhead is the same as the existingOnCalcBestDirectUnitCostOnBeforeConvertPriceToVATevent, which already fires in the same loop. When no subscriber changesInMinQty, execution follows the standardIsInMinQtyresult unchanged.Data Sensitivity Review
The event parameters expose:
var PurchPrice: Record "Purchase Price"- the record currently being evaluated; already in scope in the same loop iteration.QtyPerUOM: Decimal- already used internally byIsInMinQty.Qty: Decimal- already used internally byIsInMinQty.var InMinQty: Boolean- the result of theIsInMinQtycall; a Boolean with no sensitive data. Passed asvarso subscribers can change it.No data is exposed beyond what is already available inside
CalcBestDirectUnitCostat this point. NoIsHandledparameter is needed becauseIsInMinQtyis a small local function with no side effects - there is no cost to always calling it first and letting the event override the result afterward.Multi-Extension Interaction
Multiple extensions can subscribe to this event at the same time. If multiple subscribers change
InMinQty, the last subscriber to run determines the final value. When no subscriber is active,InMinQtykeeps the value returned byIsInMinQtyand behaviour is unchanged.2. Event Request:
OnCalcBestDirectUnitCostOnBeforeCaseStatementPlease add a new integration event with an
IsHandledparameter in theCalcBestDirectUnitCostprocedure of codeunit 7010 "Purch. Price Calc. Mgt.", immediately after the three price conversions (ConvertPriceToVAT,ConvertPriceToUoM,ConvertPriceLCYToFCY) and before thecase true ofblock that decides whether the current price replaces the current best price.The
case true ofblock applies two rules:Some business setups need a different rule for the cost comparison step. For example, selecting the price from the highest applicable minimum-quantity tier rather than the lowest cost, or preferring the most recently started price as a tiebreaker. These strategies cannot be implemented by subscribing to any existing event because no event exposes both the current candidate price (
PurchPrice, already converted) and the current best price (BestPurchPrice) simultaneously - at a point where the selection decision can still be changed.Alternatives Evaluated
The following existing events were evaluated:
OnCalcBestDirectUnitCostOnBeforeConvertPriceToVAT(PurchPrice): Fires before the three price conversions. The prices have not yet been normalised to the same VAT, unit of measure and currency basis, so comparing them is not meaningful.BestPurchPriceis also not accessible here. This event cannot be used.OnAfterCalcBestDirectUnitCostFound(PurchPrice, BestPurchPriceFound, IsHandled): Fires after the entire loop has finished. The selection is already final andBestPurchPriceis not passed, so it is not possible to change which price was chosen. This event cannot be used.OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item): A subscriber settingIsHandled := truemust re-implement the complete loop including all conversion calls and selection logic. This event cannot be used.An event after the
case true ofblock: Considered as a simpler alternative - the standard rules would always run and the subscriber could override the result afterward. However, this has a critical edge case: when the standard second rule determines the current candidate is better and setsBestPurchPrice := PurchPrice, the event fires withBestPurchPriceandPurchPricepointing to the same record. The subscriber has lost the previous best price and cannot restore it. For use cases that need to replace the cost comparison with a different criterion (e.g. pick by minimum-quantity tier), the subscriber must compare the old best against the current candidate - which is only possible before thecase true ofruns.None of the existing events allow a subscriber to compare the unconverted current candidate against the current best price before the selection is made.
Performance Considerations
The event fires once per price record that passes the min-quantity check, inside the
CalcBestDirectUnitCostloop. The overhead is the same as the existingOnCalcBestDirectUnitCostOnBeforeConvertPriceToVATevent, which already fires at the same frequency in the same loop. When no subscriber setsIsHandled := true, the standardcase true oflogic runs unchanged.Data Sensitivity Review
The event parameters expose:
var PurchPrice: Record "Purchase Price"- the current candidate record, already converted; already in scope at this point.var BestPurchPrice: Record "Purchase Price"- the current best price found so far; a temporary record local toCalcBestDirectUnitCost. Passed asvarso subscribers can assign to it when they select a different winner.var BestPurchPriceFound: Boolean- must bevarso a subscriber that replaces thecase true ofblock can also mark that a selection was made.var IsHandled: Boolean- standard skip flag.LineDiscPerCent: Decimal- the line discount percentage set viaSetLineDiscbefore the loop began. Required so subscribers can replicate the effective-amount calculation used by the standard second rule:"Direct Unit Cost" * (1 - LineDiscPerCent / 100). The localCalcLineAmountprocedure that performs this calculation is not accessible to subscribers.All values are already in scope within the procedure at this point.
BestPurchPriceis a temporary record that is not persisted. No sensitive data is introduced.Multi-Extension Interaction
Multiple extensions can subscribe at the same time. The
IsHandledpattern is standard across codeunit 7010. If multiple subscribers setIsHandled := true, each one runs in sequence and the last value written toBestPurchPriceis used. Extensions that only need to readBestPurchPriceas context without replacing the standard logic should leaveIsHandled := false.3. Event Request: Extend
OnCalcBestDirectUnitCostOnBeforeNoPriceFoundwithPriceInSKUandSKUParametersPlease extend the existing integration event
OnCalcBestDirectUnitCostOnBeforeNoPriceFoundin codeunit 7010 "Purch. Price Calc. Mgt." to includevar PriceInSKU: Booleanandvar SKU: Record "Stockkeeping Unit"as additional parameters.When no purchase price is found for an item/vendor combination,
CalcBestDirectUnitCostfalls back to eitherSKU."Last Direct Cost"orItem."Last Direct Cost", depending on whetherPriceInSKUevaluates totrue. Some setups need to intercept this fallback - for example, to set the cost to0instead of using the item's last direct cost when a setup flag says that falling back to item cost is not allowed.The existing event fires before
PriceInSKUis recalculated on the linePriceInSKU := PriceInSKU and (SKU."Last Direct Cost" <> 0). A subscriber that setsIsHandled := trueneeds to make the samePriceInSKUevaluation itself to decide whether to use the SKU cost or apply custom logic. Without the current value ofPriceInSKUand access toSKU."Last Direct Cost", the subscriber cannot make this decision correctly and is forced to duplicate internal logic or hardcode assumptions.Adding
PriceInSKUandSKUasvarparameters gives subscribers the information they need without having to duplicate the internal state.Alternatives Evaluated
The following existing events were evaluated:
OnCalcBestDirectUnitCostOnAfterSetUnitCost(var BestPurchPrice: Record "Purchase Price"): Fires after the fallback cost has been assigned and all three conversion procedures (ConvertPriceToVAT,ConvertPriceToUoM,ConvertPriceLCYToFCY) have already run against it. Changing the cost here does not undo those conversions. A subscriber cannot cleanly set the cost to0at this point. This event cannot be used.OnBeforeCalcBestDirectUnitCost(PurchPrice, BestPurchPrice, BestPurchPriceFound, IsHandled, SKU, Item): A subscriber settingIsHandled := truemust re-implement the complete price selection loop, all min-quantity checks, all conversions, and the fallback logic. This is not a viable targeted intercept for a change that affects only the fallback branch.Adding a new second event after the
PriceInSKUupdate line: This would work technically, but adds an extra event for a case that the existingOnCalcBestDirectUnitCostOnBeforeNoPriceFoundalready targets. Extending the existing event's parameters is simpler and less disruptive.None of the existing events allow a subscriber to intercept the fallback cost assignment with full knowledge of what
PriceInSKUwill be.Performance Considerations
The event fires at most once per
CalcBestDirectUnitCostcall, and only when no price was found (not BestPurchPriceFound). When purchase prices are configured for the item/vendor, this branch is never reached. Passing two additional parameters (BooleanandRecord) to an existing event has no measurable impact. When a price is found, theif not BestPurchPriceFound thenblock is never entered and the event never fires..Data Sensitivity Review
The two new parameters expose:
var PriceInSKU: Boolean- a computed flag indicating whether a stockkeeping unit with a non-zero last direct cost was matched. No sensitive data. Passed asvarso a subscriber can update it if needed before the standardif not IsHandled thenblock runs.var SKU: Record "Stockkeeping Unit"- the matched SKU record (if any). Already exposed byOnBeforeCalcBestDirectUnitCostin the same codeunit, setting the same precedent. Contains standard SKU fields including"Last Direct Cost".Both values are already in scope within
CalcBestDirectUnitCostat the event's insertion point and are used in the lines immediately following the event call. No sensitive data is introduced beyond what the procedure already handles.Multi-Extension Interaction
The
IsHandledpattern is unchanged. If multiple subscribers setIsHandled := true, each runs in sequence and the last value written toBestPurchPrice."Direct Unit Cost"is used. BecausePriceInSKUisvar, one subscriber's update to it is visible to subsequent subscribers in the same event firing, which is consistent with howvarparameters work for integration events in Business Central.Describe the request
Requested changes in
CalcBestDirectUnitCost:OnCalcBestDirectUnitCostOnAfterIsInMinQtyNew event declaration:
Subscriber example:
2. Event Request:
OnCalcBestDirectUnitCostOnBeforeCaseStatementNew event declaration:
Subscriber example:
3. Event Request: Extend
OnCalcBestDirectUnitCostOnBeforeNoPriceFoundwithPriceInSKUandSKUParametersUpdated event declaration:
Requested change in
CalcBestDirectUnitCost(call site only - no structural change):Subscriber example: