Sales price agreement deleted
Fires when a sales-price trade agreement (a PriceDiscTable row with relation Price (sales)) is deleted - when a user deletes the price, when it is deleted through a data entity or by code through the table's delete() method, and when a posted trade-agreement journal deletes it through a line marked for deletion. The payload is captured while the row still exists, so it describes the price that no longer exists; subscribers can use it to withdraw the price from storefronts, CPQ tools or external pricing engines.
| Event class | MGCLatticeTradeAgreementPriceDeletedBusinessEvent |
| Contract class | MGCLatticeTradeAgreementPriceBusinessEventContract |
| Category | Sales orders |
| Module | ModuleAxapta::SalesOrder |
| Label IDs | TradeAgrPriceDeletedName / TradeAgrPriceDeletedDesc |
| Trigger | PriceDiscTable.delete() -> after the row is deleted, when Relation == PriceType::PriceSales; and PriceDiscAdmTable::deleteMarked() -> the sales prices a posted journal deletes |
Trigger / Hook
Which path raises which event
The six sales price agreement events share one payload contract and are told apart by changeType / changeTypeName. All six are limited to sales price agreements - PriceDiscTable rows whose relation is Price (sales) (PriceType::PriceSales); purchase prices and discount agreements never raise them, and journal lines with another relation are ignored. Below, agreement means a row's account code and account relation, item code and item relation, currency, unit, quantity break (from quantity) and inventory dimensions; the dates and the amount are not part of it.
| Event | Raised by | Fires when |
|---|---|---|
| Sales price agreement created | Posting a trade-agreement journal | A posted journal line inserted a sales price for an agreement that had no sales price in effect on the new price's From date |
| Sales price agreement modified | Posting a trade-agreement journal | A posted journal line that references an existing sales price changed at least one of its values |
| Sales price agreement superseded | Posting a trade-agreement journal | A posted journal line inserted a sales price for an agreement that already had a sales price in effect on the new price's From date |
| Sales price agreement deleted (this event) | PriceDiscTable.delete(), and posting a journal line marked for deletion | A sales price is deleted |
| Sales price agreement effective | Effectivity batch (background process, every legal entity) | A sales price's From date is after the batch's position and on or before the run date |
| Sales price agreement expired | Effectivity batch (background process, every legal entity) | A sales price's To date is after the batch's position and before the run date, and no other sales price of the same agreement is in effect on the day after that To date |
Sales-price rows that are inserted or updated outside a journal post - for example through a data entity or by code that calls PriceDiscTable.insert() or update() - are not classified by these events; the record-based Sales price trade agreement changed event (catalog category Accounts receivable) reports them. The journal post itself writes PriceDiscTable with set-based statements; whether those writes also raise the record-based event depends on the Accounts receivable parameters Skip price agreement insert alerts (rows the post inserts) and Skip price agreement update alerts (rows it updates). Set to Yes, a journal post raises only the events in the table above; set to No, the record-based event is also raised for each such row.
Delete hook
- Extension class:
MGCLatticePriceDiscTable_Table_Extension, decorated with[ExtensionOf(tableStr(PriceDiscTable))]. - CoC method signature:
public void delete(). Whether to send is decided beforenext delete(), while the row still exists; the event is sent afternext delete()from the same buffer, which keeps the row's values, so the payload describes the price as it was at deletion and a delete that fails sends nothing. - Firing condition:
this.Relation == PriceType::PriceSales. Every sales-price row deleted throughdelete()raises the event once, whatever started the delete - except while a posted journal deletes its marked lines, which the journal hook below reports instead.
Journal hook (lines marked for deletion)
- Extension class:
MGCLatticePriceDiscAdmTable_Table_Extension, decorated with[ExtensionOf(tableStr(PriceDiscAdmTable))], wrappingpublic static void deleteMarked(PriceDiscJournalNum _journalNum)- the method the trade-agreement journal post uses to delete the rows its lines marked for deletion refer to. - The standard code deletes those rows either one at a time through
delete()or with a single set-baseddelete_fromthat skipsdelete(), depending on Microsoft feature flights in your application version (it always uses the set-based delete when a marked line refers to a generic-currency agreement). So the hook does not rely ondelete(): beforenext,MGCLatticeTradeAgreementPriceDeleteScopecaptures the sales-price rows the marked lines refer to; while the delete runs, the scope keepsdelete()from sending its own event; after the delete, one event is sent per captured row. Each deleted sales price is reported exactly once on either path. - There is no per-journal cap for this event: a journal that deletes many sales prices raises one event per price.
Common to both hooks
- Deletes that skip table methods outside a journal post (for example a set-based delete run by custom code with
skipDataMethods) do not rundelete()and raise nothing. - Transaction and failure handling: the event is sent from within the delete (or the journal post). When that runs inside a transaction (as interactive deletes and journal posting do),
send()only writes to the business events commit log and delivery happens after commit, so a delete that is rolled back never delivers. Inside a transaction an X++catchblock does not run, so the hooks do not rely on one: they only read values that cannot fail and send. - Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeTradeAgreementPriceDeletedBusinessEvent))must return true; the journal hook does not even capture the rows when it is not. - No Lattice parameters or other setup are required.
The CoC wrappers:
public void delete()
{
boolean raiseDeleted = this.RecId != 0
&& this.Relation == PriceType::PriceSales
&& !MGCLatticeTradeAgreementPriceDeleteScope::isActive()
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeTradeAgreementPriceDeletedBusinessEvent));
next delete();
if (raiseDeleted)
{
MGCLatticeTradeAgreementPriceDeletedBusinessEvent::newFromPriceDiscTable(this).send();
}
}
public static void deleteMarked(PriceDiscJournalNum _journalNum)
{
using (MGCLatticeTradeAgreementPriceDeleteScope scope = MGCLatticeTradeAgreementPriceDeleteScope::newForJournal(_journalNum))
{
next deleteMarked(_journalNum);
scope.raiseDeleted();
}
}
Payload Contract
One row per [DataMember], in declaration order. All six sales price agreement events use this contract; the sources are fields on the PriceDiscTable row being deleted, mapped in initialize().
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | PriceDiscTable.DataAreaId | Company of the trade agreement row |
changeType | Change Type | int | enum2int(MGCLatticeTradeAgreementPriceChangeType) | Integer change type: Created 0, Modified 1, Superseded 2, Deleted 3, Effective 4, Expired 5. Always 3 for this event |
changeTypeName | Change Type Name | str | enum2Symbol(enumNum(MGCLatticeTradeAgreementPriceChangeType), …) | Change type symbol; always "Deleted" for this event |
priceType | Price Type | int | enum2int(PriceDiscTable.Relation) | Integer value of the PriceType enum; always 4 (PriceSales) |
priceTypeName | Price Type Name | str | enum2Symbol(enumNum(PriceType), enum2int(PriceDiscTable.Relation)) | Always "PriceSales" |
accountCode | Account Code | int | enum2int(PriceDiscTable.AccountCode) | PriceDiscPartyCodeType: 0 = Table (one customer), 1 = GroupId (customer price group), 2 = All (all customers) |
accountCodeName | Account Code Name | str | enum2Symbol(enumNum(PriceDiscPartyCodeType), …) | Symbol of accountCode: Table, GroupId or All |
accountRelation | Account Relation | PriceDiscAccountRelation | PriceDiscTable.AccountRelation | Customer account or price group; empty when accountCode is 2 |
itemCode | Item Code | int | enum2int(PriceDiscTable.ItemCode) | PriceDiscProductCodeType: 0 = Table (one item), 1 = GroupId, 2 = All |
itemCodeName | Item Code Name | str | enum2Symbol(enumNum(PriceDiscProductCodeType), …) | Symbol of itemCode: Table, GroupId or All |
itemRelation | Item Relation | PriceDiscItemRelation | PriceDiscTable.ItemRelation | Item number (or item group) |
currencyCode | Currency Code | CurrencyCode | PriceDiscTable.Currency | Currency of the price |
unitId | Unit | UnitOfMeasureSymbol | PriceDiscTable.UnitId | Unit of measure the price applies to |
quantityAmountFrom | Quantity From | AmountQty | PriceDiscTable.QuantityAmountFrom | From quantity of the row's quantity break |
quantityAmountTo | Quantity To | AmountQty | PriceDiscTable.QuantityAmountTo | To quantity of the row's quantity break; 0 when the break has no upper limit |
inventDimId | Inventory Dimensions | InventDimId | PriceDiscTable.InventDimId | Inventory dimension combination the price applies to; AllBlank when the price is not dimension-specific |
priceUnit | Price Unit | PriceUnit | PriceDiscTable.PriceUnit | Number of units the amount is quoted for |
fromDate | From Date | FromDate | PriceDiscTable.FromDate | Start of the validity period, in the \/Date(milliseconds)\/ form. A row without a start date carries 1900-01-01 (\/Date(-2208988800000)\/) |
toDate | To Date | ToDate | PriceDiscTable.ToDate | Last valid day, in the same form. An open-ended price carries 1900-01-01 (\/Date(-2208988800000)\/) |
amount | Amount | PriceDiscAmount | PriceDiscTable.Amount | The price of the deleted row |
oldAmount | Old Amount | PriceDiscAmount | Not used by this event | Always 0 |
discountPercent | Discount Percent | DiscPct | PriceDiscTable.Percent1 | Discount percentage stored on the row (Percent1); normally 0 on a sales price row |
priceDiscTableRecId | Price Disc Table RecId | RecId | PriceDiscTable.RecId | Record ID of the deleted row |
previousPriceDiscTableRecId | Previous Price Disc Table RecId | RecId | Not used by this event | Always 0 |
changedFields | Changed Fields | list of str | Not used by this event | Always an empty array |
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": "MGCLatticeTradeAgreementPriceDeletedBusinessEvent",
"ControlNumber": 5637148701,
"EventId": "DDDCAF3D-7B88-4FB7-9BAC-1F2FBF0ABFF2",
"EventTime": "\/Date(1790071541000)\/",
"EventTimeIso8601": "2026-09-22T10:05:41.2828601Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{5309BF51-C4DB-8D08-CFC2-E5DD282A2DEF}",
"ParentContextRecordSubjects": [],
"eventSequence": 48019,
"correlationId": "Event:d55aa3c9-6516-46b6-bd06-7919b9e2e763",
"schemaVersion": 1,
"legalEntity": "usmf",
"changeType": 3,
"changeTypeName": "Deleted",
"priceType": 4,
"priceTypeName": "PriceSales",
"accountCode": 0,
"accountCodeName": "Table",
"accountRelation": "US-001",
"itemCode": 0,
"itemCodeName": "Table",
"itemRelation": "D0011",
"currencyCode": "USD",
"unitId": "ea",
"quantityAmountFrom": 0,
"quantityAmountTo": 0,
"inventDimId": "AllBlank",
"priceUnit": 1,
"fromDate": "\/Date(-2208988800000)\/",
"toDate": "\/Date(-2208988800000)\/",
"amount": 979.00,
"oldAmount": 0,
"discountPercent": 0,
"priceDiscTableRecId": 22565424064,
"previousPriceDiscTableRecId": 0,
"changedFields": []
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Sales price agreement deleted") 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). No other setup is needed.
Related Events
- Sales price agreement created
- Sales price agreement modified
- Sales price agreement superseded
- Sales price agreement effective
- Sales price agreement expired
- Sales price trade agreement changed - record-based complement for sales-price rows inserted or updated outside a journal post