Skip to main content

Maintenance work order scheduled

Fires when an Asset Management work order is scheduled: it moves into the Scheduled state of its work order type's lifecycle model (or the model's Mobile app scheduled state). That is the state standard work order scheduling moves a work order to when all its jobs are scheduled, so the event works with the standard setup; in the USMF demo data the Standard lifecycle model's scheduled state is Scheduled. Downstream systems can use it to hand the planned job, its scheduled window and the assigned technician to dispatch or field-service tools, warn production about planned equipment downtime, or stage spare parts.

Event classMGCLatticeMaintenanceWorkOrderScheduledBusinessEvent
Contract classMGCLatticeMaintenanceWorkOrderScheduledBusinessEventContract
CategoryAsset management
ModuleModuleAxapta::AssetManagement
Label IDsMaintenanceWorkOrderScheduledBusinessEventName / MaintenanceWorkOrderScheduledBusinessEventDescription
TriggerLifecycle state update (EntAssetLifecycleStateUpdate_WorkOrderTable.updateStateRecord()) -> lifecycle state changes to the lifecycle model's scheduled (or mobile app scheduled) state, which has neither Set actual end nor Set actual start

Trigger / Hook​

  • Extension class: MGCLatticeEntAssetLifecycleStateUpdate_WorkOrderTable_Class_Extension, [ExtensionOf(classStr(EntAssetLifecycleStateUpdate_WorkOrderTable))], wrapping protected void updateStateRecord(RecId _recId) - a process hook on the lifecycle state update, not a table data event. It is the only standard code that changes a work order's lifecycle state: the Update work order state action, work order scheduling (through EntAssetWorkOrderTable.setState()), unscheduling and the mobile app all run it.
  • The wrapper reads the work order's state before next and calls MGCLatticeMaintenanceWorkOrderEventHandlers::raiseForLifecycleTransition() afterwards, which reads the work order again. The same helper also raises the Started and Completed events; the decision is made by eventForTransition().
  • Other writers: work orders written by data entities, imports or code outside the lifecycle state update do not raise the event, even when they change the state.
  • Firing condition:
    1. The lifecycle state update must change the work order's lifecycle state (the state read after next differs from the one read before it), and the work order must already have had a state. Updates that leave the state unchanged, such as rescheduling a work order that is already in the scheduled state, assigning workers or editing lines, are ignored. The first state a work order receives while it is created is reported by the Created event instead.
    2. The new state is checked in a fixed order: Set actual end raises Completed; otherwise Set actual start raises Started; otherwise, when the new state is the lifecycle model's Scheduled state or Mobile app scheduled state (EntAssetWorkOrderLifecycleModel.ScheduledLifecycleState / MobileAppScheduledLifecycleState of the work order type's model), this event is raised. At most one of the three events is raised per transition.
  • Which state changes count: every change that lands the work order in the model's scheduled state, whether it comes from work order scheduling (the Schedule work orders process, the work order scheduling page, or the automatic scheduling of work orders created in the mobile app, which uses the mobile app scheduled state when the model has one) or from Update work order state. Standard scheduling moves a work order to that state once all its lines are scheduled; Microsoft's own "work order is scheduled" business event is raised at the same point. The Ready option of a lifecycle state plays no part.
  • Setup required: none with the standard lifecycle models. A lifecycle model without a scheduled state never raises this event, because scheduling then leaves the work order's state unchanged.
  • Timing: the standard lifecycle-state update saves the work order inside a transaction. send() writes the event to the business events commit log in that transaction and it is delivered after commit, so a state change that rolls back never delivers the event. The payload reflects the work order as saved in its new state, including the scheduled window and worker that scheduling has just set.
  • Enablement gate: guarded by BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeMaintenanceWorkOrderScheduledBusinessEvent)), so the event only sends when activated in the business events catalog.
// MGCLatticeEntAssetLifecycleStateUpdate_WorkOrderTable_Class_Extension
protected void updateStateRecord(RecId _recId)
{
RefRecId previousState = EntAssetWorkOrderTable::findRecId(_recId).WorkOrderLifecycleState;

next updateStateRecord(_recId);

MGCLatticeMaintenanceWorkOrderEventHandlers::raiseForLifecycleTransition(_recId, previousState);
}

// MGCLatticeMaintenanceWorkOrderEventHandlers::raiseForLifecycleTransition(_workOrderRecId, _previousState)
// reads the work order again, returns when the state is unchanged, and otherwise asks
// eventForTransition() which event (if any) the transition raises: Completed (Set actual end),
// Started (Set actual start) or Scheduled (the model's scheduled or mobile app scheduled state).

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 updated work order header, the records it references and its work order lines.

JSON fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdEntAssetWorkOrderTable.DataAreaIdCompany of the work order, in lower case
workOrderIdWork OrderEntAssetWorkOrderIDEntAssetWorkOrderTable.WorkOrderIdWork order number
workOrderTypeWork Order TypeEntAssetWorkOrderTypeIDEntAssetWorkOrderType::findRecId(EntAssetWorkOrderTable.WorkOrderType).WorkOrderTypeIdThe work order type, for example Corrective or Preventive
descriptionDescriptionDescriptionEntAssetWorkOrderTable.DescriptionWork order description
lifecycleStateLifecycle StateEntAssetLifecycleStateIdEntAssetWorkOrderLifecycleState::find(EntAssetWorkOrderTable.WorkOrderLifecycleState).WorkOrderLifecycleStateIdID of the new lifecycle state: the model's scheduled or mobile app scheduled state
functionalLocationFunctional LocationEntAssetFunctionalLocationIDEntAssetFunctionalLocation::findRecID(EntAssetWorkOrderTable.FunctionalLocation).FunctionalLocationIdThe 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
assetIdsAssetsList of EntAssetObjectIDEntAssetObjectTable.ObjectID of each EntAssetWorkOrderLine.Object, in line-number orderEvery distinct asset on the work order's lines, as a JSON array of strings; [] when the work order has no lines
responsibleWorkerPersonnelNumberResponsible Worker Personnel NumberHcmPersonnelNumberIdHcmWorker.PersonnelNumber of the responsible maintenance workerEmpty when no responsible worker is set
responsibleWorkerNameResponsible Worker NameHcmWorkerNameEntAssetWorker::find(EntAssetWorkOrderTable.ResponsibleWorker).workerName()Name of the worker responsible for the work order; empty when none
scheduledWorkerPersonnelNumberScheduled Worker Personnel NumberHcmPersonnelNumberIdHcmWorker.PersonnelNumber of the scheduled maintenance workerEmpty when no worker is scheduled
scheduledWorkerNameScheduled Worker NameHcmWorkerNameEntAssetWorker::find(EntAssetWorkOrderTable.WorkerScheduled).workerName()Name of the worker in the work order's "All maintenance jobs scheduled to" field; empty unless scheduling assigned every job to the same worker
expectedStartDateTimeExpected Start Date TimeutcdatetimeEntAssetWorkOrderTable.ExpectedStart"\/Date(ms)\/" form
expectedEndDateTimeExpected End Date TimeutcdatetimeEntAssetWorkOrderTable.ExpectedEndThe empty date-time (1900-01-01T00:00:00Z, "\/Date(-2208988800000)\/") when not set
scheduledStartDateTimeScheduled Start Date TimeutcdatetimeEntAssetWorkOrderTable.ScheduledStartEarliest scheduled start of the work order's jobs, set by scheduling
scheduledEndDateTimeScheduled End Date TimeutcdatetimeEntAssetWorkOrderTable.ScheduledEndLatest scheduled end of the work order's jobs, set by scheduling
actualStartDateTimeActual Start Date TimeutcdatetimeEntAssetWorkOrderTable.ActualStartNormally the empty date-time at this point
actualEndDateTimeActual End Date TimeutcdatetimeEntAssetWorkOrderTable.ActualEndNormally the empty date-time at this point

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 preventive work order scheduled to one technician, using the USMF Standard lifecycle model.

{
"BusinessEventId": "MGCLatticeMaintenanceWorkOrderScheduledBusinessEvent",
"ControlNumber": 587958,
"EventId": "9E4A7C21-6B3D-4F80-A2E5-18C0D7B94F36",
"EventTime": "\/Date(1790260337000)\/",
"EventTimeIso8601": "2026-09-24T14:32:17.3141550Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{277F8196-7ABE-EA77-8E24-9CD3896107A0}",
"ParentContextRecordSubjects": [],
"eventSequence": 48290,
"correlationId": "Event:5f1c8b3e-0d2a-4c67-9e14-b7a3f6d08c52",
"schemaVersion": 1,
"legalEntity": "usmf",
"workOrderId": "WO-000006",
"workOrderType": "Preventive",
"description": "Emergency Generator Weekly PM",
"lifecycleState": "Scheduled",
"functionalLocation": "PP-04-03",
"assetIds": ["GEN-002"],
"responsibleWorkerPersonnelNumber": "",
"responsibleWorkerName": "",
"scheduledWorkerPersonnelNumber": "000020",
"scheduledWorkerName": "Bill Malone",
"expectedStartDateTime": "\/Date(1790319600000)\/",
"expectedEndDateTime": "\/Date(-2208988800000)\/",
"scheduledStartDateTime": "\/Date(1790341200000)\/",
"scheduledEndDateTime": "\/Date(1790357400000)\/",
"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 scheduled") 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).

The event follows the Scheduled state (and Mobile app scheduled state) set on each work order lifecycle model in Asset management > Setup > Work orders > Lifecycle models. Make sure that state does not also have Set actual start or Set actual end enabled (Asset management > Setup > Work orders > Lifecycle states), otherwise the Started or Completed event is raised instead.