Collection case changed
Fires when a customer's collections case is opened or closed in Credit and collections. The payload carries the customer, the case ID, the case status, the action (Opened or Closed) and the customer's current balance, so credit teams can mirror collections activity in a CRM or ticketing system and drive external follow-up. Together with Collection letter posted and Dunning level changed it makes up the collections family of events.
| Event class | MGCLatticeCollectionCaseChangedBusinessEvent |
| Contract class | MGCLatticeCollectionCaseChangedBusinessEventContract |
| Category | Accounts receivable |
| Module | ModuleAxapta::Customer |
| Label IDs | CollectionCaseChangedBusinessEventName / CollectionCaseChangedBusinessEventDescription |
| Trigger | CustCollectionsCaseDetail::createCase() / closeCase() -> after a collections case is created (Opened) or closed (Closed) |
Trigger / Hook
- Extension class:
MGCLatticeCustCollectionsCaseDetail_Table_Extension,[ExtensionOf(tableStr(CustCollectionsCaseDetail))], wrapping two static table methods with Chain of Command:public static recId createCase(RefRecId _caseCategoryRecId, DirPartyRecId _partyRecId, Description _description)- sends the event with actionOpened. Standard D365 calls it when a user creates a new case from the Collections page (the new case dialog). The case is created with status In process.public static void closeCase(RefRecId _caseRecId, RefRecId _custTableRecId, boolean _shouldResolveDisputedTransactions)- sends the event with actionClosed. Standard D365 calls it from the Collections page's close-case action, once for each selected case.
- Firing condition: unconditional once
nexthas returned; the case is then re-read so the payload shows its state after the change. Only these two methods raise the event - changing a case's status any other way (for example directly on the case record) does not. - Company scope:
createCasecreates the case in the session's current company, so after it the case is re-read withCustCollectionsCaseDetail::find()and the customer withCustTable::findByPartyRecId()in that company. The Collections page lists cases across legal entities, and the standardcloseCasecan close a case that belongs to another legal entity (it finds the case cross-company and switches to its company). ThecloseCasewrapper does the same: it reads the case cross-company by record ID and, in the case's company, reads the customer by record ID (falling back to the customer of the case's party), builds the payload and sends the event. The customer's balance is therefore calculated in the case's company,legalEntityis the case's company, and the event is raised for - and must be activated in - that legal entity. - Timing: the standard methods complete their own
ttsbegin/ttscommitbefore the event is sent;send()joins any transaction the caller still has open, and delivery happens asynchronously after commit. - Enablement gate:
BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeCollectionCaseChangedBusinessEvent)), checked in both wrappers (forcloseCase, in the case's company).
public static recId createCase(RefRecId _caseCategoryRecId, DirPartyRecId _partyRecId, Description _description)
{
recId caseRecId = next createCase(_caseCategoryRecId, _partyRecId, _description);
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeCollectionCaseChangedBusinessEvent)))
{
CustCollectionsCaseDetail caseDetail = CustCollectionsCaseDetail::find(caseRecId);
CustTable custTable = CustTable::findByPartyRecId(_partyRecId);
MGCLatticeCollectionCaseChangedBusinessEvent::newFromCase(caseDetail, custTable, 'Opened').send();
}
return caseRecId;
}
public static void closeCase(RefRecId _caseRecId, RefRecId _custTableRecId, boolean _shouldResolveDisputedTransactions)
{
next closeCase(_caseRecId, _custTableRecId, _shouldResolveDisputedTransactions);
CustCollectionsCaseDetail caseDetail;
// The case can belong to another legal entity than the session's; standard closeCase finds it the same way.
select firstonly crosscompany caseDetail
where caseDetail.RecId == _caseRecId;
if (caseDetail.RecId)
{
changecompany(caseDetail.company())
{
if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeCollectionCaseChangedBusinessEvent)))
{
CustTable custTable;
select firstonly custTable
where custTable.RecId == _custTableRecId;
if (!custTable.RecId)
{
custTable = CustTable::findByPartyRecId(caseDetail.Party);
}
MGCLatticeCollectionCaseChangedBusinessEvent::newFromCase(caseDetail, custTable, 'Closed').send();
}
}
}
}
Payload Contract
One row per [DataMember], in declaration order. initialize() reads the re-read case (CustCollectionsCaseDetail), the customer (CustTable) and the action string passed by the wrapper.
| JSON field | Display label | Type / EDT | Source | Notes |
|---|---|---|---|---|
legalEntity | Legal Entity | DataAreaId | CustTable.DataAreaId | Company of the customer record, which is the case's company, in lower case (for example usmf); empty only if no customer record is found for the case. |
customerAccount | Customer Account | CustAccount | CustTable.AccountNum | Customer the case is for. |
customerName | Customer Name | Name | CustTable.name() | Party name resolved by the display method. |
caseId | Case Id | CaseId | CustCollectionsCaseDetail.CaseId | Case number. |
caseStatus | Case Status | int | enum2int(CustCollectionsCaseDetail.Status) | CaseStatus: 0 = None, 1 = Planned, 2 = InProcess, 3 = Closed, 4 = Canceled. A new case is 2; a closed case is 3. |
caseStatusName | Case Status Name | str | enum2Symbol(enumNum(CaseStatus), ...) | InProcess when opened, Closed when closed. |
caseAction | Case Action | str | literal passed by the wrapper | Opened (from createCase) or Closed (from closeCase). |
balanceMST | Balance MST | AmountMST | CustTable.balanceMST() | The customer's balance in the accounting currency of the case's company, calculated when the event is built. |
eventDate | Event Date | TransDate | DateTimeUtil::getToday(DateTimeUtil::getUserPreferredTimeZone()) | The current date in the time zone of the user whose action raised the event - the real date, not the session date, and not the server's UTC date, which in the evening west of UTC is already the next day. Sent as midnight UTC in the \/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": "MGCLatticeCollectionCaseChangedBusinessEvent",
"ControlNumber": 5637148826,
"EventId": "0F6A3D98-7B21-4E54-8C0B-D2A9E4F17C63",
"EventTime": "\/Date(1790344521000)\/",
"EventTimeIso8601": "2026-09-25T13:55:21.4782903Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{CE41A5C9-D2D5-45F0-34BB-BC86C784D483}",
"ParentContextRecordSubjects": [],
"eventSequence": 48460,
"correlationId": "Event:e3b98c12-4f0a-4d6e-9a27-5c1d8b6f04a9",
"schemaVersion": 1,
"legalEntity": "usmf",
"customerAccount": "US-007",
"customerName": "Desert Wholesales",
"caseId": "CAS-000042",
"caseStatus": 2,
"caseStatusName": "InProcess",
"caseAction": "Opened",
"balanceMST": 18734.50,
"eventDate": "\/Date(1790294400000)\/"
}
Activation
In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Collection case 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 - collection letters posted for the customer.
- Dunning level changed - customer transactions moved to a new collection letter level.
- Customer credit limit exceeded - a common reason to open a collections case.
- Customer hold status changed - the customer's Blocked status changed.