Inventory closing completed
Fires when an inventory close or an inventory recalculation has finished for a legal entity (Inventory management > Periodic tasks > Closing and adjustment): settlement and cost adjustment are done, the adjustments are posted to the ledger, and the closing record's status is Ended. The payload says which of the two ran, with its date and voucher. Downstream finance, costing, and reporting systems can use it as the signal that costed inventory values for that date are final and can be read or reconciled.
| Event class | MGCLatticeInventClosingCompletedBusinessEvent |
| Contract class | MGCLatticeInventClosingCompletedBusinessEventContract |
| Category | Inventory management |
| Module | ModuleAxapta::Inventory |
| Label IDs | InventClosingCompletedBusinessEventName / InventClosingCompletedBusinessEventDescription |
| Trigger | InventCostLedgerPostingTask.updateLedgerPosting() -> after the ledger posting stage moves the closing record to Ended (inventory close and recalculation) |
Trigger / Hook
- Extension class:
MGCLatticeInventCostLedgerPostingTask_Class_Extension,[ExtensionOf(classStr(InventCostLedgerPostingTask))]. - CoC method signature:
protected void updateLedgerPosting(). - Why this point: an inventory close always runs in batch, and in batch the job's
InventCostClosing.run()only creates the closing record and schedules the calculation tasks. Every close and recalculation, including one that is resumed after a stop, ends with the ledger posting task. ItsupdateLedgerPosting()posts the adjustments and production variances, sets the closing record'sInventCostStatustoEnded, and commits. - Firing condition: when the event is activated, the extension reads the closing record's status (found by the task's voucher and date) before
next updateLedgerPosting(), and reads the record again afterwards. The event is sent only when:- the record's status was not
Endedbefore and isEndedafter, so a retried task that finds the closing already ended sends nothing; - the record isn't a cancellation of an earlier close (
Cancellationis No); cancellations end through a different process and never send this event; - the adjustment type is
ClosingorRecalculation.
- the record's status was not
- One event per completed run. When a close is run with "Run recalculation" selected, the recalculation that follows is a separate run and sends its own event.
- Transaction: the event is sent after the ledger posting stage has committed, in the ledger posting batch task.
- Correlation:
correlationIdisInventClose:<company>:<voucher>, for exampleInventClose:usmf:IC-000118, so it identifies the close or recalculation run. - Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeInventClosingCompletedBusinessEvent)), checked before the closing record is read.
protected void updateLedgerPosting()
{
boolean eventEnabled = BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeInventClosingCompletedBusinessEvent));
InventCostStatus statusBefore;
if (eventEnabled)
{
statusBefore = InventClosing::find(this.parmVoucher(), this.parmTransDate()).InventCostStatus;
}
next updateLedgerPosting();
if (eventEnabled)
{
InventClosing inventClosingAfter = InventClosing::find(this.parmVoucher(), this.parmTransDate());
if (inventClosingAfter.RecId
&& MGCLatticeInventClosingCompletedBusinessEvent::isCompletionTransition(
statusBefore,
inventClosingAfter.InventCostStatus,
inventClosingAfter.Cancellation,
inventClosingAfter.AdjustmentType))
{
MGCLatticeInventClosingCompletedBusinessEvent::newFromInventClosing(inventClosingAfter).send();
}
}
}
Payload Contract
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
| legalEntity | Legal Entity | DataAreaId | InventClosing.DataAreaId | Company of the close or recalculation |
| closingDate | Closing Date | TransDate | InventClosing.TransDate | The date the inventory was closed or recalculated to. A date, sent as midnight UTC in the "\/Date(milliseconds)\/" form |
| adjustmentType | Adjustment Type | int | enum2int(InventClosing.AdjustmentType) | Which run completed, as the InventAdjustmentType integer: 2 = Closing for an inventory close, 3 = Recalculation for an inventory recalculation |
| adjustmentTypeName | Adjustment Type Name | str | enum2Symbol(enumNum(InventAdjustmentType), adjustmentType) | Enum symbol: Closing or Recalculation |
| voucher | Voucher | Voucher | InventClosing.Voucher | Voucher of the close or recalculation, as shown on the Closing and adjustment page |
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.
For this event, correlationId is the run correlation described under Trigger / Hook rather than a per-emission value.
Example Payload
{
"BusinessEventId": "MGCLatticeInventClosingCompletedBusinessEvent",
"ControlNumber": 587412,
"EventId": "1F9B6C2E-7A4D-4C1E-9B3A-2D5E8F0A1C44",
"EventTime": "\/Date(1790301607000)\/",
"EventTimeIso8601": "2026-09-25T02:00:07.6183971Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{B53C77CC-A09E-6083-5B6E-337CB03EAA7E}",
"ParentContextRecordSubjects": [],
"eventSequence": 48590,
"correlationId": "InventClose:usmf:IC-000118",
"schemaVersion": 1,
"legalEntity": "usmf",
"closingDate": "\/Date(1788134400000)\/",
"adjustmentType": 2,
"adjustmentTypeName": "Closing",
"voucher": "IC-000118"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Inventory closing completed") or by filtering the Category column to Inventory management. 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).
No other setup is needed; the event is raised when an inventory close or recalculation started from Closing and adjustment finishes.