IAccountManagementPATCH Apex Interface

The IAccountManagementPATCH Apex interface provides extensibility for PATCH operations through Apex pre- and post-hooks and GraphQL query customization. Implementations can validate incoming requests, modify query structures, and refine the response to support business-specific retrieval rules while maintaining TMF-compliant behavior.

This interface supports the following hooks.

Update Operation Use Cases 

Use CaseHook(s)DescriptionExample ScenariosBenefit
Resolve an External Key to the Salesforce IDtransformRequestResolves a caller-provided business key or external identifier to the canonical Salesforce Account ID before validation and update processing begins.- Resolve an ERP customer number
- Map a billing-system identifier to an Account ID
- Translate a partner-specific key
- Resolve a legacy Account reference
Allows callers to address Account records using their own identifiers instead of requiring a Salesforce ID.
Default or Enrich Fields Before UpdatetransformRequestNormalizes the incoming partial update and supplies default, derived, or contextual values before validation. This reduces the amount of data clients must provide and ensures consistent input.- Normalize phone numbers and addresses
- Derive a region from the country code
- Supply a source-system value from the request context
- Standardize status or classification values
Reduces client payload complexity and ensures consistent Account data before it is written.
Validate State Transitions and Business RulesapplyCustomValidationsEvaluates the proposed partial update against organization-specific lifecycle rules and business constraints before the mutation is built.- Reject an invalid Active to Closed transition
- Prevent status changes while open balances exist
- Require a reason when suspending an Account
- Restrict sensitive updates to authorized roles
Rejects invalid partial updates early and protects Account lifecycle and data integrity.
Enable Conditional Validators for Partial UpdatesconfigureDefaultValidationsEnables validation flags according to the fields included in the partial update. Validators for nested objects run only when the corresponding data is present.- Validate relatedParty when included
- Validate Contact sub-objects when supplied
- Enable address validation only when an address changes
- Apply stricter checks when regulated fields are updated
Applies relevant validation without requiring absent fields or running unnecessary checks during partial updates.
Auto-Populate or Override Mutation FieldscustomiseMutationPayloadAdds or replaces fields in the GraphQL mutation payload before execution, including organization-specific values that are not represented in the TMF schema.- Set lastModifiedBy and lastModifiedAt
- Add a source-system or correlation ID
- Override a derived lifecycle status
- Populate internal classification or compliance fields
Persists organization-specific fields and provenance beyond the standard TMF schema.
Update Related Records Not Covered by PATCHcustomiseMutationPayload (insertGraphQlSnippet)Inserts an additional GraphQL operation into the mutation so related records can be updated atomically with the Account.- Add or remove Account-Contact junctions
- Update relationship roles
- Modify related-party associations
- Synchronize Account hierarchy links
Updates junctions and other related records in the same mutation, reducing extra API calls and inconsistent intermediate states.
Create an Audit Trail or Trigger Downstream ProcessinghandlePostOperationRuns after the update mutation succeeds and uses the operation result to perform compliance logging, publish events, or invoke downstream integrations.- Write an Account-update audit record
- Publish an Account-changed event
- Synchronize changes with billing or CRM systems
- Trigger compliance review or notifications
- Invalidate related caches or indexes
Supports compliance and integration side effects while keeping them separate from the core update operation.

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 an external identifier into the Salesforce ID.

1global class AccountMgmtTransformRequestPatchExt implements comms_apex_ext.IAccountManagementPATCH {
2
3
4    global static Map<String, Object> transformRequest(Map<String, Object> context) {
5        if (context == null) {
6            return context;
7        }
8
9
10        // If the caller addressed the record by an external key, resolve it to the SF id.
11        Object idObj = context.get('id');
12        if (idObj instanceof String && ((String) idObj).startsWith('EXT-')) {
13            String externalKey = (String) idObj;
14            List<BillingAccount> matches = [
15                SELECT Id FROM BillingAccount WHERE ExtlBillingAccountId__c = :externalKey LIMIT 1
16            ];
17            if (!matches.isEmpty()) {
18                context.put('id', matches[0].Id);
19            }
20        }
21        return context;
22    }
23
24
25    // ... other hooks return null / empty map ...
26}

configureDefaultValidations 

This hook enables, disables, or modifies the built-in validation rules before they execute.

Hook Method 

Map<String, Boolean> configureDefaultValidations(Map<String, Boolean> defaultValidationConfiguration, Map<String, Object> context)

Sample Apex Implementation 

Enable conditional validators for PATCH.

1global class AccountMgmtValidationConfigPatchExt implements comms_apex_ext.IAccountManagementPATCH {
2
3
4    global static Map<String, Boolean> configureDefaultValidations(
5        Map<String, Boolean> defaultValidationConfiguration,
6        Map<String, Object> context
7    ) {
8        // Start from the passed-in default and enable only the conditional validators.
9        Map<String, Boolean> spec = defaultValidationConfiguration != null
10            ? defaultValidationConfiguration.clone()
11            : new Map<String, Boolean>();
12        spec.put('mandatoryFieldsValidation', false); // keep OFF for merge-patch
13        spec.put('relatedPartyValidation', true);     // conditional: only fires if relatedParty is present
14        spec.put('contactValidation', true);          // conditional: only fires if contact is present
15        return spec;
16    }
17
18
19    // ... other hooks return null / empty map ...
20}

Do not enable mandatoryFieldsValidation for a typical PATCH request. It requires a non-empty relatedParty array and name, which merge-patch callers may omit, causing valid partial updates to be rejected. The PATCH handler only updates BillingAccount scalar fields; relatedParty and contact values in the request are validated, if those validators are enabled, but are not persisted as junction changes.

Note

applyCustomValidations 

This hook validates custom business logic before retrieving customer record. If validation fails, it rejects the request and returns an error response.

The handler processes return values as follows.

Success:

  • Return a map with validationStatus set to “pass” (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 

Enforce a valid state transition.

1global class AccountMgmtCustomValidationPatchExt implements comms_apex_ext.IAccountManagementPATCH {
2
3
4    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
5        Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
6        Map<String, Object> result = new Map<String, Object>();
7
8
9        if (requestBody != null && 'Closed'.equalsIgnoreCase(String.valueOf(requestBody.get('state')))) {
10            result.put('validationStatus', 'fail');
11            result.put('validationMessage',
12                'Custom validation failed: billing accounts cannot be Closed via the API.');
13            result.put('validationDetails', new Map<String, Object>{
14                'rejectedState' => requestBody.get('state'),
15                'rule'          => 'NO_API_CLOSE'
16            });
17            return result;
18        }
19
20
21        result.put('validationStatus', 'pass');
22        return result;
23    }
24
25
26    // ... other hooks return null / empty map ...
27}

customiseMutationPayload 

This hook modifies the GraphQL query before execution to add fields, filters, or transformations.

Hook Method 

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

Sample Apex Implementation 

Add a field to update mutation.

1global class AccountMgmtMutationPayloadPatchExt implements comms_apex_ext.IAccountManagementPATCH {
2
3
4    global static Map<String, Object> customiseMutationPayload(
5        Map<String, Object> mutationGraphQLPayload,
6        Map<String, Object> context
7    ) {
8        Map<String, Object> node = new Map<String, Object>{
9            'path'      => 'billingAccountUpdate',
10            'addFields' => new Map<String, Object>{ 'CustomerClass' => 'Commercial' }
11        };
12        return new Map<String, Object>{ 'nodes' => new List<Object>{ node } };
13    }
14
15    // ... other hooks return null / empty map ...
16}

handlePostOperation 

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

Hook Method 

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

Sample Apex Implementation 

Audit-log the updated Billing Account.

1global class AccountMgmtPostOpAuditPatchExt implements comms_apex_ext.IAccountManagementPATCH {
2    global static Map<String, Object> handlePostOperation(
3        Map<String, Object> graphQLResult,
4        Map<String, Object> tmfResponse,
5        Map<String, Object> context
6    ) {
7        try {
8            String updatedId = (String) context.get('id');
9            System.debug('BillingAccount updated via TMF666 PATCH by '
10                + UserInfo.getName() + ': ' + updatedId);
11        } catch (Exception e) {
12            System.debug('Audit logging failed: ' + e.getMessage());
13        }
14        // Return value is ignored for PATCH.
15        return null;
16    }
17
18    // ... other hooks return null / empty map ...
19}

Full Implementation Example 

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

1/**
2 * Complete TMF666 Account Management PATCH API Extension
3 * Demonstrates transformRequest, configureDefaultValidations, applyCustomValidations,
4 * customiseMutationPayload, and handlePostOperation.
5 */
6global class AccountManagementPATCHExtension implements comms_apex_ext.IAccountManagementPATCH {
7
8
9    /** HOOK 1: Resolve an external id into the Salesforce record id */
10    global static Map<String, Object> transformRequest(Map<String, Object> context) {
11        if (context == null) { return context; }
12        Object idObj = context.get('id');
13        if (idObj instanceof String && ((String) idObj).startsWith('EXT-')) {
14            List<BillingAccount> m = [
15                SELECT Id FROM BillingAccount WHERE ExtlBillingAccountId__c = :((String) idObj) LIMIT 1
16            ];
17            if (!m.isEmpty()) { context.put('id', m[0].Id); }
18        }
19        return context;
20    }
21
22
23    /** HOOK 2: Keep mandatory off; enable conditional validators for the partial update */
24    global static Map<String, Boolean> configureDefaultValidations(
25        Map<String, Boolean> defaultValidationConfiguration,
26        Map<String, Object> context
27    ) {
28        Map<String, Boolean> spec = defaultValidationConfiguration != null
29            ? defaultValidationConfiguration.clone()
30            : new Map<String, Boolean>();
31        spec.put('mandatoryFieldsValidation', false);
32        spec.put('relatedPartyValidation', true);
33        spec.put('contactValidation', true);
34        return spec;
35    }
36
37
38    /** HOOK 3: Block Closing an account via the API */
39    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
40        Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
41        Map<String, Object> result = new Map<String, Object>();
42        if (body != null && 'Closed'.equalsIgnoreCase(String.valueOf(body.get('state')))) {
43            result.put('validationStatus', 'fail');
44            result.put('validationMessage', 'billing accounts cannot be Closed via the API.');
45            return result;
46        }
47        result.put('validationStatus', 'pass');
48        return result;
49    }
50
51
52    /** HOOK 4: Force CustomerClass on the updated record */
53    global static Map<String, Object> customiseMutationPayload(
54        Map<String, Object> mutationGraphQLPayload,
55        Map<String, Object> context
56    ) {
57        return new Map<String, Object>{
58            'nodes' => new List<Object>{
59                new Map<String, Object>{
60                    'path'      => 'billingAccountUpdate',
61                    'addFields' => new Map<String, Object>{ 'CustomerClass' => 'Commercial' }
62                }
63            }
64        };
65    }
66
67
68    /** HOOK 5: Audit (return ignored for PATCH) */
69    global static Map<String, Object> handlePostOperation(
70        Map<String, Object> graphQLResult,
71        Map<String, Object> tmfResponse,
72        Map<String, Object> context
73    ) {
74        System.debug('BillingAccount updated via TMF666 PATCH by ' + UserInfo.getName());
75        return null;
76    }
77
78
79    /** NOT APPLICABLE for PATCH — never invoked */
80    global static Map<String, Object> customiseGraphQLQuery(
81        Map<String, Object> querySpec,
82        Map<String, Object> context
83    ) {
84        return new Map<String, Object>();
85    }
86}