Sales packing slip posted
Fires when a sales order packing slip is posted in Dynamics 365 Finance and Operations. Posting a packing slip records the physical shipment (delivery) of goods against a sales order and creates a CustPackingSlipJour journal header. Pro forma packing slips, packing slip cancellations and return order receipts do not raise it. This business event lets downstream systems react to the moment goods leave the warehouse - for example to notify a customer of dispatch, update a third-party logistics or e-commerce system, or trigger transportation and tracking workflows.
| Event class | MGCLatticeSalesPackingSlipPostedBusinessEvent |
| Contract class | MGCLatticeSalesPackingSlipPostedBusinessEventContract |
| Category | Sales orders |
| Module | ModuleAxapta::SalesOrder |
| Label IDs | SalesPackingSlipPostedBusinessEventName / SalesPackingSlipPostedBusinessEventDescription |
| Trigger | SalesPackingSlipJournalPost.postJournalPost() -> after a packing slip journal is posted (pro forma, cancellations and return orders gated out) |
Trigger / Hook
- Extension class
MGCLatticeSalesPackingSlipJournalPost_Class_Extension, decorated with[ExtensionOf(classStr(SalesPackingSlipJournalPost))]. - Chain of Command wraps the form-letter post-completion process method. Exact signature:
public void postJournalPost()(no parameters). The class itself already overridespostJournalPost()(callingsuper()theninitTrackingNumbers), proving this is the framework's intended post-completion hook; callingnextfirst preserves that behavior. - Firing condition: after
next postJournalPost()completes, the journal is resolved viathis.parmJournalTable() as CustPackingSlipJourand the event sends only whenMGCLatticeSalesPackingSlipPostedBusinessEvent::mustRaiseForPosting(jour.RecId, jour.SalesType, isCancellation, this.isProforma())is true:jour.RecIdis non-zero, the post is not a cancellation, the posting is not pro forma AND the order is not a return order. The form-letter framework (FormletterService.createAndPostJournal()) callspostJournalPost()once for every packing slip journal it created, so the event fires once perCustPackingSlipJour. - Cancellation gate: packing-slip cancellations and corrections also flow through this posting engine. The event gates OUT
VersioningUpdateType::Cancel, read through the inherited accessorthis.parmChainFormletterContract().parmVersioningUpdateType(). Corrections (VersioningUpdateType::Correction) are not excluded and raise the event like a regular posting. - Pro forma gate: a pro forma packing slip also creates a temporary
CustPackingSlipJour(deleted after the pro forma document is printed) andpostJournalPost()runs for it. The hook checks the posting'sisProforma()flag and sends nothing for it. - Return order gate: return orders (
SalesType::ReturnItem) receive returned items through this same packing slip posting. The hook readsCustPackingSlipJour.SalesTypeand sends nothing for them. Use Return order received for returns. - Transaction:
postJournalPost()runs inside the posting transaction ofFormletterService.processJournal(). The commit-log write is transactional and delivery is asynchronous post-commit, so an aborted post - including a simulated posting, which ends withttsabort- never delivers. - Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeSalesPackingSlipPostedBusinessEvent))must return true (the event must be activated against an endpoint in the business events catalog). - The event is built and sent via
MGCLatticeSalesPackingSlipPostedBusinessEvent::newFromCustPackingSlipJour(jour).send().
/// <summary>
/// Triggers the sales packing slip posted business event after a packing slip journal is posted,
/// excluding pro forma, cancellation and return order postings.
/// </summary>
public void postJournalPost()
{
CustPackingSlipJour jour;
FormLetterContract contract;
boolean isCancellation;
next postJournalPost();
jour = this.parmJournalTable() as CustPackingSlipJour;
contract = this.parmChainFormletterContract();
isCancellation = contract && contract.parmVersioningUpdateType() == VersioningUpdateType::Cancel;
if (MGCLatticeSalesPackingSlipPostedBusinessEvent::mustRaiseForPosting(jour.RecId, jour.SalesType, isCancellation, this.isProforma())
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeSalesPackingSlipPostedBusinessEvent)))
{
MGCLatticeSalesPackingSlipPostedBusinessEvent::newFromCustPackingSlipJour(jour).send();
}
}
public static boolean mustRaiseForPosting(RecId _journalRecId, SalesType _salesType, boolean _isCancellation, boolean _isProforma)
{
return _journalRecId != 0
&& !_isCancellation
&& !_isProforma
&& _salesType != SalesType::ReturnItem;
}
Payload Contract
One row per [DataMember], in declaration order. All sources are fields on the triggering CustPackingSlipJour record, mapped in initialize().
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | CustPackingSlipJour.DataAreaId | Company/legal entity of the packing slip |
salesOrderId | Sales Order ID | SalesId | CustPackingSlipJour.SalesId | Sales order the packing slip was posted against |
customerAccount | Customer Account | CustAccount | CustPackingSlipJour.OrderAccount | Order account (ordering customer) on the journal |
packingSlipId | Packing Slip ID | PackingSlipId | CustPackingSlipJour.PackingSlipId | Posted packing slip (delivery note) number |
deliveryDate | Delivery Date | TransDate | CustPackingSlipJour.DeliveryDate | Delivery date recorded on the packing slip; serialized in the \/Date(milliseconds)\/ form |
crossReferences | Cross references | List of MGCLatticeCrossReferenceEventContract | MGCLatticeCrossReferenceEventHelper::listForSalesOrder(CustPackingSlipJour.SalesId, CustPackingSlipJour.DataAreaId) | Keys external systems hold for the sales order the packing slip was posted for: 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 sales order's, not the journal's. |
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 sent inside the business transaction; it orders the events of one record, but two transactions can commit in the opposite order of their values, and gaps are normal and permanent - see the events overview), correlationId (String - always Scope:Key: Event: followed by a GUID unique to this emission, unless the event joins a 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": "MGCLatticeSalesPackingSlipPostedBusinessEvent",
"ControlNumber": 5637148937,
"EventId": "9A9833E7-4810-4181-AA61-982509F83D84",
"EventTime": "\/Date(1790262472000)\/",
"EventTimeIso8601": "2026-09-24T15:07:52.9169889Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{B10F798F-53AE-A4D0-5E87-E4F05D59446E}",
"ParentContextRecordSubjects": [],
"eventSequence": 48227,
"correlationId": "Event:08be914a-e4d1-4450-a420-23d1989d3ef6",
"schemaVersion": 1,
"legalEntity": "usmf",
"salesOrderId": "001787",
"customerAccount": "US-004",
"packingSlipId": "SPK-00002043",
"deliveryDate": "\/Date(1790208000000)\/",
"crossReferences": [
{
"externalSystem": "SHOP",
"externalKey": "WEB-1004871"
}
]
}
Activation
Go to System administration > Setup > Business events > Business events catalog. Find the event by its name ("Sales packing slip posted") or filter the Category column to Sales orders. Select it and activate it against the desired endpoint (Service Bus, Event Grid, Event Hub, HTTPS, etc.). Once activated, posting a sales packing slip will deliver the payload above to that endpoint.
Related Events
- Sales order confirmed - earlier in the sales order fulfillment lifecycle.
- Sales order invoice posted - the next step after shipment.
- Warehouse load shipped - shipment confirmation on the warehouse side.
- Warehouse picking completed - picking that precedes shipment.
- Sales order canceled - sibling Sales orders event.
- Sales quotation confirmed - sibling Sales orders event.
- Sales agreement confirmed - sibling Sales orders event.
- Sales return order created - sibling Sales orders event covering the reverse-logistics flow.