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 class | MGCLatticeProdPickListPostedBusinessEvent |
| Contract class | MGCLatticeProdPickListPostedBusinessEventContract |
| Category | Production control |
| Module | ModuleAxapta::ProductionControl |
| Label IDs | ProdPickListPostedBusinessEventName / ProdPickListPostedBusinessEventDescription |
| Trigger | ProdJournalCheckPostBOM.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 theProdJournalTable.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 withProdJournalTable::find(this.parmJournalId())and the production order withProdTable::find(prodJournalTable.ProdId). - The event is sent only when the production order is found (
prodTable.RecId), the journal type isProdJournalType::Picklist, and the journal changed from not posted to posted during this run (Postedwas 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 oflines.
- Before
- 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 ownttsbegin/ttscommit, and the event is sent afterrun()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 field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | ProdTable.DataAreaId | Company of the production order. |
productionOrderId | Production Order ID | ProdId | ProdTable.ProdId | Production order the journal belongs to (the journal header's ProdId). |
journalId | Journal ID | ProdJournalId | ProdJournalTable.JournalId | The posted picking list journal. |
itemId | Item Number | ItemId | ProdTable.ItemId | Item being produced (not a component). |
itemSearchName | Item Search Name | str | InventTable::find(ProdTable.ItemId).NameAlias | The produced item's search name, not its product name. |
productionStatus | Production Status | int | enum2int(ProdTable.ProdStatus) | ProdStatus as an integer, read after posting (for example, 4 for StartedUp). |
productionStatusName | Production Status Name | str | enum2Symbol(enumNum(ProdStatus), enum2int(ProdTable.ProdStatus)) | ProdStatus symbol, read after posting. |
lines | Lines | List of MGCLatticeProdPickListPostedLineContract | ProdJournalBOM lines of the journal | One entry per posted line, in line number order. See the table below. |
crossReferences | Cross references | List of MGCLatticeCrossReferenceEventContract | MGCLatticeCrossReferenceEventHelper::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 field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
lineNumber | Line Number | LineNum | ProdJournalBOM.LineNum | Journal line number. |
itemId | Item Number | ItemId | ProdJournalBOM.ItemId | The consumed component. |
quantity | Quantity | InventQty | ProdJournalBOM.InventConsump | Quantity consumed from inventory, in unitId. Negative for a reversing picking list. |
unitId | Inventory Unit | UnitOfMeasureSymbol | InventTable.inventUnitId() of the component | Unit of quantity: the component's inventory unit. |
operationNumber | Operation Number | OprNum | ProdJournalBOM.OprNum | Route 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).