Skip to main content

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 classMGCLatticeSalesPackingSlipPostedBusinessEvent
Contract classMGCLatticeSalesPackingSlipPostedBusinessEventContract
CategorySales orders
ModuleModuleAxapta::SalesOrder
Label IDsSalesPackingSlipPostedBusinessEventName / SalesPackingSlipPostedBusinessEventDescription
TriggerSalesPackingSlipJournalPost.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 overrides postJournalPost() (calling super() then initTrackingNumbers), proving this is the framework's intended post-completion hook; calling next first preserves that behavior.
  • Firing condition: after next postJournalPost() completes, the journal is resolved via this.parmJournalTable() as CustPackingSlipJour and the event sends only when MGCLatticeSalesPackingSlipPostedBusinessEvent::mustRaiseForPosting(jour.RecId, jour.SalesType, isCancellation, this.isProforma()) is true: jour.RecId is 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()) calls postJournalPost() once for every packing slip journal it created, so the event fires once per CustPackingSlipJour.
  • Cancellation gate: packing-slip cancellations and corrections also flow through this posting engine. The event gates OUT VersioningUpdateType::Cancel, read through the inherited accessor this.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) and postJournalPost() runs for it. The hook checks the posting's isProforma() flag and sends nothing for it.
  • Return order gate: return orders (SalesType::ReturnItem) receive returned items through this same packing slip posting. The hook reads CustPackingSlipJour.SalesType and sends nothing for them. Use Return order received for returns.
  • Transaction: postJournalPost() runs inside the posting transaction of FormletterService.processJournal(). The commit-log write is transactional and delivery is asynchronous post-commit, so an aborted post - including a simulated posting, which ends with ttsabort - 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 fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdCustPackingSlipJour.DataAreaIdCompany/legal entity of the packing slip
salesOrderIdSales Order IDSalesIdCustPackingSlipJour.SalesIdSales order the packing slip was posted against
customerAccountCustomer AccountCustAccountCustPackingSlipJour.OrderAccountOrder account (ordering customer) on the journal
packingSlipIdPacking Slip IDPackingSlipIdCustPackingSlipJour.PackingSlipIdPosted packing slip (delivery note) number
deliveryDateDelivery DateTransDateCustPackingSlipJour.DeliveryDateDelivery date recorded on the packing slip; serialized in the \/Date(milliseconds)\/ form
crossReferencesCross referencesList of MGCLatticeCrossReferenceEventContractMGCLatticeCrossReferenceEventHelper::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.