ICustomerManagementDELETE Apex Interface

The ITroubleTicketManagementDELETE interface provides extensibility for DELETE operations through Apex-based validations, mutation-payload adjustments, and post-operation processing. Users can enforce deletion policies, apply soft-delete or metadata logic, and customize final responses without impacting TMF-compliant delete behavior.

This interface supports the following hooks.

These hooks are not used for the Trouble Ticket Management API. If implemented, the method may be invoked, but its return values are ignored.

  • configureDefaultValidations
  • customiseGraphQLQuery

Note

Deletion Lifecycle Use Cases and Hook Mapping 

This table lists common use cases and the hooks required for the DELETE operation.

Use CaseHook(s)Description
Example Scenarios
Benefit
Enforce Deletion RulesapplyCustomValidationsValidates whether a record is eligible for deletion by checking dependencies, business constraints, and user permissions. Prevents unsafe deletions that may violate data integrity or operational rules.• Block deletion if active child records exist• Enforce that only admins can delete high-value accounts• Prevent deletion when linked to open cases or agreementsPrevents accidental deletion of critical records and preserves data integrity
Maintain Audit TrailcustomiseMutationPayload, handlePostOperationEnriches the deletion request with audit metadata and records post-delete events, enabling traceability for compliance and regulatory review. Supports soft-delete or archival strategies.• Capture who deleted the record and timestamp• Add soft-delete flags before actual deletion• Trigger audit-event logging after deletionProvides a complete audit trail for compliance and monitoring
Support External IdentifiersresolveUniqueIdentifiersAllows records to be deleted using external IDs instead of Salesforce IDs. Enables seamless integration with external or legacy customer systems.• Delete by external CRM customer ID• Resolve telecom subscriber ID to internal account before deletion• Accept partner-system identifier for record lookupEnables interoperability with external platforms and simplifies integrations
Clean Up Related DatahandlePostOperationPerforms cascading cleanup of related or dependent data after a record has been deleted. Ensures the system remains consistent and free of orphaned or stale records.• Archive or remove associated contact mediums• Delete orphaned child records after parent deletion• Trigger asynchronous cleanup workflow for related dataMaintains data consistency and avoids accumulation of stale or orphaned records
CaseNumber to Id ResolutiontransformRequestResolve CaseNumber identifiers to Salesforce Ids for API compatibility.• An external system sends a CaseNumber (e.g., 00001027), which is resolved to a Salesforce record ID for API processingSupport external systems that use CaseNumber instead of Salesforce Id

transformRequest 

This hook transforms the request context before processing any operation. It is invoked early in the request lifecycle, allowing implementers to adjust the context for subsequent extensibility hooks.

Hook Method 

Map<String, Object> transformRequest(Map<String, Object> context)

Sample Apex Implementation 

Resolve Salesforce ID to CaseNumber

1public Map<String, Object> transformRequest(Map<String, Object> context) {
2    if (context == null) {
3        return context;
4    }
5
6
7    Object idObj = context.get('id');
8    if (idObj == null || !(idObj instanceof String)) {
9        return context;
10    }
11
12
13    String idValue = (String)idObj;
14
15
16    // Check if the ID looks like a Salesforce ID (starts with 500, 15 or 18 characters)
17    // If not, assume it's already a CaseNumber and return as-is
18    if (idValue.length() == 15 || idValue.length() == 18) {
19        // Looks like a Salesforce ID, resolve to CaseNumber
20        List<Case> cases = [
21            SELECT Id, CaseNumber
22            FROM Case
23            WHERE Id = :idValue
24            LIMIT 1
25        ];
26
27
28        // If found, replace the ID in context with the CaseNumber
29        if (!cases.isEmpty() && cases[0].CaseNumber != null) {
30            context.put('id', cases[0].CaseNumber);
31        }
32    }
33
34
35    return context;
36}

applyCustomValidations 

This hook validates custom business logic before deleting a trouble ticket data. If validation fails, rejects the request and returns an error response.

The handler processes return values as follows.

Success:

  • Return a map with validationStatus set to any value other than “fail” (case-insensitive).
  • The API request continues normally.
  • Other fields in the map are logged but not used.

Failure:

  • Return a map with validationStatus set to “fail” (case-insensitive).
  • The API request is terminated immediately.
  • Raise a ValidationException containing
    • message: value from validationMessage key (or “Validation failed” by default)
    • details: value from validationDetails key (optional)
  • The client receives an HTTP 400 error response.

Hook Method 

Map<String, Object> applyCustomValidations(Map<String, Object> context)

Sample Apex Implementation 

Validate Trouble Ticket Deletion Request

1public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
2    // Input context:
3    // {
4    //   api: 'TroubleTicketManagement',
5    //   version: '5.0',
6    //   id: '00001234' or '500xx000001ABCDEF'
7    // }
8
9    String troubleTicketId = (String) context.get('id');
10    Map<String, Object> validationResult = new Map<String, Object>();
11
12    try {
13        // Resolve CaseNumber to Salesforce ID if needed
14        String caseId = resolveCaseId(troubleTicketId);
15        if (String.isBlank(caseId)) {
16            validationResult.put('validationStatus', 'FAIL');
17            validationResult.put('validationMessage', 'Trouble ticket not found');
18            return validationResult;
19        }
20
21        // Query the Case to check its status
22        List<Case> cases = [
23            SELECT Id, Status, CaseNumber
24            FROM Case
25            WHERE Id = :caseId
26            LIMIT 1
27        ];
28
29        if (cases.isEmpty()) {
30            validationResult.put('validationStatus', 'FAIL');
31            validationResult.put('validationMessage', 'Trouble ticket not found');
32            return validationResult;
33        }
34
35        Case caseRecord = cases[0];
36
37        // Prevent deletion of closed cases
38        if (caseRecord.Status == 'Closed') {
39            validationResult.put('validationStatus', 'FAIL');
40            validationResult.put('validationMessage', 'Cannot delete closed trouble ticket');
41            validationResult.put('validationDetails', new Map<String, Object>{
42                'reason' => 'CASE_ALREADY_CLOSED',
43                'status' => caseRecord.Status,
44                'caseNumber' => caseRecord.CaseNumber
45            });
46            return validationResult;
47        }
48
49        // Prevent deletion of cases with open child cases
50        List<Case> childCases = [
51            SELECT Id FROM Case
52            WHERE ParentId = :caseId
53            AND Status != 'Closed'
54            LIMIT 1
55        ];
56
57        if (!childCases.isEmpty()) {
58            validationResult.put('validationStatus', 'FAIL');
59            validationResult.put('validationMessage', 'Cannot delete trouble ticket with open child cases');
60            validationResult.put('validationDetails', new Map<String, Object>{
61                'reason' => 'OPEN_CHILD_CASES_EXIST'
62            });
63            return validationResult;
64        }
65
66        // Check user permissions
67        if (!UserInfo.getProfileId().equals('00e000000000001')) { // Admin profile
68            validationResult.put('validationStatus', 'FAIL');
69            validationResult.put('validationMessage', 'User does not have permission to delete trouble tickets');
70            validationResult.put('validationDetails', new Map<String, Object>{
71                'requiredRole' => 'Administrator'
72            });
73            return validationResult;
74        }
75
76        // PASS: All validations passed
77        validationResult.put('validationStatus', 'PASS');
78        validationResult.put('validationMessage', 'Trouble ticket deletion validation passed');
79
80    } catch (Exception e) {
81        // FAIL: Error during validation
82        validationResult.put('validationStatus', 'FAIL');
83        validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
84    }
85
86    return validationResult;
87}
88
89
90/**
91 * Helper method to resolve CaseNumber to Salesforce ID
92 */
93private String resolveCaseId(String identifier) {
94    // If it's already a Salesforce ID (15 or 18 characters), return as-is
95    if (identifier.length() == 15 || identifier.length() == 18) {
96        return identifier;
97    }
98
99    // Otherwise, treat as CaseNumber and query for Salesforce ID
100    List<Case> cases = [
101        SELECT Id FROM Case
102        WHERE CaseNumber = :identifier
103        LIMIT 1
104    ];
105
106    return cases.isEmpty() ? null : cases[0].Id;
107}

customiseMutationPayload 

This hook modifies the mutation payload before deleting a trouble ticket data. Use this to add deletion metadata, set soft-delete flags, or apply business logic transformations.

Hook Method 

Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context)

Sample Apex Implementation 

Add Deletion Metadata and Clean Up Related Records

1public Map<String, Object> customiseMutationPayload(
2    Map<String, Object> request,
3    Map<String, Object> context
4) {
5    String troubleTicketId = (String) context.get('id');
6    String caseId = resolveCaseId(troubleTicketId);
7
8    return new Map<String, Object>{
9        'nodes' => new List<Object>{
10            // Delete related CaseComments before deleting the Case
11            new Map<String, Object>{
12                'path' => 'deleteCaseComments',
13                'insertGraphQlSnippet' => 'deleteCaseComments: CaseCommentDelete(input: { Id: "00a000000000001AAA" }) { Id }'
14            },
15            // Unlink Case from Account before deletion
16            new Map<String, Object>{
17                'path' => 'unlinkCase',
18                'insertGraphQlSnippet' => 'unlinkCase: CaseUpdate(input: { Id: "' + caseId + '" Case: { AccountId: null, ContactId: null } }) { Record { Id AccountId { value } ContactId { value } } }'
19            },
20            // Delete the Case itself
21            new Map<String, Object>{
22                'path' => 'deleteCase',
23                'insertGraphQlSnippet' => 'deleteCase: CaseDelete(input: { Id: "' + caseId + '" }) { Id }'
24            }
25        }
26    };
27}

handlePostOperation 

This hook post-processes and transforms the API response after data is deleted.

Hook Method 

Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)

Sample Apex Implementation 

1public Map<String, Object> handlePostOperation(
2    Map<String, Object> graphQLQueryResultAsMap,
3    Map<String, Object> constructedTMFResponse,
4    Map<String, Object> context
5) {
6    String troubleTicketId = (String) context.get('id');
7
8    // Log deletion event
9    System.debug('Trouble ticket deleted: ' + troubleTicketId);
10    System.debug('Deleted by user: ' + UserInfo.getUserId());
11    System.debug('Deleted at: ' + System.now());
12
13    // You can also create audit records, send notifications, etc.
14    // Note: Return value is ignored, so any processing here is for side effects only
15
16    return null;
17}

resolveUniqueIdentifiers 

This hook resolves external identifiers (for example, CaseNumber) to Salesforce record IDs and is invoked for batch resolution of multiple identifiers.

Hook Method 

Map<String, String> resolveUniqueIdentifiers(List<String> ids)

Sample Apex Implementation 

1public Map<String, String> resolveUniqueIdentifiers(List<String> ids) {
2    Map<String, String> resolvedIds = new Map<String, String>();
3
4    try {
5        // Query Case objects where CaseNumber matches any of the provided IDs
6        List<Case> cases = [
7            SELECT Id, CaseNumber
8            FROM Case
9            WHERE CaseNumber IN :ids
10        ];
11
12        // Build map of CaseNumber to Salesforce ID
13        for (Case caseRecord : cases) {
14            if (caseRecord.CaseNumber != null) {
15                resolvedIds.put(caseRecord.CaseNumber, caseRecord.Id);
16            }
17        }
18
19    } catch (Exception e) {
20        System.debug('Error resolving IDs: ' + e.getMessage());
21    }
22
23    return resolvedIds;
24}

Full Implementation Example 

Here’s a complete sample Apex implementation using all supported hooks.

1/**
2 * Complete Trouble Ticket Management DELETE API Extension
3 * Demonstrates all applicable lifecycle hooks with business logic
4 */
5public class TroubleTicketManagementDELETEExtension implements comms_apex_ext.ITroubleTicketManagementDELETE {
6
7    /**
8     * Hook 1: Transform request context
9     */
10    public Map<String, Object> transformRequest(Map<String, Object> context) {
11        if (context == null) {
12            return context;
13        }
14
15        Object idObj = context.get('id');
16        if (idObj == null || !(idObj instanceof String)) {
17            return context;
18        }
19
20        String idValue = (String)idObj;
21
22        // If it's not a Salesforce ID format, treat as CaseNumber and resolve
23        if (idValue.length() != 15 && idValue.length() != 18) {
24            List<Case> cases = [
25                SELECT Id, CaseNumber
26                FROM Case
27                WHERE CaseNumber = :idValue
28                LIMIT 1
29            ];
30
31            if (!cases.isEmpty() && cases[0].Id != null) {
32                context.put('id', cases[0].Id);
33            }
34        }
35
36        return context;
37    }
38
39    /**
40     * Hook 2: Apply custom validations
41     */
42    public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
43        String troubleTicketId = (String) context.get('id');
44        Map<String, Object> validationResult = new Map<String, Object>();
45
46        try {
47            String caseId = resolveCaseId(troubleTicketId);
48            if (String.isBlank(caseId)) {
49                validationResult.put('validationStatus', 'FAIL');
50                validationResult.put('validationMessage', 'Trouble ticket not found');
51                return validationResult;
52            }
53
54            // Check if case is closed
55            List<Case> cases = [
56                SELECT Id, Status FROM Case
57                WHERE Id = :caseId AND Status = 'Closed' LIMIT 1
58            ];
59
60            if (!cases.isEmpty()) {
61                validationResult.put('validationStatus', 'FAIL');
62                validationResult.put('validationMessage', 'Cannot delete closed trouble ticket');
63                return validationResult;
64            }
65
66            validationResult.put('validationStatus', 'PASS');
67            validationResult.put('validationMessage', 'Validation passed');
68
69        } catch (Exception e) {
70            validationResult.put('validationStatus', 'FAIL');
71            validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
72        }
73
74        return validationResult;
75    }
76
77    /**
78     * Hook 3: Customize mutation payload
79     */
80    public Map<String, Object> customiseMutationPayload(
81        Map<String, Object> request,
82        Map<String, Object> context
83    ) {
84        String troubleTicketId = (String) context.get('id');
85        String caseId = resolveCaseId(troubleTicketId);
86
87        return new Map<String, Object>{
88            'nodes' => new List<Object>{
89                new Map<String, Object>{
90                    'path' => 'unlinkCase',
91                    'insertGraphQlSnippet' => 'unlinkCase: CaseUpdate(input: { Id: "' + caseId + '" Case: { AccountId: null, ContactId: null } }) { Record { Id AccountId { value } ContactId { value } } }'
92                },
93                new Map<String, Object>{
94                    'path' => 'deleteCase',
95                    'insertGraphQlSnippet' => 'deleteCase: CaseDelete(input: { Id: "' + caseId + '" }) { Id }'
96                }
97            }
98        };
99    }
100
101    /**
102     * Hook 4: Resolve unique identifiers
103     */
104    public Map<String, String> resolveUniqueIdentifiers(List<String> ids) {
105        Map<String, String> resolvedIds = new Map<String, String>();
106
107        try {
108            List<Case> cases = [
109                SELECT Id, CaseNumber
110                FROM Case
111                WHERE CaseNumber IN :ids
112            ];
113
114            for (Case caseRecord : cases) {
115                if (caseRecord.CaseNumber != null) {
116                    resolvedIds.put(caseRecord.CaseNumber, caseRecord.Id);
117                }
118            }
119
120        } catch (Exception e) {
121            System.debug('Error resolving IDs: ' + e.getMessage());
122        }
123
124        return resolvedIds;
125    }
126
127    /**
128     * Hook 5: Post-process response
129     */
130    public Map<String, Object> handlePostOperation(
131        Map<String, Object> graphQLQueryResultAsMap,
132        Map<String, Object> constructedTMFResponse,
133        Map<String, Object> context
134    ) {
135       /*
136      TODO Any post processing to be performed.
137       */
138
139        return null;
140    }
141
142    /**
143     * Helper method to resolve CaseNumber to Salesforce ID
144     */
145    private String resolveCaseId(String identifier) {
146        if (identifier.length() == 15 || identifier.length() == 18) {
147            return identifier;
148        }
149
150        List<Case> cases = [
151            SELECT Id FROM Case
152            WHERE CaseNumber = :identifier
153            LIMIT 1
154        ];
155
156        return cases.isEmpty() ? null : cases[0].Id;
157    }
158
159    /**
160     * NOT APPLICABLE: configureDefaultValidations is not used for Trouble Ticket Management DELETE API
161     */
162    public Map<String, Boolean> configureDefaultValidations(
163        Map<String, Boolean> defaultValidationConfiguration,
164        Map<String, Object> context
165    ) {
166        return defaultValidationConfiguration;
167    }
168
169    /**
170     * NOT APPLICABLE: customiseGraphQLQuery is not used for Trouble Ticket Management DELETE API
171     */
172    public Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context) {
173        return null;
174    }
175}

GraphQL Mutation - Delete Operations 

Delete mutations remove records. The delete transformation supports inserting GraphQL snippets to perform related operations, such as unlinking relationships before deletion or creating audit logs. The insertGraphQlSnippet instruction allows you to embed complete GraphQL operations within a mutation and serves as the primary mechanism for extending delete operations.

Delete Enhancements: Unlink Before Delete 

Transformation Instructions (JSON):

1{
2  "nodes": [
3    {
4      "path": "unlinkCase",
5      "insertGraphQlSnippet": "unlinkCase: CaseUpdate(input: { Id: \"500xx000000bnecAAA\" Case: { ContactId: null, AccountId, null } }) { Record { Id ContactId { value } AccountId { value } } }"
6    },
7    {
8      "path": "deleteCase",
9      "insertGraphQlSnippet": "deleteCase: CaseDelete(input: { Id: \"500xx000000bnecAAA\" }) { Id }"
10    }
11  ]
12}

Result (GraphQL):

1mutation DeleteCase {
2  uiapi(input: { allOrNone: true }) {
3    unlinkCase: CaseUpdate(input: { Id: "500xx000000bnecAAA" Case: { ContactId: null, AccountId: null } }) {
4      Record {
5        Id
6        ContactId {
7          value
8        }
9        AccountId {
10          value
11        }
12      }
13    }
14    deleteCase: CaseDelete(input: { Id: "500xx000000bnecAAA" }) {
15      Id
16    }
17  }
18}