Sales quotation confirmed
Fires when a sales quotation is confirmed and a quotation confirmation journal (CustQuotationConfirmJour) record is created. This marks the moment a customer quotation transitions from an open offer into a confirmed commitment - confirming a quotation also creates the sales order from it - letting downstream systems (CRM, pricing, fulfillment) react to the agreed quotation.
| Event class | MGCLatticeSalesQuotationConfirmedBusinessEvent |
| Contract class | MGCLatticeSalesQuotationConfirmedBusinessEventContract |
| Category | Sales orders |
| Module | ModuleAxapta::SalesOrder |
| Label IDs | SalesQuotationConfirmedBusinessEventName / SalesQuotationConfirmedBusinessEventDescription |
| Trigger | SalesQuotationEditLinesForm_Sales_Confir.createJournal() -> when a confirmation journal is created (journal().RecId guard) |
Trigger / Hook
- Extension class:
MGCLatticeSalesQuotationEditLinesForm_Sales_Confir_Class_Extension,[ExtensionOf(classStr(SalesQuotationEditLinesForm_Sales_Confir))] - CoC method signature:
public boolean createJournal()(no parameters, returns boolean) - Firing condition:
ret = next createJournal(), then the event sends only whenret && this.journal().RecId- i.e. a confirmation journal was actually created (once per confirmed quotation). Quotation confirmation uses a different framework from the FormletterService form letters: the driverSalesQuotationEditLinesForm.run()wraps each quotation in its own ttsbegin/ttscommit aroundcreateJournal(), and the journal row is written bywriteJournal()(during the same step the sales order is created from the quotation, unless the quotation is owned by the Dynamics 365 Sales integration). HookingcreateJournal()is the true business moment; the table write is only its side effect. - Scope: only sales quotations. Project quotations are confirmed by
SalesQuotationEditLinesForm_Proj_Confir, a separate class this hook does not extend, so confirming a project quotation does not raise this event. - Pro forma: quotation confirmation has no pro forma posting path, so every confirmation journal is a real confirmation.
- Transaction: the hook runs inside the per-quotation transaction, before commit. This is transactionally safe because
BusinessEventsBase.send()only writes to the commit log inside the current transaction and actual delivery is asynchronous post-commit, so a tts-aborted confirmation never delivers. - Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeSalesQuotationConfirmedBusinessEvent))must return true before the event is built and sent. - The factory
MGCLatticeSalesQuotationConfirmedBusinessEvent::newFromCustQuotationConfirmJour(this.journal(), this.salesQuotationTable().QuotationId)passes the just-created journal record and the number of the quotation being confirmed (the journal does not store it) into the contract.
/// <summary>
/// Triggers the sales quotation confirmed business event when a confirmation journal is created.
/// </summary>
/// <returns>true if the journal was created; otherwise false.</returns>
public boolean createJournal()
{
boolean ret;
ret = next createJournal();
if (ret
&& this.journal().RecId
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeSalesQuotationConfirmedBusinessEvent)))
{
MGCLatticeSalesQuotationConfirmedBusinessEvent::newFromCustQuotationConfirmJour(this.journal(), this.salesQuotationTable().QuotationId).send();
}
return ret;
}
Payload Contract
One row per [DataMember], in declaration order. Source record: CustQuotationConfirmJour, mapped in initialize(), plus the number of the confirmed quotation passed in by the hook.
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | CustQuotationConfirmJour.DataAreaId | Company/legal entity of the confirmation journal |
quotationId | Quotation ID | QuotationIdBase | SalesQuotationTable.QuotationId of the quotation being confirmed | The quotation number |
confirmationId | Confirmation ID | QuotationConfirmId | CustQuotationConfirmJour.QuotationConfirmId | The quotation confirmation number, allocated from the quotation confirmation number sequence |
salesOrderId | Sales Order ID | SalesId | CustQuotationConfirmJour.SalesId | The sales order the confirmation created; empty when no sales order was created (a quotation owned by the Dynamics 365 Sales integration) |
customerAccount | Customer Account | CustAccount | CustQuotationConfirmJour.OrderAccount | Order/customer account on the quotation |
confirmationDate | Confirmation Date | TransDate | CustQuotationConfirmJour.QuotationDate | Confirmation date entered when confirming; serialized in the \/Date(milliseconds)\/ form |
crossReferences | Cross references | List of MGCLatticeCrossReferenceEventContract | MGCLatticeCrossReferenceEventHelper::listForSalesQuotation(quotationId, CustQuotationConfirmJour.DataAreaId) | Keys external systems hold for the sales quotation that was confirmed: 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 quotation's, not the journal's. |
Note: the confirmationDate field is exposed by the parm method parmTransDate (the parm method name differs from the JSON field name confirmationDate).
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": "MGCLatticeSalesQuotationConfirmedBusinessEvent",
"ControlNumber": 5637148712,
"EventId": "B8D30403-8C8D-456A-BD82-04DFD19EB07C",
"EventTime": "\/Date(1790083089000)\/",
"EventTimeIso8601": "2026-09-22T13:18:09.7238571Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{F6921A98-944E-C44E-A49A-36372D62B034}",
"ParentContextRecordSubjects": [],
"eventSequence": 48031,
"correlationId": "Event:70aa29d9-4e71-4062-8077-5a2d9b1707d3",
"schemaVersion": 1,
"legalEntity": "usmf",
"quotationId": "000077",
"confirmationId": "CQC-000002",
"salesOrderId": "001791",
"customerAccount": "US-004",
"confirmationDate": "\/Date(1790035200000)\/",
"crossReferences": [
{
"externalSystem": "CRM",
"externalKey": "QUO-2026-0412"
}
]
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Locate the event by name ("Sales quotation confirmed") or filter the Category column to Sales orders, select it, and activate it to a target endpoint (for example Azure Service Bus, Event Grid, or an HTTPS endpoint) for the desired legal entities.