ITroubleTicketManagementPATCH Apex Interface

The ITroubleTicketManagementPATCH interface supports extensibility for PATCH operations by allowing validation, transformation, and post-processing of update requests. Implementations can enforce business rules, adjust mutation payloads, and refine the resulting responses maintaining TMF-compliant behavior.

This interface supports the following hooks.

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

  • configureDefaultValidations

Note

Update Lifecycle Use Cases and Hook Mapping 

The following table lists common use cases and hooks required for PATCH operation.

Use CaseHook(s)Description
Example Scenarios
Benefit
Enforce Status TransitionsapplyCustomValidationsValidates requested status changes before updating the record to ensure transitions adhere to defined lifecycle rules, regulatory constraints, or business policies. Stops invalid or unauthorized state changes.• Prevent transition from InactiveActive without required approvals• Block changes to Suspended unless preconditions are met• Enforce linear lifecycle progression (e.g., PendingActive, but not ClosedActive)Prevents invalid or noncompliant state transitions and preserves data integrity
Automatic Field PopulationcustomiseMutationPayloadAutomatically populate fields like Reason or Origin during updates.• Add Reason and Origin fields• Track system-initiated vs. user-initiated changesProvides a complete and reliable audit trail for internal governance and external compliance needs
Validate Data FormatapplyCustomValidationsEnsures updated data—such as contact details, identifiers, or structured fields—meets required formatting rules before the update is processed. Prevents malformed or inconsistent data from entering the system.• Validate phone number format• Confirm email structure and domain correctness• Ensure address fields follow standardized formatsEnsures required fields are always set without requiring client to provide them
Response EnrichmenthandlePostOperationAutomatically enriches the update payload with audit metadata before persistence. Ensures all modifications are traceable and compliant with auditing standards.• Add “modifiedBy” and timestamp fields• Track system-initiated vs. user-initiated changes• Add audit IDs for downstream monitoring systemsProvides a complete and reliable audit trail for internal governance and external compliance needs
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 

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

applyCustomValidations 

This hook validates custom business logic before updating a customer 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 

1public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
2    // Input context:
3    // {
4    //   api: 'TroubleTicketManagement',
5    //   version: '5.0',
6    //   id: '00001027',
7    //   requestBody: {
8    //     status: 'Closed',
9    //     name: 'Updated Name'
10    //   }
11    // }
12
13    String troubleTicketId = (String) context.get('id');
14    Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
15
16    if (requestBody == null || !requestBody.containsKey('status')) {
17        // No status update requested, validation passes
18        return null;
19    }
20
21    String newStatus = (String) requestBody.get('status');
22
23    Map<String, Object> validationResult = new Map<String, Object>();
24
25    try {
26        // Fetch current trouble ticket status
27        Case troubleTicket = [SELECT Id, Status, CaseNumber FROM Case WHERE CaseNumber = :troubleTicketId LIMIT 1];
28        String currentStatus = troubleTicket.Status;
29
30        // Validate status transition: Cannot change from Closed to any other status
31        if ('Closed'.equals(currentStatus) && !'Closed'.equals(newStatus)) {
32            validationResult.put('validationStatus', 'FAIL');
33            validationResult.put('validationMessage', 'Cannot change status from Closed to ' + newStatus);
34            validationResult.put('validationDetails', new Map<String, Object>{
35                'denialReason' => 'INVALID_STATUS_TRANSITION',
36                'currentStatus' => currentStatus,
37                'requestedStatus' => newStatus
38            });
39        } else {
40            // Validation passes
41            validationResult.put('validationStatus', 'PASS');
42            validationResult.put('validationMessage', 'Status transition is valid');
43        }
44    } catch (Exception e) {
45        // FAIL: Error during validation
46        validationResult.put('validationStatus', 'FAIL');
47        validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
48        validationResult.put('validationDetails', new Map<String, Object>{
49            'errorType' => e.getTypeName()
50        });
51    }
52
53    return validationResult;
54}

customiseMutationPayload 

This hook modifies the mutation payload before updating a customer record. Use this to add computed fields, transform values, or apply business logic transformations.

Hook Method 

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

Sample Apex Implementation 

1public Map<String, Object> customiseMutationPayload(
2    Map<String, Object> mutationGraphQLPayload,
3    Map<String, Object> context
4) {
5    // Create mutation transformation specification
6    List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();
7
8    // Node 1: Add custom input fields to the Case mutation
9    // Path: troubleTicket (the Case mutation alias)
10    Map<String, Object> modifyInputNode = new Map<String, Object>{
11        'path' => 'troubleTicket',
12        'modifyInput' => new Map<String, Object>{
13            'addInputFields' => new Map<String, Object>{
14                'Reason' => 'Updated via PATCH API',
15                'Origin' => 'Web'
16            }
17        }
18    };
19    transformationNodes.add(modifyInputNode);
20
21    // Return transformation specification
22    return new Map<String, Object>{
23        'nodes' => transformationNodes
24    };
25}

For Trouble Ticket Management API, use path troubleTicket to target the main Case mutation

Note

customiseGraphQLQuery 

This hook modifies the GraphQL query before execution to add fields, filters, or transformations. Returns a map with a spec key containing a list of QueryTransformationNode objects.

Hook Method 

Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context)

Sample Apex Implementation 

1public Map<String, Object> customiseGraphQLQuery(
2    Map<String, Object> graphQLAsMap,
3    Map<String, Object> context
4) {
5    // Create transformation specification to add custom fields
6    List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();
7
8    // Add custom fields to the Case node
9    // Path: troubleTicket (the Case alias in the GraphQL query)
10    Map<String, Object> addFieldsNode = new Map<String, Object>{
11        'path' => 'troubleTicket',
12        'addFields' => new List<String>{'IsClosed', 'SlaStartDate', 'SlaExitDate'}
13    };
14    transformationNodes.add(addFieldsNode);
15
16    // Return transformation specification
17    return new Map<String, Object>{
18        'nodes' => transformationNodes
19    };
20}

handlePostOperation 

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

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    // Input TMF response:
7    // {
8    //   id: '00001027',
9    //   name: 'Updated Trouble Ticket',
10    //   status: 'In Progress',
11    //   type: 'TroubleTicket'
12    // }
13
14    String ticketId = (String) constructedTMFResponse.get('id');
15
16    if (ticketId != null) {
17        // Fetch additional data for enrichment
18        Case caseRecord = [SELECT Id, CaseNumber, CreatedDate, LastModifiedDate, Status FROM Case WHERE CaseNumber = :ticketId LIMIT 1];
19
20        // Add calculated fields
21        if (caseRecord.CreatedDate != null && caseRecord.LastModifiedDate != null) {
22            Long hoursSinceLastUpdate = (caseRecord.LastModifiedDate.getTime() - caseRecord.CreatedDate.getTime()) / (1000 * 60 * 60);
23            constructedTMFResponse.put('hoursSinceCreation', hoursSinceLastUpdate);
24        }
25
26        // Add audit information
27        constructedTMFResponse.put('lastUpdatedAt', DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
28        constructedTMFResponse.put('updatedBy', UserInfo.getName());
29    }
30
31    // Return enriched response
32    return constructedTMFResponse;
33
34    // Output response:
35    // {
36    //   id: '00001027',
37    //   name: 'Updated Trouble Ticket',
38    //   status: 'In Progress',
39    //   type: 'TroubleTicket',
40    //   hoursSinceCreation: 48,
41    //   lastUpdatedAt: '2024-01-17T10:30:00Z',
42    //   updatedBy: 'John Smith'
43    // }
44}

Full Implementation Example 

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

1/**
2 * Complete Trouble Ticket Management PATCH API Extension
3 * Demonstrates all applicable lifecycle hooks with business logic
4 */
5public class TroubleTicketManagementPATCHExtension implements comms_apex_ext.ITroubleTicketManagementPATCH {
6
7    /**
8     * Hook 1: Transform request
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            String idValue = (String)idObj;
18
19            // If not a Salesforce ID, resolve CaseNumber to Id
20            if (idValue.length() != 15 && idValue.length() != 18) {
21                List<Case> cases = [
22                    SELECT Id, CaseNumber
23                    FROM Case
24                    WHERE CaseNumber = :idValue
25                    LIMIT 1
26                ];
27
28                if (!cases.isEmpty()) {
29                    context.put('id', cases[0].Id);
30                }
31            }
32        }
33
34        return context;
35    }
36
37    /**
38     * Hook 2: Apply custom validations
39     */
40    public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
41        String troubleTicketId = (String) context.get('id');
42        Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
43
44        if (requestBody == null || !requestBody.containsKey('status')) {
45            return null;
46        }
47
48        String newStatus = (String) requestBody.get('status');
49
50        try {
51            Case troubleTicket = [SELECT Id, Status, CaseNumber FROM Case WHERE CaseNumber = :troubleTicketId LIMIT 1];
52            String currentStatus = troubleTicket.Status;
53
54            // Validate status transition: Cannot change from Closed to any other status
55            if ('Closed'.equals(currentStatus) && !'Closed'.equals(newStatus)) {
56                return new Map<String, Object>{
57                    'validationStatus' => 'FAIL',
58                    'validationMessage' => 'Cannot change status from Closed to ' + newStatus,
59                    'validationDetails' => new Map<String, Object>{
60                        'denialReason' => 'INVALID_STATUS_TRANSITION',
61                        'currentStatus' => currentStatus,
62                        'requestedStatus' => newStatus
63                    }
64                };
65            }
66        } catch (Exception e) {
67            return new Map<String, Object>{
68                'validationStatus' => 'FAIL',
69                'validationMessage' => 'Validation error: ' + e.getMessage()
70            };
71        }
72
73        return null;
74    }
75
76    /**
77     * Hook 3: Customize GraphQL query
78     */
79    public Map<String, Object> customiseGraphQLQuery(
80        Map<String, Object> graphQLAsMap,
81        Map<String, Object> context
82    ) {
83        // Add custom fields to the query if needed
84        List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();
85
86        transformationNodes.add(new Map<String, Object>{
87            'path' => 'troubleTicket',
88            'addFields' => new List<String>{'IsClosed', 'SlaStartDate'}
89        });
90
91        return new Map<String, Object>{ 'nodes' => transformationNodes };
92    }
93
94    /**
95     * Hook 4: Customize mutation payload
96     */
97    public Map<String, Object> customiseMutationPayload(
98        Map<String, Object> mutationGraphQLPayload,
99        Map<String, Object> context
100    ) {
101        List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();
102
103        // Add custom input fields
104        Map<String, Object> modifyInput = new Map<String, Object>{
105            'addInputFields' => new Map<String, Object>{
106                'Reason' => 'Updated via PATCH API',
107                'Origin' => 'Web'
108            }
109        };
110
111        transformationNodes.add(new Map<String, Object>{
112            'path' => 'troubleTicket',
113            'modifyInput' => modifyInput
114        });
115
116        return new Map<String, Object>{ 'nodes' => transformationNodes };
117    }
118
119    /**
120     * Hook 5: Post-process response
121     */
122    public Map<String, Object> handlePostOperation(
123        Map<String, Object> graphQLQueryResultAsMap,
124        Map<String, Object> constructedTMFResponse,
125        Map<String, Object> context
126    ) {
127        String ticketId = (String) constructedTMFResponse.get('id');
128
129        if (ticketId != null) {
130            try {
131                Case caseRecord = [SELECT Id, CaseNumber, CreatedDate, LastModifiedDate FROM Case WHERE CaseNumber = :ticketId LIMIT 1];
132
133                if (caseRecord.CreatedDate != null && caseRecord.LastModifiedDate != null) {
134                    Long hoursSinceCreation = (caseRecord.LastModifiedDate.getTime() - caseRecord.CreatedDate.getTime()) / (1000 * 60 * 60);
135                    constructedTMFResponse.put('hoursSinceCreation', hoursSinceCreation);
136                }
137
138                constructedTMFResponse.put('lastUpdatedAt', DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
139                constructedTMFResponse.put('updatedBy', UserInfo.getName());
140            } catch (Exception e) {
141                System.debug('Error enriching trouble ticket data: ' + e.getMessage());
142            }
143        }
144
145        return constructedTMFResponse;
146    }
147
148    /**
149     * NOT APPLICABLE: configureDefaultValidations is not used for Trouble Ticket Management API
150     * If implemented, this method will be invoked but return values will be ignored.
151     */
152    public Map<String, Boolean> configureDefaultValidations(
153        Map<String, Boolean> defaultValidationConfiguration,
154        Map<String, Object> context
155    ) {
156        // This hook is not applicable for Trouble Ticket Management API
157        // Return the configuration unchanged
158        return defaultValidationConfiguration;
159    }
160}

Response Structure 

Successful PATCH Response

1{
2  "id": "00001027",
3  "name": "Updated Trouble Ticket Name",
4  "type": "TroubleTicket",
5  "href": "/services/data/v67.0/connect/comms/troubleticket/v5/troubleticket/00001027",
6  "description": "Updated description",
7  "status": "In Progress",
8  "ticketType": "Network",
9  "priority": "High",
10  "creationDate": "2024-01-15T10:00:00Z",
11  "lastUpdate": "2024-01-17T14:30:00Z"
12}

GraphQL Mutation - Update Operations 

Update mutations modify existing records and return the updated records with the specified output fields.

Update Enhancements: Add Output Fields 

Original Mutation (GraphQL):

1mutation UpdateCustomerAndRelatedEntities {
2  uiapi {
3    customerAccount: AccountUpdate(input: {
4      Id: "001xx000003DHP"
5      Account: { Name: "Updated Name" }
6    }) {
7      Record {
8        Id
9        Name { value }
10      }
11    }
12  }
13}

Transformation Instructions (JSON):

1{
2  "nodes": [{
3    "path": "uiapi.customerAccount",
4    "addFields": {
5      "LastModifiedDate": "2024-01-15",
6      "ModifiedBy": "user123",
7      "Status": "Updated"
8    }
9  }]
10}

Result (GraphQL):

1mutation UpdateCustomerAndRelatedEntities {
2  uiapi {
3    customerAccount: AccountUpdate(input: {
4      Id: "001xx000003DHP"
5      Account: { Name: "Updated Name" }
6    }) {
7      Record {
8        Id
9        Name { value }
10        LastModifiedDate
11        ModifiedBy
12        Status
13      }
14    }
15  }
16}

Update Enhancements: Modify Input – Add Fields 

Original Mutation (GraphQL):

1mutation UpdateCustomerAndRelatedEntities {
2  uiapi {
3    customerAccount: AccountUpdate(input: {
4      Id: "001xx000003DHP"
5      Account: { Name: "Updated Name" }
6    }) {
7      Record {
8        Id
9        Name { value }
10      }
11    }
12  }
13}

Transformation Instructions (JSON - Add Input Fields):

1{
2  "nodes": [{
3    "path": "uiapi.customerAccount",
4    "modifyInput": {
5      "addInputFields": {
6        "Industry": "Technology",
7        "Status": "Active"
8      }
9    }
10  }]
11}

Result (GraphQL):

1mutation UpdateCustomerAndRelatedEntities {
2  uiapi {
3    customerAccount: AccountUpdate(input: {
4      Id: "001xx000003DHP"
5      Account: {
6        Name: "Updated Name",
7        Industry: "Technology",
8        Status: "Active"
9      }
10    }) {
11      Record {
12        Id
13        Name { value }
14      }
15    }
16  }
17}

Update Enhancements: Modify Input and Add Output Fields 

Transformation Instructions (JSON):

1{
2  "nodes": [{
3    "path": "uiapi.customerAccount",
4    "addFields": {
5      "LastModifiedDate": "2024-01-15",
6      "ChangeLog": "Updated via API"
7    },
8    "modifyInput": {
9      "addInputFields": {
10        "UpdateReason": "Bulk Update",
11        "UpdateSource": "API"
12      },
13      "inputModifications": {
14        "Status": "Active"
15      }
16    }
17  }]
18}

Result (GraphQL):

1mutation UpdateCustomerAndRelatedEntities {
2  uiapi {
3    customerAccount: AccountUpdate(input: {
4      Id: "001xx000003DHP"
5      Account: {
6        Name: "Updated Name",
7        Status: "Active",
8        UpdateReason: "Bulk Update",
9        UpdateSource: "API"
10      }
11    }) {
12      Record {
13        Id
14        Name { value }
15        LastModifiedDate
16        ChangeLog
17      }
18    }
19  }
20}