Dunning level changed
Fires when a collection letter moves an open customer transaction to a different collection letter (dunning) level - one event per transaction whose level actually changes. With the standard setting this happens when the letter is posted. The payload carries the customer, the collection letter number, the transaction's new and previous level, and the transaction's invoice, voucher, dates and remaining amount, so collections tools can track which invoices were escalated and to which level. The fee transaction that posting adds to a letter is not a dunned transaction and is not reported.
| Event class | MGCLatticeDunningLevelChangedBusinessEvent |
| Contract class | MGCLatticeDunningLevelChangedBusinessEventContract |
| Category | Accounts receivable |
| Module | ModuleAxapta::Customer |
| Label IDs | DunningLevelChangedBusinessEventName / DunningLevelChangedBusinessEventDescription |
| Trigger | CustCollectionLetterJour.updateCollectionLetterCodeCustTrans() -> an open transaction's collection letter code changes |
Trigger / Hook
- Extension class:
MGCLatticeCustCollectionLetterJour_Table_Extension,[ExtensionOf(tableStr(CustCollectionLetterJour))], wrappingpublic void updateCollectionLetterCodeCustTrans()with Chain of Command. The capture, comparison and sending live inMGCLatticeCollectionsEventHandlers(captureDunningLevels(),findDunningLevelChanges(),raiseDunningLevelChanges()). - What the standard method does: it sets each open customer transaction on the letter (
CustTransOpen.CollectionLetterCode) to the level of the letter's line for it, raises the customer transaction's own level (CustTrans.CollectionLetterCode) when the new level is higher, and - when collection letters are generated per customer - sets the customer's level. - When it runs: Post collection letters calls it for every letter it posts, right after the letter is saved as posted. When the Accounts receivable parameter Update collection letter code is set to Printout, printing a letter for the first time also calls it, so the levels - and this event - change at printing; posting the letter afterwards finds nothing left to change.
- Firing condition: before the standard method runs, the wrapper captures the level of every open transaction referenced by the letter's issued lines, excluding the fee line (
CustCollectionLetterTrans.Fee= Yes). After it runs, it compares, and sends one event for each transaction whose level is now different. A transaction that is already at the letter's level (for example one carried over unchanged into a customer-level letter) raises nothing, and a canceled letter raises nothing. - Not reported: the customer-level code that per-customer letter generation sets on the customer record; levels reset by canceling a letter; lines of letters created before open transactions were linked to them.
- Creating letters: Create collection letters inserts the letter lines with a single set-based statement and raises nothing. Nothing in Lattice subscribes to the letter lines' data events, so that statement keeps running set-based.
- Timing: the capture, the standard update and the events share one transaction (inside the posting run's transaction when posting);
send()writes to the business events commit log and delivery happens asynchronously after commit, so a change that is rolled back never delivers. - Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeDunningLevelChangedBusinessEvent)), checked before anything is captured.
public void updateCollectionLetterCodeCustTrans()
{
boolean announce = MGCLatticeCollectionsEventHandlers::mustAnnounceDunningLevelChanges(this);
Map previousLevels;
ttsbegin;
if (announce)
{
previousLevels = MGCLatticeCollectionsEventHandlers::captureDunningLevels(this);
}
next updateCollectionLetterCodeCustTrans();
if (announce)
{
MGCLatticeCollectionsEventHandlers::raiseDunningLevelChanges(this, previousLevels);
}
ttscommit;
}
Payload Contract
One row per [DataMember], in declaration order. The levels are the open transaction's CollectionLetterCode before and after the change; the other fields come from the transaction's line on the collection letter (CustCollectionLetterTrans), which D365 fills from the customer transaction and its open-transaction record when the letter is created.
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | CustCollectionLetterTrans.DataAreaId | Company of the letter, in lower case (for example usmf). |
customerAccount | Customer Account | CustAccount | CustCollectionLetterTrans.AccountNum | Customer. |
collectionLetterNum | Collection Letter Number | CollectionLetterNum | CustCollectionLetterTrans.CollectionLetterNum | Letter that changed the level; matches the letter's Collection letter posted event. |
collectionLetterCode | Dunning Level | int | CustTransOpen.CollectionLetterCode after the change | The new level. CustCollectionLetterCode: 0 = None, 1 = CollectionLetter1, 2 = CollectionLetter2, 3 = CollectionLetter3, 4 = CollectionLetter4, 5 = Collection, 6 = All, 7 = CollectionPerCust. |
collectionLetterCodeName | Dunning Level Name | str | enum2Symbol(enumNum(CustCollectionLetterCode), ...) | Symbol of the new level, for example CollectionLetter2. |
previousCollectionLetterCode | Previous Dunning Level | int | CustTransOpen.CollectionLetterCode before the change | The level the transaction had before; 0 (None) the first time it is dunned. |
previousCollectionLetterCodeName | Previous Dunning Level Name | str | enum2Symbol(enumNum(CustCollectionLetterCode), ...) | Symbol of the previous level, for example CollectionLetter1 or None. |
invoiceId | Invoice ID | InvoiceId | CustCollectionLetterTrans.Invoice | Invoice number of the customer transaction (CustTrans.Invoice); empty for a transaction without one. |
remainAmount | Remaining Amount | AmountCur | CustCollectionLetterTrans.RemainAmount | Open amount of the transaction as shown on the letter (taken when the letter was created), in currencyCode. |
currencyCode | Currency Code | CurrencyCode | CustCollectionLetterTrans.CurrencyCode | Transaction currency. |
voucher | Voucher | Voucher | CustCollectionLetterTrans.Voucher | Voucher of the customer transaction. |
transDate | Transaction Date | TransDate | CustCollectionLetterTrans.TransDate | Date of the open transaction (date, \/Date(milliseconds)\/ form). |
dueDate | Due Date | DueDate | CustCollectionLetterTrans.DueDate | Due date of the open transaction (date, \/Date(milliseconds)\/ form). |
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 raised inside the business transaction, not when it commits; it orders the events of one business record, gaps are permanent and normal, and it must not be used to re-sequence the whole stream), correlationId (String, always Scope:Key - groups emissions that belong to one logical run; an event that joins no run carries Event: followed by a lower-case GUID unique to this emission) 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": "MGCLatticeDunningLevelChangedBusinessEvent",
"ControlNumber": 5637148803,
"EventId": "8D4B0E27-1A6F-4C39-9B72-E6C3A5F18D04",
"EventTime": "\/Date(1790338173000)\/",
"EventTimeIso8601": "2026-09-25T12:09:33.6080091Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{B69222CB-8BD8-BA48-51B0-11FAD0E15FF0}",
"ParentContextRecordSubjects": [],
"eventSequence": 48434,
"correlationId": "Event:6a1c9e53-b2f7-4d08-a5c4-19e8d0b7f362",
"schemaVersion": 1,
"legalEntity": "usmf",
"customerAccount": "US-007",
"collectionLetterNum": "000023",
"collectionLetterCode": 2,
"collectionLetterCodeName": "CollectionLetter2",
"previousCollectionLetterCode": 1,
"previousCollectionLetterCodeName": "CollectionLetter1",
"invoiceId": "CIV-000612",
"remainAmount": 4280.00,
"currencyCode": "USD",
"voucher": "ARI-000612",
"transDate": "\/Date(1787616000000)\/",
"dueDate": "\/Date(1788220800000)\/"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Dunning level changed") or by filtering the Category column to Accounts receivable. 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).
Related Events
- Collection letter posted - the posted letter these transactions belong to; with the standard setting it is raised in the same posting run, just before these events.
- Collection case changed - collections cases opened or closed for the customer.
- Customer hold status changed - raised when posting a blocking collection letter changes the customer's Blocked status.