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.
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 Case
Hook(s)
Description
Example Scenarios
Benefit
Enforce Deletion Rules
applyCustomValidations
Validates 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 agreements
Prevents accidental deletion of critical records and preserves data integrity
Maintain Audit Trail
customiseMutationPayload, handlePostOperation
Enriches 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 deletion
Provides a complete audit trail for compliance and monitoring
Support External Identifiers
resolveUniqueIdentifiers
Allows 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 lookup
Enables interoperability with external platforms and simplifies integrations
Clean Up Related Data
handlePostOperation
Performs 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 data
Maintains data consistency and avoids accumulation of stale or orphaned records
CaseNumber to Id Resolution
transformRequest
Resolve 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 processing
Support 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.
1public Map<String, Object> transformRequest(Map<String, Object> context) {2 if (context == null) {3 return context;4 }567 Object idObj = context.get('id');8 if (idObj == null || !(idObj instanceof String)) {9 return context;10 }111213 String idValue = (String)idObj;141516 // 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-is18 if (idValue.length() == 15 || idValue.length() == 18) {19 // Looks like a Salesforce ID, resolve to CaseNumber20 List<Case> cases = [21 SELECT Id, CaseNumber22 FROM Case23 WHERE Id = :idValue24 LIMIT 125 ];262728 // If found, replace the ID in context with the CaseNumber29 if (!cases.isEmpty() && cases[0].CaseNumber != null) {30 context.put('id', cases[0].CaseNumber);31 }32 }333435 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)
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 // }89 String troubleTicketId = (String) context.get('id');10 Map<String, Object> validationResult = new Map<String, Object>();1112 try {13 // Resolve CaseNumber to Salesforce ID if needed14 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 }2021 // Query the Case to check its status22 List<Case> cases = [23 SELECT Id, Status, CaseNumber24 FROM Case25 WHERE Id = :caseId26 LIMIT 127 ];2829 if (cases.isEmpty()) {30 validationResult.put('validationStatus', 'FAIL');31 validationResult.put('validationMessage', 'Trouble ticket not found');32 return validationResult;33 }3435 Case caseRecord = cases[0];3637 // Prevent deletion of closed cases38 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.CaseNumber45 });46 return validationResult;47 }4849 // Prevent deletion of cases with open child cases50 List<Case> childCases = [51 SELECT Id FROM Case52 WHERE ParentId = :caseId53 AND Status != 'Closed'54 LIMIT 155 ];5657 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 }6566 // Check user permissions67 if (!UserInfo.getProfileId().equals('00e000000000001')) { // Admin profile68 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 }7576 // PASS: All validations passed77 validationResult.put('validationStatus', 'PASS');78 validationResult.put('validationMessage', 'Trouble ticket deletion validation passed');7980 } catch (Exception e) {81 // FAIL: Error during validation82 validationResult.put('validationStatus', 'FAIL');83 validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());84 }8586 return validationResult;87}888990/**91 * Helper method to resolve CaseNumber to Salesforce ID92 */93private String resolveCaseId(String identifier) {94 // If it's already a Salesforce ID (15 or 18 characters), return as-is95 if (identifier.length() == 15 || identifier.length() == 18) {96 return identifier;97 }9899 // Otherwise, treat as CaseNumber and query for Salesforce ID100 List<Case> cases = [101 SELECT Id FROM Case102 WHERE CaseNumber = :identifier103 LIMIT 1104 ];105106 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.
1public Map<String, Object> handlePostOperation(2 Map<String, Object> graphQLQueryResultAsMap,3 Map<String, Object> constructedTMFResponse,4 Map<String, Object> context5) {6 String troubleTicketId = (String) context.get('id');78 // Log deletion event9 System.debug('Trouble ticket deleted: ' + troubleTicketId);10 System.debug('Deleted by user: ' + UserInfo.getUserId());11 System.debug('Deleted at: ' + System.now());1213 // You can also create audit records, send notifications, etc.14 // Note: Return value is ignored, so any processing here is for side effects only1516 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.
1public Map<String, String> resolveUniqueIdentifiers(List<String> ids) {2 Map<String, String> resolvedIds = new Map<String, String>();34 try {5 // Query Case objects where CaseNumber matches any of the provided IDs6 List<Case> cases = [7 SELECT Id, CaseNumber8 FROM Case9 WHERE CaseNumber IN :ids10 ];1112 // Build map of CaseNumber to Salesforce ID13 for (Case caseRecord : cases) {14 if (caseRecord.CaseNumber != null) {15 resolvedIds.put(caseRecord.CaseNumber, caseRecord.Id);16 }17 }1819 } catch (Exception e) {20 System.debug('Error resolving IDs: ' + e.getMessage());21 }2223 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 Extension3 * Demonstrates all applicable lifecycle hooks with business logic4 */5public class TroubleTicketManagementDELETEExtension implements comms_apex_ext.ITroubleTicketManagementDELETE {67 /**8 * Hook 1: Transform request context9 */10 public Map<String, Object> transformRequest(Map<String, Object> context) {11 if (context == null) {12 return context;13 }1415 Object idObj = context.get('id');16 if (idObj == null || !(idObj instanceof String)) {17 return context;18 }1920 String idValue = (String)idObj;2122 // If it's not a Salesforce ID format, treat as CaseNumber and resolve23 if (idValue.length() != 15 && idValue.length() != 18) {24 List<Case> cases = [25 SELECT Id, CaseNumber26 FROM Case27 WHERE CaseNumber = :idValue28 LIMIT 129 ];3031 if (!cases.isEmpty() && cases[0].Id != null) {32 context.put('id', cases[0].Id);33 }34 }3536 return context;37 }3839 /**40 * Hook 2: Apply custom validations41 */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>();4546 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 }5354 // Check if case is closed55 List<Case> cases = [56 SELECT Id, Status FROM Case57 WHERE Id = :caseId AND Status = 'Closed' LIMIT 158 ];5960 if (!cases.isEmpty()) {61 validationResult.put('validationStatus', 'FAIL');62 validationResult.put('validationMessage', 'Cannot delete closed trouble ticket');63 return validationResult;64 }6566 validationResult.put('validationStatus', 'PASS');67 validationResult.put('validationMessage', 'Validation passed');6869 } catch (Exception e) {70 validationResult.put('validationStatus', 'FAIL');71 validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());72 }7374 return validationResult;75 }7677 /**78 * Hook 3: Customize mutation payload79 */80 public Map<String, Object> customiseMutationPayload(81 Map<String, Object> request,82 Map<String, Object> context83 ) {84 String troubleTicketId = (String) context.get('id');85 String caseId = resolveCaseId(troubleTicketId);8687 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 }100101 /**102 * Hook 4: Resolve unique identifiers103 */104 public Map<String, String> resolveUniqueIdentifiers(List<String> ids) {105 Map<String, String> resolvedIds = new Map<String, String>();106107 try {108 List<Case> cases = [109 SELECT Id, CaseNumber110 FROM Case111 WHERE CaseNumber IN :ids112 ];113114 for (Case caseRecord : cases) {115 if (caseRecord.CaseNumber != null) {116 resolvedIds.put(caseRecord.CaseNumber, caseRecord.Id);117 }118 }119120 } catch (Exception e) {121 System.debug('Error resolving IDs: ' + e.getMessage());122 }123124 return resolvedIds;125 }126127 /**128 * Hook 5: Post-process response129 */130 public Map<String, Object> handlePostOperation(131 Map<String, Object> graphQLQueryResultAsMap,132 Map<String, Object> constructedTMFResponse,133 Map<String, Object> context134 ) {135 /*136 TODO Any post processing to be performed.137 */138139 return null;140 }141142 /**143 * Helper method to resolve CaseNumber to Salesforce ID144 */145 private String resolveCaseId(String identifier) {146 if (identifier.length() == 15 || identifier.length() == 18) {147 return identifier;148 }149150 List<Case> cases = [151 SELECT Id FROM Case152 WHERE CaseNumber = :identifier153 LIMIT 1154 ];155156 return cases.isEmpty() ? null : cases[0].Id;157 }158159 /**160 * NOT APPLICABLE: configureDefaultValidations is not used for Trouble Ticket Management DELETE API161 */162 public Map<String, Boolean> configureDefaultValidations(163 Map<String, Boolean> defaultValidationConfiguration,164 Map<String, Object> context165 ) {166 return defaultValidationConfiguration;167 }168169 /**170 * NOT APPLICABLE: customiseGraphQLQuery is not used for Trouble Ticket Management DELETE API171 */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}