Skip to main content

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 classMGCLatticeCollectionCaseChangedBusinessEvent
Contract classMGCLatticeCollectionCaseChangedBusinessEventContract
CategoryAccounts receivable
ModuleModuleAxapta::Customer
Label IDsCollectionCaseChangedBusinessEventName / CollectionCaseChangedBusinessEventDescription
TriggerCustCollectionsCaseDetail::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 action Opened. 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 action Closed. Standard D365 calls it from the Collections page's close-case action, once for each selected case.
  • Firing condition: unconditional once next has 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: createCase creates the case in the session's current company, so after it the case is re-read with CustCollectionsCaseDetail::find() and the customer with CustTable::findByPartyRecId() in that company. The Collections page lists cases across legal entities, and the standard closeCase can close a case that belongs to another legal entity (it finds the case cross-company and switches to its company). The closeCase wrapper 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, legalEntity is 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/ttscommit before 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 (for closeCase, 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 fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdCustTable.DataAreaIdCompany 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.
customerAccountCustomer AccountCustAccountCustTable.AccountNumCustomer the case is for.
customerNameCustomer NameNameCustTable.name()Party name resolved by the display method.
caseIdCase IdCaseIdCustCollectionsCaseDetail.CaseIdCase number.
caseStatusCase Statusintenum2int(CustCollectionsCaseDetail.Status)CaseStatus: 0 = None, 1 = Planned, 2 = InProcess, 3 = Closed, 4 = Canceled. A new case is 2; a closed case is 3.
caseStatusNameCase Status Namestrenum2Symbol(enumNum(CaseStatus), ...)InProcess when opened, Closed when closed.
caseActionCase Actionstrliteral passed by the wrapperOpened (from createCase) or Closed (from closeCase).
balanceMSTBalance MSTAmountMSTCustTable.balanceMST()The customer's balance in the accounting currency of the case's company, calculated when the event is built.
eventDateEvent DateTransDateDateTimeUtil::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).