Skip to main content

Sales order invoice posted

Fires when a customer invoice for a sales order is posted in Accounts receivable - once for each invoice journal (CustInvoiceJour) that sales order invoicing posts, after the posting has written the invoice totals. Billing, revenue, logistics and customer-portal systems can use it to learn that an order has been invoiced, with the invoice number, date, total and currency. Credit notes posted from sales orders raise it too (their invoiceAmount is negative); pro forma invoices do not. It is the sales-order counterpart of Free text invoice posted; the two never fire for the same invoice.

Event classMGCLatticeSalesOrderInvoicePostedBusinessEvent
Contract classMGCLatticeSalesOrderInvoicePostedBusinessEventContract
CategorySales orders
ModuleModuleAxapta::SalesOrder
Label IDsSalesOrderInvoicePostedBusinessEventName / SalesOrderInvoicePostedBusinessEventDescription
TriggerSalesInvoiceJournalPost.postJournalPost() -> a posted (not pro forma) invoice journal with a SalesId

Trigger / Hook​

  • Extension class: MGCLatticeSalesInvoiceJournalPost_Class_Extension, [ExtensionOf(classStr(SalesInvoiceJournalPost))], wrapping public void postJournalPost() with Chain of Command. The event is sent by MGCLatticeSalesOrderInvoicePostedEventHandlers::raisePostedInvoiceEvents().
  • When it runs: sales order invoicing (FormletterService) calls postJournalPost() once for every invoice journal it creates, after that journal has been posted. By then the posting has written the header's totals (SalesInvoiceJournalPostBase.updateJournalTable()), and the header is read again by record ID so the payload carries what was stored. This covers invoicing a single order, summary invoicing of several orders, and batch and parallel posting. Project invoicing that posts sales order lines through a project invoice creates no CustInvoiceJour and does not raise it (see Project invoice posted).
  • Firing condition: the invoice journal exists, belongs to a sales order (SalesId is set), and is not pro forma - neither the posting (isProforma()) nor the header (Proforma) is flagged as pro forma. One event per invoice journal: a summary invoice that covers several orders is one journal and raises one event, carrying the order the invoice is posted under.
  • Credit notes: a credit note posted from a sales order is an invoice journal with a negative total, so it raises this event with a negative invoiceAmount. When the credit note is for a return order, Return credit note posted is raised for the same journal as well; if you subscribe to both, de-duplicate on invoiceId.
  • Pro forma invoices: D365 saves a pro forma invoice as a real invoice journal flagged as pro forma, prints it and deletes it again. Those journals are skipped, so a pro forma invoice never raises this event.
  • Timing: postJournalPost() runs inside the posting transaction (FormletterService.processJournal()); send() writes to the business events commit log inside it and delivery happens asynchronously after commit, so a posting that fails, or a simulated posting (which D365 rolls back), never delivers.
  • Enablement gate: BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeSalesOrderInvoicePostedBusinessEvent)).
public void postJournalPost()
{
next postJournalPost();

// Null when the posted journal is not an invoice header (project invoicing posts against the parm table).
CustInvoiceJour invoiceJournal = this.parmJournalTable() as CustInvoiceJour;

// The activation checks are cached lookups; re-read the header only when one of the two events is wanted.
if (invoiceJournal && invoiceJournal.RecId && !this.isProforma()
&& (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeSalesOrderInvoicePostedBusinessEvent))
|| BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeReturnCreditNotePostedBusinessEvent))))
{
// Re-read the header so the events carry what the posting stored, totals included.
MGCLatticeSalesOrderInvoicePostedEventHandlers::raisePostedInvoiceEvents(CustInvoiceJour::findRecId(invoiceJournal.RecId));
}
}

raisePostedInvoiceEvents() checks that the header has a SalesId and is not pro forma, sends this event when it is activated, and then hands the header to the return credit note check.

Payload Contract​

One row per [DataMember], in declaration order. All sources are fields on the posted CustInvoiceJour header, read after the posting wrote its totals and mapped in initialize().

JSON fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdCustInvoiceJour.DataAreaIdCompany of the invoice, as stored (lowercase, for example usmf).
invoiceAccountInvoice AccountCustAccountCustInvoiceJour.InvoiceAccountThe customer billed, taken from the sales order's invoice account (it can differ from the order account).
salesOrderIdSales Order IDSalesIdCustInvoiceJour.SalesIdThe sales order on the invoice header. For a summary invoice this is the order the invoice is posted under; the other orders on the invoice are not listed.
invoiceIdInvoice IDInvoiceIdCustInvoiceJour.InvoiceIdInvoice number.
invoiceDateInvoice DateInvoiceDateCustInvoiceJour.InvoiceDateInvoice date. A date, serialized in the \/Date(milliseconds)\/ form (midnight UTC).
invoiceAmountInvoice AmountAmountCurCustInvoiceJour.InvoiceAmountInvoice total including tax, in currencyCode. Negative for a credit note.
currencyCodeCurrency CodeCurrencyCodeCustInvoiceJour.CurrencyCodeTransaction currency of the invoice (from the sales order).
crossReferencesCross referencesList of MGCLatticeCrossReferenceEventContractMGCLatticeCrossReferenceEventHelper::listForSalesOrder(CustInvoiceJour.SalesId, CustInvoiceJour.DataAreaId)Keys external systems hold for the sales order that was invoiced: 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": "MGCLatticeSalesOrderInvoicePostedBusinessEvent",
"ControlNumber": 5637148720,
"EventId": "4D7A9E12-C3B8-4F06-9E51-8A2B6C0D7F94",
"EventTime": "\/Date(1790270772000)\/",
"EventTimeIso8601": "2026-09-24T17:26:12.4336268Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{55920BD2-9AE5-F41E-5D62-9DC7EAB242C4}",
"ParentContextRecordSubjects": [],
"eventSequence": 48342,
"correlationId": "Event:0ed82331-b041-4ea9-b4f7-31cb6cc743af",
"schemaVersion": 1,
"legalEntity": "usmf",
"invoiceAccount": "US-001",
"salesOrderId": "000745",
"invoiceId": "CIV-000745",
"invoiceDate": "\/Date(1790208000000)\/",
"invoiceAmount": 4386.75,
"currencyCode": "USD",
"crossReferences": [
{
"externalSystem": "SHOP",
"externalKey": "WEB-1004871"
}
]
}

Activation​

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