Skip to main content

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 classMGCLatticeWarehouseWorkCompletedBusinessEvent
Contract classMGCLatticeWarehouseWorkCompletedBusinessEventContract
CategoryWarehouse management
ModuleModuleAxapta::Warehouse
Label IDsWarehouseWorkCompletedBusinessEventName / WarehouseWorkCompletedBusinessEventDescription
TriggerWHSWorkTable.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's WorkStatus is captured via this.orig().WorkStatus before calling next update(). After the update, the event fires only when the status actually transitioned (origStatus != this.WorkStatus) and the new status is WHSWorkStatus::Closed. This guard prevents re-firing on resaves where the status was already closed.
    • public void insert(): after next insert(), the event fires when the new work's status is already WHSWorkStatus::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 same update() wrapper then raises Warehouse Picking Completed when the closed work is order picking work with completed Pick lines; each event is activated independently. The insert() 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 fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaId_whsWorkTable.DataAreaIdCompany/legal entity of the work record.
workIdWork IDWHSWorkId_whsWorkTable.WorkIdThe warehouse work order number.
workStatusWork Statusintenum2int(_whsWorkTable.WorkStatus)Integer value of the WHSWorkStatus enum; 4 (Closed) for this event.
workStatusNameWork Status Namestrenum2Symbol(enumNum(WHSWorkStatus), enum2int(_whsWorkTable.WorkStatus))Enum symbol name for WHSWorkStatus (e.g. Closed).
workTransTypeWork Transaction Typeintenum2int(_whsWorkTable.WorkTransType)Integer value of the WHSWorkTransType enum (e.g. 1 = Purch, 2 = Sales, 5 = TransferIssue, 11 = Replenishment).
workTransTypeNameWork Transaction Type Namestrenum2Symbol(enumNum(WHSWorkTransType), enum2int(_whsWorkTable.WorkTransType))Enum symbol name for WHSWorkTransType (e.g. Sales).
loadIdLoad IDWHSLoadId_whsWorkTable.LoadIdAssociated load, if any.
shipmentIdShipment IDWHSShipmentId_whsWorkTable.ShipmentIdAssociated shipment, if any.
orderNumberOrder NumberWHSOrderNum_whsWorkTable.OrderNumSource order number that generated the work.
warehouseIdWarehouse IDInventLocationId_whsWorkTable.InventLocationIdWarehouse (inventory location) of the work.
siteIdSite IDInventSiteId_whsWorkTable.InventSiteIdSite of the work.
workClosedDateTimeWork Closed Date TimeWHSWorkClosedUTCDateTime_whsWorkTable.WorkClosedUTCDateTimeUTC 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.

  • 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.