Skip to main content

Production route card posted

Fires when a production route card journal is posted, recording the operation time and the good and error quantities reported for a production order's operations. The payload identifies the production order and its item, its status after posting, and lists each posted line with its operation, resource, hours and quantities. Downstream systems can use it for shop-floor progress tracking, labor and machine utilization, and scrap monitoring.

Event classMGCLatticeProdRouteCardPostedBusinessEvent
Contract classMGCLatticeProdRouteCardPostedBusinessEventContract
CategoryProduction control
ModuleModuleAxapta::ProductionControl
Label IDsProdRouteCardPostedBusinessEventName / ProdRouteCardPostedBusinessEventDescription
TriggerProdJournalCheckPostRoute.run() -> the run posts the journal and JournalType == ProdJournalType::RouteCard (lines captured before posting)

Trigger / Hook​

  • Extension class: MGCLatticeProdJournalCheckPostRoute_Class_Extension, decorated with [ExtensionOf(classStr(ProdJournalCheckPostRoute))].
  • CoC method signature: void run() (no parameters; the wrapper mirrors the default visibility of the standard ProdJournalCheckPostRoute.run()). This is the posting process for production route journals, not the ProdJournalTable.update() table write.
  • Firing condition:
    • Before next run(), the journal's lines are read in line number order (MGCLatticeProdRouteCardPostedBusinessEvent::captureJournalLines()), because posting deletes them when the journal name is set to delete lines after posting. The journal's posted flag is read too.
    • After next run(), the journal header is re-read with ProdJournalTable::find(this.parmJournalId()) and the production order with ProdTable::find(prodJournalTable.ProdId).
    • The event is sent only when the production order is found (prodTable.RecId), the journal type is ProdJournalType::RouteCard, and the journal changed from not posted to posted during this run (Posted was No before and is Yes after). Job card journals are excluded by the journal-type check. A journal that fails to post isn't marked as posted, so nothing is sent for it. The order is found through the journal header, so without a header nothing is sent.
    • Lines with errors that posting moved to a new journal instead of posting them (transferJournalId()) are left out of lines.
  • What run() includes: the standard ProdJournalCheckPostRoute.run() posts the route card and then posts any picking list and report-as-finished journals that the route card generated. The event is sent after all of these, so a picking list posted automatically by the route card raises Production pick list posted first, a report-as-finished journal that moves the order to Reported as finished raises Production order reported as finished first, and productionStatus reflects the order after those postings.
  • Coverage: raised once per route card journal posted through ProdJournalCheckPostRoute, whichever process posts it. That includes posting from the production journal page and route cards that other production processes post automatically, such as the start and report-as-finished updates and manufacturing execution registrations that are posted as route cards (registrations posted as job cards don't raise it). Reversing route cards are posted the same way (for example, when the status of a started order is reset), so the event is also raised for them, with negative line quantities.
  • Transaction: JournalCheckPost.run() posts the route card inside its own ttsbegin/ttscommit, and the event is sent after run() returns. When the route card is posted as part of a larger update, that update's transaction is still open. BusinessEventsBase.send() only writes to the commit log inside the current transaction and delivery is asynchronous post-commit, so if that transaction rolls back, the event is never delivered.
  • Enablement gate: guarded by BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeProdRouteCardPostedBusinessEvent)), so the event only sends when activated in the business events catalog. The lines are only read when the event is enabled.
void run()
{
JournalId journalId = this.parmJournalId();
boolean eventEnabled = BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeProdRouteCardPostedBusinessEvent));
NoYes postedBefore;
List capturedLines;
ProdJournalTable prodJournalTable;
ProdTable prodTable;

if (eventEnabled)
{
postedBefore = ProdJournalTable::find(journalId).Posted;
capturedLines = MGCLatticeProdRouteCardPostedBusinessEvent::captureJournalLines(journalId);
}

next run();

if (eventEnabled && postedBefore == NoYes::No)
{
prodJournalTable = ProdJournalTable::find(journalId);
prodTable = ProdTable::find(prodJournalTable.ProdId);

if (prodTable.RecId
&& prodJournalTable.Posted == NoYes::Yes
&& prodJournalTable.JournalType == ProdJournalType::RouteCard)
{
MGCLatticeProdRouteCardPostedBusinessEvent::newFromJournal(
prodTable,
journalId,
MGCLatticeProdRouteCardPostedBusinessEvent::postedLines(capturedLines, this.transferJournalId())).send();
}
}
}

Payload Contract​

One row per [DataMember], in declaration order. The order fields come from the re-read ProdTable record; the lines come from the journal lines captured before posting. All are mapped in initialize(). Hours and quantities are reported per line, each with its operation. There is no journal total: the same units pass through every operation, so adding good quantities across operations would count them more than once.

JSON fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdProdTable.DataAreaIdCompany of the production order.
productionOrderIdProduction Order IDProdIdProdTable.ProdIdProduction order the journal belongs to (the journal header's ProdId).
journalIdJournal IDProdJournalIdProdJournalTable.JournalIdThe posted route card journal.
itemIdItem NumberItemIdProdTable.ItemIdItem being produced.
itemSearchNameItem Search NamestrInventTable::find(ProdTable.ItemId).NameAliasThe item's search name, not its product name.
productionStatusProduction Statusintenum2int(ProdTable.ProdStatus)ProdStatus as an integer, read after posting (for example, 4 for StartedUp or 5 for ReportedFinished).
productionStatusNameProduction Status Namestrenum2Symbol(enumNum(ProdStatus), enum2int(ProdTable.ProdStatus))ProdStatus symbol, read after posting.
unitIdInventory UnitUnitOfMeasureSymbolInventTable.inventUnitId() of the produced itemUnit of the good and error quantities on the lines: the produced item's inventory unit.
linesLinesList of MGCLatticeProdRouteCardPostedLineContractProdJournalRoute lines of the journalOne entry per posted line, in line number order. See the table below.
crossReferencesCross referencesList of MGCLatticeCrossReferenceEventContractMGCLatticeCrossReferenceEventHelper::listFor(ProdTable)Keys external systems hold for the production order: one { externalSystem, externalKey } object per external system, ordered by external system, historical keys included; [] when it has none. See Cross-reference keys. The keys are the production order's, not the journal's.

Each entry in lines:

JSON fieldDisplay labelType / EDTSourceNotes
lineNumberLine NumberLineNumProdJournalRoute.LineNumJournal line number.
operationNumberOperation NumberOprNumProdJournalRoute.OprNumRoute operation the line reports on. An operation appears once per line, so it can appear more than once (for example, for several resources or workers).
operationIdOperation IDRouteOprIdProdJournalRoute.OprIdThe operation.
resourceIdResourceWrkCtrIdBaseProdJournalRoute.WrkCtrIdResource the line was reported on.
goodQuantityGood QuantityProdReportedGoodProdJournalRoute.QtyGoodGood quantity reported for the operation on this line, in unitId; 0 on a line that reports only time. Negative for a reversing route card.
errorQuantityError QuantityProdReportedErrorProdJournalRoute.QtyErrorError (scrap) quantity reported for the operation on this line, in unitId.
hoursHoursProdReportedHoursProdJournalRoute.HoursHours reported on this line.
isOperationFinishedIs Operation FinishedbooleanProdJournalRoute.OprFinished == NoYes::Yestrue when the line reports the operation as finished.

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": "MGCLatticeProdRouteCardPostedBusinessEvent",
"ControlNumber": 5637151722,
"EventId": "A584C41C-EC67-4C42-BDC9-AAC0A6B80B2E",
"EventTime": "\/Date(1790075059000)\/",
"EventTimeIso8601": "2026-09-22T11:04:19.7846509Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{534DE51B-8EDB-164A-6D8C-4344995E4E40}",
"ParentContextRecordSubjects": [],
"eventSequence": 48122,
"correlationId": "Event:01863c8b-b93e-4f52-80e1-4ec3cb7c06c3",
"schemaVersion": 1,
"legalEntity": "usmf",
"productionOrderId": "P000108",
"journalId": "01390",
"itemId": "D0001",
"itemSearchName": "MidRangeSpeaker",
"productionStatus": 4,
"productionStatusName": "StartedUp",
"unitId": "ea",
"lines": [
{
"lineNumber": 1.00,
"operationNumber": 10,
"operationId": "Assembly",
"resourceId": "1211",
"goodQuantity": 20.00,
"errorQuantity": 0.00,
"hours": 2.50,
"isOperationFinished": true
},
{
"lineNumber": 2.00,
"operationNumber": 20,
"operationId": "Testing",
"resourceId": "1225",
"goodQuantity": 19.00,
"errorQuantity": 1.00,
"hours": 1.25,
"isOperationFinished": false
}
],
"crossReferences": [
{
"externalSystem": "MES",
"externalKey": "WO-104522"
}
]
}

Activation​

In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Production route card posted") or by filtering the Category column to Production control. 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).