ICustomerManagementPATCH Apex Interface

The ICustomerManagementPATCH 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.

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

  • configureDefaultValidations
  • customiseGraphQLQuery

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
Maintain Audit TrailcustomiseMutationPayloadAutomatically 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
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 formatsImproves data quality, prevents inconsistencies, and enhances downstream system reliability

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 001, 003, etc.)
14       // If not, assume it's an AccountNumber 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 Account objects where AccountNumber matches the provided ID
21       List<Account> accounts = [
22           SELECT Id, AccountNumber
23           FROM Account
24           WHERE AccountNumber = :idValue
25           LIMIT 1
26       ];
27
28       // If found, replace the ID in context with the Salesforce ID
29       if (!accounts.isEmpty() && accounts[0].AccountNumber != null) {
30           context.put('id', accounts[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: 'CustomerManagement',
5       //   version: '4.0',
6       //   customerId: '001xx000003DHP',
7       //   requestBody: { name: 'Updated Name', status: 'Inactive' }
8       // }
9
10       String customerId = (String) context.get('id');
11       Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
12       Map<String, Object> validationResult = new Map<String, Object>();
13
14       try {
15           String newStatus = (String) requestBody.get('status');
16
17           // Validate status transition
18           if (newStatus != null) {
19               Account currentAccount = [SELECT Id, Status__c FROM Account WHERE Id = :customerId LIMIT 1];
20
21               if (!isValidStatusTransition(currentAccount.Status__c, newStatus)) {
22                   validationResult.put('validationStatus', 'FAIL');
23                   validationResult.put('validationMessage', 'Invalid status transition from ' + currentAccount.Status__c + ' to ' + newStatus);
24                   validationResult.put('validationDetails', new Map<String, Object>{
25                       'currentStatus' => currentAccount.Status__c,
26                       'requestedStatus' => newStatus,
27                       'allowedTransitions' => getAllowedTransitions(currentAccount.Status__c)
28                   });
29                   return validationResult;
30               }
31           }
32
33           // Validate contact information if provided
34           Map<String, Object> contactMedium = (Map<String, Object>) requestBody.get('contactMedium');
35           if (contactMedium != null && !isValidContactMedium(contactMedium)) {
36               validationResult.put('validationStatus', 'FAIL');
37               validationResult.put('validationMessage', 'Invalid contact medium format');
38               return validationResult;
39           }
40
41           // PASS: All validations passed
42           validationResult.put('validationStatus', 'PASS');
43           validationResult.put('validationMessage', 'Customer update validation passed');
44
45       } catch (Exception e) {
46           // FAIL: Error during validation
47           validationResult.put('validationStatus', 'FAIL');
48           validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
49       }
50
51       return validationResult;
52   }
53
54   private Boolean isValidStatusTransition(String currentStatus, String newStatus) {
55       // Define valid status transitions
56       Map<String, Set<String>> validTransitions = new Map<String, Set<String>>{
57           'Active' => new Set<String>{'Inactive', 'Suspended'},
58           'Inactive' => new Set<String>{'Active'},
59           'Suspended' => new Set<String>{'Active', 'Inactive'}
60       };
61
62       Set<String> allowed = validTransitions.get(currentStatus);
63       return allowed != null && allowed.contains(newStatus);
64   }
65
66   private List<String> getAllowedTransitions(String currentStatus) {
67       Map<String, List<String>> transitions = new Map<String, List<String>>{
68           'Active' => new List<String>{'Inactive', 'Suspended'},
69           'Inactive' => new List<String>{'Active'},
70           'Suspended' => new List<String>{'Active', 'Inactive'}
71       };
72
73       return transitions.get(currentStatus);
74   }
75
76   private Boolean isValidContactMedium(Map<String, Object> contactMedium) {
77       String mediumType = (String) contactMedium.get('mediumType');
78       return mediumType != null && !mediumType.isBlank();
79   }

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 

1/**
2     * Customizes the mutation payload before the GraphQL mutation is executed.
3     * Reads Description and Industry from the request body in context and adds them
4     * to the Account node at alias path "customerAccount".
5     *
6     * @param request The mutation request payload
7     * @param context Request context containing API details and request body
8     * @return Map containing mutation transformation instructions with nodes array:
9     *         - nodes[0]: Contains path='customerAccount' and addFields with Description and Industry
10     */
11    public Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context) {
12        // Return mutation transformation instructions: add Account fields at alias path "customerAccount"
13        Map<String, Object> nodes0 = new Map<String, Object>();
14        nodes0.put('path', 'customerAccount');
15        Map<String, Object> addFields = new Map<String, Object>();
16
17        // Read Description and Industry from request body in context
18        Map<String, Object> requestBody = (Map<String, Object>)context.get('requestBody');
19        if (requestBody != null) {
20            String description = (String)requestBody.get('description');
21            String industry = (String)requestBody.get('industry');
22
23            // Add Description to Account if present in request
24            if (description != null) {
25                addFields.put('Description', description);
26            }
27            // Add Industry to Account if present in request
28            if (industry != null) {
29                addFields.put('Industry', industry);
30            }
31        }
32
33        nodes0.put('addFields', addFields);
34
35        // Build nodes array containing the transformation instructions
36        List<Object> nodes = new List<Object>();
37        nodes.add(nodes0);
38
39        // Return result with nodes array
40        Map<String, Object> result = new Map<String, Object>();
41        result.put('nodes', nodes);
42        return result;
43    }

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)

Full Implementation Example 

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

1/**
2 * Complete Customer Management POST API Extension
3 * Demonstrates all three applicable lifecycle hooks with business logic
4 */
5public class CustomerManagementPATCHExtension implements comms_apex_ext.ICustomerManagementPATCH {
6
7    /**
8     * Hook 1: Apply custom validations
9     */
10    public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
11        Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
12        Map<String, Object> validationResult = new Map<String, Object>();
13
14        try {
15            String customerName = (String) requestBody.get('name');
16            String customerType = (String) requestBody.get('type');
17
18            // Validate required fields
19            if (String.isBlank(customerName)) {
20                validationResult.put('validationStatus', 'FAIL');
21                validationResult.put('validationMessage', 'Customer name is required');
22                return validationResult;
23            }
24
25            // Validate customer type
26            if (!isValidCustomerType(customerType)) {
27                validationResult.put('validationStatus', 'FAIL');
28                validationResult.put('validationMessage', 'Invalid customer type');
29                return validationResult;
30            }
31
32            // Check for duplicates
33            List<Account> existingAccounts = [SELECT Id FROM Account WHERE Name = :customerName LIMIT 1];
34            if (!existingAccounts.isEmpty()) {
35                validationResult.put('validationStatus', 'FAIL');
36                validationResult.put('validationMessage', 'Customer with this name already exists');
37                return validationResult;
38            }
39
40            validationResult.put('validationStatus', 'PASS');
41            validationResult.put('validationMessage', 'Validation passed');
42
43        } catch (Exception e) {
44            validationResult.put('validationStatus', 'FAIL');
45            validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
46        }
47
48        return validationResult;
49    }
50
51    /**
52     * Hook 2: Customize mutation payload
53     */
54    /**
55     * Customizes the mutation payload before the GraphQL mutation is executed.
56     * Reads Description and Industry from the request body in context and adds them
57     * to the Account node at alias path "customerAccount".
58     *
59     * @param request The mutation request payload
60     * @param context Request context containing API details and request body
61     * @return Map containing mutation transformation instructions with nodes array:
62     *         - nodes[0]: Contains path='customerAccount' and addFields with Description and Industry
63     */
64    public Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context) {
65        // Return mutation transformation instructions: add Account fields at alias path "customerAccount"
66        Map<String, Object> nodes0 = new Map<String, Object>();
67        nodes0.put('path', 'customerAccount');
68        Map<String, Object> addFields = new Map<String, Object>();
69
70        // Read Description and Industry from request body in context
71        Map<String, Object> requestBody = (Map<String, Object>)context.get('requestBody');
72        if (requestBody != null) {
73            String description = (String)requestBody.get('description');
74            String industry = (String)requestBody.get('industry');
75
76            // Add Description to Account if present in request
77            if (description != null) {
78                addFields.put('Description', description);
79            }
80            // Add Industry to Account if present in request
81            if (industry != null) {
82                addFields.put('Industry', industry);
83            }
84        }
85
86        nodes0.put('addFields', addFields);
87
88        // Build nodes array containing the transformation instructions
89        List<Object> nodes = new List<Object>();
90        nodes.add(nodes0);
91
92        // Return result with nodes array
93        Map<String, Object> result = new Map<String, Object>();
94        result.put('nodes', nodes);
95        return result;
96    }
97
98    /**
99     * Hook 3: Post-process response
100     */
101    public Map<String, Object> handlePostOperation(
102        Map<String, Object> graphQLQueryResultAsMap,
103        Map<String, Object> constructedTMFResponse,
104        Map<String, Object> context
105    ) {
106        return null;
107    }
108
109    /**
110     * NOT APPLICABLE: configureDefaultValidations is not used for Customer Management API
111     */
112    public Map<String, Boolean> configureDefaultValidations(
113        Map<String, Boolean> defaultValidationConfiguration,
114        Map<String, Object> context
115    ) {
116        return null;
117    }
118
119    private Boolean isValidCustomerType(String type) {
120        Set<String> validTypes = new Set<String>{'Customer', 'Prospect', 'Partner'};
121        return validTypes.contains(type);
122    }
123    private String determineCustomerSegment(String name) {
124        if (name.length() > 20) {
125            return 'Enterprise';
126        } else if (name.length() > 10) {
127            return 'Mid-Market';
128        } else {
129            return 'SMB';
130        }
131    }
132    **
133     * NOT APPLICABLE: customiseGraphQLQuery is not used for Customer Management API
134     */
135    public Map<String, Object> customiseGraphQLQuery(
136        Map<String, Object> graphQLAsMap,
137        Map<String, Object> context
138    ) {
139       return null;
140    }
141}

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}