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 class | MGCLatticeBOMCalcCompletedBusinessEvent |
| Contract class | MGCLatticeBOMCalcCompletedBusinessEventContract |
| Category | Production control |
| Module | ModuleAxapta::ProductionControl |
| Label IDs | BOMCalcCompletedBusinessEventName / BOMCalcCompletedBusinessEventDescription |
| Trigger | BomCalcItem.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 theBOMCalcTable.insert()table write. - Firing condition: after
next insertBOMCalcTable(), the class'sbomCalcTablebuffer is read and the event is sent when itsRecIdis non-zero (a result row was actually written). The standard method inserts a newBOMCalcTablerow, or updates the existing row when the buffer already has aRecId. Both cases send the event. - Scope:
BomCalcItem.calc()callsinsertBOMCalcTable()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()callscorrelateTo(MGCLatticeBusinessEventRunCorrelation::currentRunCorrelation('BOMCalc', int642Str(bomCalcTable.RecId))). When the calculation runs in batch,correlationIdisBatchTask:<RecId of the batch task>, so every calculation performed by the same batch task shares one correlation ID. When it runs interactively,correlationIdisBOMCalc:<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 field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | BOMCalcTable.DataAreaId | Company of the calculation result. |
itemId | Item Number | ItemId | BOMCalcTable.ItemId | The calculated item. |
costPrice | Cost Price | real | BOMCalcTable.CostPrice | Calculated 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. |
calculationDate | Calculation Date | TransDate | BOMCalcTable.TransDate | The 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).