Maintenance work order created
Fires once for every new Asset Management maintenance work order, when the work order has been created: D365 creates a work order in several steps (the header, its work order lines, then the first lifecycle state of its lifecycle model), and the event is raised as the transaction that creates the work order commits, so the payload describes the finished work order rather than a bare header row. Downstream systems can use it to open the job in a field-service, mobile-workforce or CMMS tool, alert maintenance planners, or start tracking cost and downtime for the new work order. Use the Scheduled, Started and Completed events for the rest of the work order's life.
| Event class | MGCLatticeMaintenanceWorkOrderCreatedBusinessEvent |
| Contract class | MGCLatticeMaintenanceWorkOrderCreatedBusinessEventContract |
| Category | Asset management |
| Module | ModuleAxapta::AssetManagement |
| Label IDs | MaintenanceWorkOrderCreatedBusinessEventName / MaintenanceWorkOrderCreatedBusinessEventDescription |
| Trigger | EntAssetWorkOrderTable Inserted data event queues the work order; the event is raised when the creating transaction commits (SysTransactionScope.ttsCommitting) |
Trigger / Hook
- Handler class:
MGCLatticeMaintenanceWorkOrderEventHandlers. The methodEntAssetWorkOrderTable_onInsertedis a table data-event handler decorated with[DataEventHandler(tableStr(EntAssetWorkOrderTable), DataEventType::Inserted)]; it does not send the event itself but queues the work order in the current transaction. - Method signature:
public static void EntAssetWorkOrderTable_onInserted(Common _sender, DataEventArgs _e). - Firing condition: every insert of an
EntAssetWorkOrderTablerecord, whatever creates it (the create work order dialog, a work order created from a maintenance request or a maintenance schedule, a related work order, a copied work order, a data import). There is no status or type filter, and the header is inserted once per work order, so the event is raised exactly once per work order. - When it is raised: just before the outermost transaction that inserted the header commits. The queued work order is read again at that moment and the payload is built from it, so everything the creating process did in that transaction is included: the work order lines (and with them the asset list, the functional location and the responsible worker D365 derives from the lines) and the first lifecycle state. The standard create work order dialog, maintenance schedule work order creation and work order copy create the header, the lines and the first state in one transaction.
- Work orders whose lines are added later: when the header is saved in a transaction of its own and the lines are added afterwards (for example a data import that writes the header and the lines as separate records), the event describes the work order as it was when the header was committed:
assetIdsis[]andfunctionalLocationis empty. Lines added later do not raise the event again. - Fields that are empty at creation by design:
scheduledStartDateTime,scheduledEndDateTimeandscheduledWorkerPersonnelNumber,scheduledWorkerNameare set by work order scheduling, andactualStartDateTimeandactualEndDateTimeby the Started and Completed lifecycle states, so they are normally empty here; the planned window of a new work order is inexpectedStartDateTimeandexpectedEndDateTime. - The first lifecycle state a work order receives while it is created belongs to the creation: it does not raise the Scheduled, Started or Completed event, even if that state has Set actual start or Set actual end enabled or is the lifecycle model's scheduled state.
- Transactions: a work order creation that rolls back raises nothing, and a work order inserted and deleted again in the same transaction raises nothing.
send()writes the event to the business events commit log inside the committing transaction, and it is delivered after commit. - Enablement gate: guarded by
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeMaintenanceWorkOrderCreatedBusinessEvent))both when the work order is queued and when the event is raised, so the event only sends when activated in the business events catalog. - No Lattice parameters, filters or batch jobs are involved.
[DataEventHandler(tableStr(EntAssetWorkOrderTable), DataEventType::Inserted)]
public static void EntAssetWorkOrderTable_onInserted(Common _sender, DataEventArgs _e)
{
EntAssetWorkOrderTable workOrderTable = _sender as EntAssetWorkOrderTable;
if (workOrderTable.RecId
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeMaintenanceWorkOrderCreatedBusinessEvent)))
{
// Queued in the transaction scope; raisePendingCreatedEvents() raises it on ttsCommitting.
MGCLatticeMaintenanceWorkOrderEventHandlers::queueCreatedEvent(workOrderTable.DataAreaId, workOrderTable.RecId);
}
}
private static void raiseCreatedEvent(DataAreaId _dataAreaId, RecId _workOrderRecId)
{
changecompany(_dataAreaId)
{
EntAssetWorkOrderTable workOrderTable = EntAssetWorkOrderTable::findRecId(_workOrderRecId);
if (workOrderTable.RecId
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeMaintenanceWorkOrderCreatedBusinessEvent)))
{
MGCLatticeMaintenanceWorkOrderCreatedBusinessEvent::newFromWorkOrderTable(workOrderTable).send();
}
}
}
Payload Contract
One row per [DataMember], in declaration order. The fields are declared on MGCLatticeMaintenanceWorkOrderBusinessEventContractBase, which the four maintenance work order events share, and are read in its initializeFromWorkOrder() from the work order header, the records it references and its work order lines.
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | EntAssetWorkOrderTable.DataAreaId | Company of the work order, in lower case |
workOrderId | Work Order | EntAssetWorkOrderID | EntAssetWorkOrderTable.WorkOrderId | Work order number |
workOrderType | Work Order Type | EntAssetWorkOrderTypeID | EntAssetWorkOrderType::findRecId(EntAssetWorkOrderTable.WorkOrderType).WorkOrderTypeId | The work order type, for example Corrective or Preventive |
description | Description | Description | EntAssetWorkOrderTable.Description | Work order description |
lifecycleState | Lifecycle State | EntAssetLifecycleStateId | EntAssetWorkOrderLifecycleState::find(EntAssetWorkOrderTable.WorkOrderLifecycleState).WorkOrderLifecycleStateId | Normally the first state of the work order type's lifecycle model (New in the USMF demo data) |
functionalLocation | Functional Location | EntAssetFunctionalLocationID | EntAssetFunctionalLocation::findRecID(EntAssetWorkOrderTable.FunctionalLocation).FunctionalLocationId | The work order's functional location, which D365 sets from the lines when they all share one functional location (at the level set in Asset management parameters); empty otherwise |
assetIds | Assets | List of EntAssetObjectID | EntAssetObjectTable.ObjectID of each EntAssetWorkOrderLine.Object, in line-number order | Every distinct asset on the work order's lines, as a JSON array of strings; [] when the work order has no lines |
responsibleWorkerPersonnelNumber | Responsible Worker Personnel Number | HcmPersonnelNumberId | HcmWorker.PersonnelNumber of the responsible maintenance worker | Empty when no responsible worker is set |
responsibleWorkerName | Responsible Worker Name | HcmWorkerName | EntAssetWorker::find(EntAssetWorkOrderTable.ResponsibleWorker).workerName() | Name of the worker responsible for the work order (from the maintenance request, or from the responsible-worker setup of the lines' assets and jobs); empty when none |
scheduledWorkerPersonnelNumber | Scheduled Worker Personnel Number | HcmPersonnelNumberId | HcmWorker.PersonnelNumber of the scheduled maintenance worker | Empty when no worker is scheduled |
scheduledWorkerName | Scheduled Worker Name | HcmWorkerName | EntAssetWorker::find(EntAssetWorkOrderTable.WorkerScheduled).workerName() | Name of the worker all the work order's jobs are scheduled to; normally empty at creation |
expectedStartDateTime | Expected Start Date Time | utcdatetime | EntAssetWorkOrderTable.ExpectedStart | "\/Date(ms)\/" form; defaults to the moment the work order was created, or comes from the service level, request or maintenance schedule |
expectedEndDateTime | Expected End Date Time | utcdatetime | EntAssetWorkOrderTable.ExpectedEnd | The empty date-time (1900-01-01T00:00:00Z, "\/Date(-2208988800000)\/") when not set |
scheduledStartDateTime | Scheduled Start Date Time | utcdatetime | EntAssetWorkOrderTable.ScheduledStart | Set by work order scheduling; normally the empty date-time at creation |
scheduledEndDateTime | Scheduled End Date Time | utcdatetime | EntAssetWorkOrderTable.ScheduledEnd | Same as scheduledStartDateTime |
actualStartDateTime | Actual Start Date Time | utcdatetime | EntAssetWorkOrderTable.ActualStart | Stamped by a lifecycle state with Set actual start; the empty date-time at creation |
actualEndDateTime | Actual End Date Time | utcdatetime | EntAssetWorkOrderTable.ActualEnd | Stamped by a lifecycle state with Set actual end; the empty date-time at creation |
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; it orders the events of one record, gaps are normal and permanent, see the README), correlationId (String - always Scope:Key; groups emissions that belong to one logical run and defaults to Event: plus a GUID unique to this emission) 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.
This event does not join a run: correlationId is unique to each emission.
Example Payload
A corrective work order created with the create work order dialog for one asset.
{
"BusinessEventId": "MGCLatticeMaintenanceWorkOrderCreatedBusinessEvent",
"ControlNumber": 587902,
"EventId": "3C9D51A7-2E84-4B6F-9A1D-7F05C2E8B413",
"EventTime": "\/Date(1790243894000)\/",
"EventTimeIso8601": "2026-09-24T09:58:14.2941246Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{39F67208-C799-0FFE-C5BA-11F02CCB11B4}",
"ParentContextRecordSubjects": [],
"eventSequence": 48213,
"correlationId": "Event:d2b7e4a1-5c3f-4e8a-9b61-0a4f7c9e2d35",
"schemaVersion": 1,
"legalEntity": "usmf",
"workOrderId": "WO-000013",
"workOrderType": "Corrective",
"description": "Replace knives on pelletizer for line 2",
"lifecycleState": "New",
"functionalLocation": "PP-02-02-01",
"assetIds": ["PX-201"],
"responsibleWorkerPersonnelNumber": "",
"responsibleWorkerName": "",
"scheduledWorkerPersonnelNumber": "",
"scheduledWorkerName": "",
"expectedStartDateTime": "\/Date(1790243880000)\/",
"expectedEndDateTime": "\/Date(-2208988800000)\/",
"scheduledStartDateTime": "\/Date(-2208988800000)\/",
"scheduledEndDateTime": "\/Date(-2208988800000)\/",
"actualStartDateTime": "\/Date(-2208988800000)\/",
"actualEndDateTime": "\/Date(-2208988800000)\/"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Maintenance work order created") or by filtering the Category column to Asset 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 required.