Warehouse work completed
Fires when a warehouse work order is completed: its work status transitions to Closed, or it's created already closed (for example, a movement, adjustment, or quarantine move that is executed at once on the mobile device). This is the moment the physical work (picking, putaway, replenishment, counting, etc.) tracked by a WHSWorkTable record finishes, allowing downstream systems to react to the closing of warehouse work and the fulfillment progress it represents for the associated load, shipment, and order.
| Event class | MGCLatticeWarehouseWorkCompletedBusinessEvent |
| Contract class | MGCLatticeWarehouseWorkCompletedBusinessEventContract |
| Category | Warehouse management |
| Module | ModuleAxapta::Warehouse |
| Label IDs | WarehouseWorkCompletedBusinessEventName / WarehouseWorkCompletedBusinessEventDescription |
| Trigger | WHSWorkTable.update() -> WorkStatus changes to WHSWorkStatus::Closed, or WHSWorkTable.insert() of work that is already Closed |
Trigger / Hook
- Extension class:
MGCLatticeWHSWorkTable_Table_Extension, decorated with[ExtensionOf(tableStr(WHSWorkTable))], with two Chain of Command wraps:public void update(): the original record'sWorkStatusis captured viathis.orig().WorkStatusbefore callingnext update(). After the update, the event fires only when the status actually transitioned (origStatus != this.WorkStatus) and the new status isWHSWorkStatus::Closed. This guard prevents re-firing on resaves where the status was already closed.public void insert(): afternext insert(), the event fires when the new work's status is alreadyWHSWorkStatus::Closed. The standard application creates work this way when the work is executed at the moment it's created, for example movements, inventory adjustments, and quarantine moves done on the mobile device. Work that is inserted closed and later saved again doesn't fire a second time, because its status doesn't change.
- Not raised: when work is closed by a set-based update, or created closed by a set-based insert (
update_recordset,doUpdate,doInsert), because those bypass the table methods. - Transaction: the event is sent inside the work-closing (or work-creating) transaction; it's written to the business events commit log and delivered only if that transaction commits.
- Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeWarehouseWorkCompletedBusinessEvent))must return true before the event is constructed and sent. - The event is built and dispatched via
MGCLatticeWarehouseWorkCompletedBusinessEvent::newFromWHSWorkTable(this).send(). The sameupdate()wrapper then raises Warehouse Picking Completed when the closed work is order picking work with completed Pick lines; each event is activated independently. Theinsert()wrapper raises only this event.
public void update()
{
WHSWorkStatus origStatus = this.orig().WorkStatus;
next update();
if (origStatus != this.WorkStatus && this.WorkStatus == WHSWorkStatus::Closed)
{
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeWarehouseWorkCompletedBusinessEvent)))
{
MGCLatticeWarehouseWorkCompletedBusinessEvent::newFromWHSWorkTable(this).send();
}
// ... Warehouse Picking Completed is sent here for order picking work ...
}
}
public void insert()
{
next insert();
if (this.WorkStatus == WHSWorkStatus::Closed
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeWarehouseWorkCompletedBusinessEvent)))
{
MGCLatticeWarehouseWorkCompletedBusinessEvent::newFromWHSWorkTable(this).send();
}
}
Payload Contract
All fields are populated in MGCLatticeWarehouseWorkCompletedBusinessEventContract::initialize(WHSWorkTable _whsWorkTable).
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | _whsWorkTable.DataAreaId | Company/legal entity of the work record. |
workId | Work ID | WHSWorkId | _whsWorkTable.WorkId | The warehouse work order number. |
workStatus | Work Status | int | enum2int(_whsWorkTable.WorkStatus) | Integer value of the WHSWorkStatus enum; 4 (Closed) for this event. |
workStatusName | Work Status Name | str | enum2Symbol(enumNum(WHSWorkStatus), enum2int(_whsWorkTable.WorkStatus)) | Enum symbol name for WHSWorkStatus (e.g. Closed). |
workTransType | Work Transaction Type | int | enum2int(_whsWorkTable.WorkTransType) | Integer value of the WHSWorkTransType enum (e.g. 1 = Purch, 2 = Sales, 5 = TransferIssue, 11 = Replenishment). |
workTransTypeName | Work Transaction Type Name | str | enum2Symbol(enumNum(WHSWorkTransType), enum2int(_whsWorkTable.WorkTransType)) | Enum symbol name for WHSWorkTransType (e.g. Sales). |
loadId | Load ID | WHSLoadId | _whsWorkTable.LoadId | Associated load, if any. |
shipmentId | Shipment ID | WHSShipmentId | _whsWorkTable.ShipmentId | Associated shipment, if any. |
orderNumber | Order Number | WHSOrderNum | _whsWorkTable.OrderNum | Source order number that generated the work. |
warehouseId | Warehouse ID | InventLocationId | _whsWorkTable.InventLocationId | Warehouse (inventory location) of the work. |
siteId | Site ID | InventSiteId | _whsWorkTable.InventSiteId | Site of the work. |
workClosedDateTime | Work Closed Date Time | WHSWorkClosedUTCDateTime | _whsWorkTable.WorkClosedUTCDateTime | UTC datetime the work was closed, sent in the "\/Date(milliseconds)\/" form. |
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
{
"BusinessEventId": "MGCLatticeWarehouseWorkCompletedBusinessEvent",
"ControlNumber": 549872,
"EventId": "1F6C2D4E-8B3A-4C1D-9F2E-7A5B3C9D0E11",
"EventTime": "\/Date(1790268112000)\/",
"EventTimeIso8601": "2026-09-24T16:41:52.6978754Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{44769660-5ACE-D45D-DBDA-FECDA990F474}",
"ParentContextRecordSubjects": [],
"eventSequence": 48518,
"correlationId": "Event:f4c148c9-4184-498a-8c8f-7d95941efca3",
"schemaVersion": 1,
"legalEntity": "usmf",
"workId": "WHS-000482",
"workStatus": 4,
"workStatusName": "Closed",
"workTransType": 2,
"workTransTypeName": "Sales",
"loadId": "LD-000137",
"shipmentId": "SH-000291",
"orderNumber": "SO-000418",
"warehouseId": "24",
"siteId": "2",
"workClosedDateTime": "\/Date(1790268110000)\/"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Warehouse work completed") or by filtering the Category column to Warehouse 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) so payloads are dispatched when warehouse work is closed.
Related Events
- Warehouse Picking Completed - order-picking event raised from the same
WHSWorkTable.update()hook, with each completed pick line's item, quantity and operator. Both events fire for the same closed picking work, so a subscriber that activates both receives two events for it: this one for every closed work, the picking event only for order picking work. - Warehouse Load Shipped - downstream load fulfillment for the same warehouse flow.