Skip to main content

BOM calculation completed

Fires when an item price calculation (BOM calculation) finishes for an item and saves the calculation result header (BOMCalcTable). This is the moment a costing run produces a calculated cost for the item. Pricing, margin-analysis and planning systems can use it to pick up the new rolled-up cost as soon as it is calculated instead of polling the calculation results.

Event classMGCLatticeBOMCalcCompletedBusinessEvent
Contract classMGCLatticeBOMCalcCompletedBusinessEventContract
CategoryProduction control
ModuleModuleAxapta::ProductionControl
Label IDsBOMCalcCompletedBusinessEventName / BOMCalcCompletedBusinessEventDescription
TriggerBomCalcItem.insertBOMCalcTable() -> after the top-level calculation result (BOMCalcTable) is inserted or updated (RecId guard)

Trigger / Hook​

  • Extension class: MGCLatticeBomCalcItem_Class_Extension, decorated with [ExtensionOf(classStr(BomCalcItem))].
  • CoC method signature: public void insertBOMCalcTable() (no parameters). This is the calculation step that saves the result header, not the BOMCalcTable.insert() table write.
  • Firing condition: after next insertBOMCalcTable(), the class's bomCalcTable buffer is read and the event is sent when its RecId is non-zero (a result row was actually written). The standard method inserts a new BOMCalcTable row, or updates the existing row when the buffer already has a RecId. Both cases send the event.
  • Scope: BomCalcItem.calc() calls insertBOMCalcTable() only for the top level of the calculation (level == 0), so the event is sent once per calculated item, not once per BOM line or sub-level. PurchCalcItem, the calculation variant used for items whose procurement mode is purchase, inherits the method, so calculations for purchased items raise the event too.
  • Transaction: the item price calculation job (BOMCalcJob.runBOMCalculation()) runs the calculation inside a transaction. BusinessEventsBase.send() only writes to the commit log inside that transaction and delivery is asynchronous post-commit, so a calculation that rolls back never delivers.
  • Correlation: buildContract() calls correlateTo(MGCLatticeBusinessEventRunCorrelation::currentRunCorrelation('BOMCalc', int642Str(bomCalcTable.RecId))). When the calculation runs in batch, correlationId is BatchTask:<RecId of the batch task>, so every calculation performed by the same batch task shares one correlation ID. When it runs interactively, correlationId is BOMCalc:<RecId of the BOMCalcTable row>.
  • Enablement gate: guarded by BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeBOMCalcCompletedBusinessEvent)), so the event only sends when activated in the business events catalog.
public void insertBOMCalcTable()
{
BOMCalcTable bomCalcTableLocal;

next insertBOMCalcTable();

bomCalcTableLocal = this.bomCalcTable;

if (bomCalcTableLocal.RecId)
{
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeBOMCalcCompletedBusinessEvent)))
{
MGCLatticeBOMCalcCompletedBusinessEvent::newFromBOMCalcTable(bomCalcTableLocal).send();
}
}
}

Payload Contract​

One row per [DataMember], in declaration order. All sources are fields on the BOMCalcTable result row, mapped in initialize().

JSON fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdBOMCalcTable.DataAreaIdCompany of the calculation result.
itemIdItem NumberItemIdBOMCalcTable.ItemIdThe calculated item.
costPriceCost PricerealBOMCalcTable.CostPriceCalculated cost for the calculation quantity. The standard calculation sets it to the per-unit cost multiplied by the quantity the item was calculated for (BOMCalcTable.Qty, which isn't in the payload), so it equals the unit cost only when that quantity is 1.
calculationDateCalculation DateTransDateBOMCalcTable.TransDateThe calculation date used for the run.

Every Lattice event also carries the Lattice ordering and correlation envelope, inherited from MGCLatticeBusinessEventContractBase: eventSequence (Int64 - increasing across the whole catalog and all companies, allocated when the event is raised inside the business transaction rather than when it commits; it orders the events of one business record, gaps are permanent and normal, and it must not be used to re-sequence the whole stream), correlationId (String, always Scope:Key - groups emissions that belong to one logical run; Event:<GUID> unique to this emission when the event belongs to no run) and schemaVersion (Int - the payload shape version; 1 for this contract). D365 F&O also adds its standard envelope fields to every business event payload: BusinessEventId, BusinessEventLegalEntity, ContextRecordSubject, ControlNumber, EventId, EventTime, EventTimeIso8601, InitiatingUserAADObjectId, MajorVersion, MinorVersion, and ParentContextRecordSubjects. Do not rely on the order of members in the delivered JSON.

Example Payload​

An interactive calculation (outside batch), so correlationId uses the BOMCalc: form.

{
"BusinessEventId": "MGCLatticeBOMCalcCompletedBusinessEvent",
"ControlNumber": 5637151841,
"EventId": "65F7401A-B519-4E9D-B651-F5173147072B",
"EventTime": "\/Date(1790260337000)\/",
"EventTimeIso8601": "2026-09-24T14:32:17.2504979Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{535F37DF-3DC8-9007-AF2D-18317DA20F65}",
"ParentContextRecordSubjects": [],
"eventSequence": 48230,
"correlationId": "BOMCalc:5637158720",
"schemaVersion": 1,
"legalEntity": "usmf",
"itemId": "D0001",
"costPrice": 302.71,
"calculationDate": "\/Date(1790208000000)\/"
}

Activation​

In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("BOM calculation completed") or by filtering the Category column to Production control. Select it and use Activate to bind it to a configured endpoint (for example, an Azure Service Bus queue, Event Grid topic, or HTTPS webhook).