Skip to main content

Production pick list posted

Fires when a production picking list journal is posted, signaling that raw materials and components have been consumed against a production order. This is the inventory-deduction moment in the shop-floor flow. The payload lists each posted component line with its own quantity and unit, so downstream systems can track material consumption, update stock projections, and reconcile what was issued to the order against the bill of materials.

Event classMGCLatticeProdPickListPostedBusinessEvent
Contract classMGCLatticeProdPickListPostedBusinessEventContract
CategoryProduction control
ModuleModuleAxapta::ProductionControl
Label IDsProdPickListPostedBusinessEventName / ProdPickListPostedBusinessEventDescription
TriggerProdJournalCheckPostBOM.run() -> the run posts the journal and JournalType == ProdJournalType::Picklist (lines captured before posting)

Trigger / Hook​

  • Extension class: MGCLatticeProdJournalCheckPostBOM_Class_Extension, decorated with [ExtensionOf(classStr(ProdJournalCheckPostBOM))].
  • CoC method signature: public void run() (no parameters). This is the posting process for production BOM journals, not the ProdJournalTable.update() table write.
  • Firing condition:
    • Before next run(), the journal's lines are read in line number order (MGCLatticeProdPickListPostedBusinessEvent::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::Picklist, and the journal changed from not posted to posted during this run (Posted was No before and is Yes after). 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.
  • Coverage: raised once per picking list journal posted through ProdJournalCheckPostBOM, whichever process posts it. That includes posting from the production journal page and picking lists that other production processes post automatically, such as the start and report-as-finished updates and route card postings. Reversing picking lists 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 journal inside its own ttsbegin/ttscommit, and the event is sent after run() returns. When the picking list is posted as part of a larger update (for example, a report as finished), 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(MGCLatticeProdPickListPostedBusinessEvent)), so the event only sends when activated in the business events catalog. The lines are only read when the event is enabled.
public void run()
{
JournalId journalId = this.parmJournalId();
boolean eventEnabled = BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeProdPickListPostedBusinessEvent));
NoYes postedBefore;
List capturedLines;
ProdJournalTable prodJournalTable;
ProdTable prodTable;

if (eventEnabled)
{
postedBefore = ProdJournalTable::find(journalId).Posted;
capturedLines = MGCLatticeProdPickListPostedBusinessEvent::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::Picklist)
{
MGCLatticeProdPickListPostedBusinessEvent::newFromJournal(
prodTable,
journalId,
MGCLatticeProdPickListPostedBusinessEvent::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(). There is no journal total: components are measured in different units, so a sum over them would mean nothing.

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 picking list journal.
itemIdItem NumberItemIdProdTable.ItemIdItem being produced (not a component).
itemSearchNameItem Search NamestrInventTable::find(ProdTable.ItemId).NameAliasThe produced item's search name, not its product name.
productionStatusProduction Statusintenum2int(ProdTable.ProdStatus)ProdStatus as an integer, read after posting (for example, 4 for StartedUp).
productionStatusNameProduction Status Namestrenum2Symbol(enumNum(ProdStatus), enum2int(ProdTable.ProdStatus))ProdStatus symbol, read after posting.
linesLinesList of MGCLatticeProdPickListPostedLineContractProdJournalBOM 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 NumberLineNumProdJournalBOM.LineNumJournal line number.
itemIdItem NumberItemIdProdJournalBOM.ItemIdThe consumed component.
quantityQuantityInventQtyProdJournalBOM.InventConsumpQuantity consumed from inventory, in unitId. Negative for a reversing picking list.
unitIdInventory UnitUnitOfMeasureSymbolInventTable.inventUnitId() of the componentUnit of quantity: the component's inventory unit.
operationNumberOperation NumberOprNumProdJournalBOM.OprNumRoute operation the component is consumed for; 0 when it isn't tied to an operation.

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": "MGCLatticeProdPickListPostedBusinessEvent",
"ControlNumber": 5637151721,
"EventId": "A7CC8F95-C596-48B9-99A6-3CB918E49602",
"EventTime": "\/Date(1790072793000)\/",
"EventTimeIso8601": "2026-09-22T10:26:33.0540493Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{9524B2C0-079C-928A-614A-C7E8A7CA1FD9}",
"ParentContextRecordSubjects": [],
"eventSequence": 48121,
"correlationId": "Event:83f76883-2933-43d4-852f-ce05954bf873",
"schemaVersion": 1,
"legalEntity": "usmf",
"productionOrderId": "P000108",
"journalId": "01389",
"itemId": "D0001",
"itemSearchName": "MidRangeSpeaker",
"productionStatus": 4,
"productionStatusName": "StartedUp",
"lines": [
{
"lineNumber": 1.00,
"itemId": "M0001",
"quantity": 20.00,
"unitId": "ea",
"operationNumber": 10
},
{
"lineNumber": 2.00,
"itemId": "M0002",
"quantity": 20.00,
"unitId": "ea",
"operationNumber": 10
}
],
"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 pick list 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).