IProductOrderPATCH Apex Interface

The IProductOrderPATCH Apex interface supports extensibility for PATCH operations by allowing validation, transformation, and post-processing of update requests. Unlike GET and DELETE, PATCH does not build a GraphQL document. It uses a declarative service-call pipeline with a single step named placeSupplemental. Implementations can enforce business rules, rewrite the outbound payload, and run side effects while the client-visible order is re-fetched via GET.

This interface supports the following hooks.

  • transformRequest
  • configureDefaultValidations
  • applyCustomValidations
  • preServiceHook
  • handlePostOperation

Note

These hooks are not used for Product Order PATCH. If implemented, the methods are required to compile, but they are never invoked.

  • customiseGraphQLQuery
  • customiseMutationPayload

Rewrite the outbound payload in preServiceHook via modifiedPayload.

{id} is an OrderNumber. Draft orders amend in place via OrderIngestionServiceImpl.ingestOrder. Activated orders create a supplemental order via PlaceSupplementalTransactionService. Flipping isDraft in modifiedPayload redirects that routing.

Update Lifecycle Use Cases and Hook Mapping 

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

Use CaseHook(s)DescriptionExample ScenariosBenefit
Resolve a PO- number to OrderNumbertransformRequestMutations to id and requestBody are written back before validation and the pipeline.PO-*Order.OrderNumber via PoNumberCanonical id before placeSupplemental
Strip a locked fieldtransformRequestRemove fields before the change graph is built. preServiceHook sees an already-built changeGraph.• Drop priceBookNamePrevents updates to protected catalog fields
Require description when cancellingapplyCustomValidationsOnly PATCH hook that surfaces an HTTP error. Return top-level validationStatus = "fail". Nested result is silently dropped.state = Cancelled with blank descriptionFail closed before the service call
Force in-place draft ingestpreServiceHookserviceName is always placeSupplemental. Payload: {relatedTransactionId, changeGraph, isDraft}.• Set isDraft true so a supplemental order is never createdOrg-wide routing policy
Dry-run without placing the transactionpreServiceHookskipCall: true is not an HTTP error. Exceptions are swallowed (WARNING).dryRun on request bodyPreview without persistence
Audit after PATCHhandlePostOperationReturn discarded. constructedTMFResponse is the request body, not the patched order. Client body is GET re-fetch.• Telemetry, correlation idsSide effects without shaping the HTTP body

transformRequest 

This hook transforms the request context before processing any operation. Put derived values on requestBody here — the change graph does not exist yet.

Hook Method 

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

Sample Apex Implementation 

1global static Map<String, Object> transformRequest(Map<String, Object> context) {
2    Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
3    if (body != null && body.containsKey('priceBookName')) {
4        body.remove('priceBookName');
5    }
6    String requestedId = (String) context.get('id');
7    if (requestedId != null && requestedId.startsWith('PO-')) {
8        Order o = [SELECT OrderNumber FROM Order WHERE PoNumber = :requestedId LIMIT 1];
9        context.put('id', o.OrderNumber);
10    }
11    return context;
12}

configureDefaultValidations 

Default spec: {"productOrderItemsValidation": true}. All other PATCH fields are optional at the framework layer. Return a map toggling that flag.

Hook Method 

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

applyCustomValidations 

This hook validates custom business logic before updating a Product Order. 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 any value other than "fail" (case-insensitive), or an empty map.

Failure

  • Return a top-level map with validationStatus set to "fail".
  • Nested "result" is silently dropped.
  • The client receives an HTTP error (default 422 unless validationDetails overrides).

Do not use skipCall or thrown exceptions in preServiceHook as HTTP errors — both are silent.

Hook Method 

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

Sample Apex Implementation 

1global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
2    Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
3    if (body != null && body.get('state') == 'Cancelled'
4            && String.isBlank((String) body.get('description'))) {
5        return new Map<String, Object>{
6            'validationStatus'  => 'fail',
7            'validationMessage' => 'A description is required when cancelling an order via PATCH'
8        };
9    }
10    return new Map<String, Object>();
11}

preServiceHook 

The executor runs twice (finalPayload re-invocation). First call builds {relatedTransactionId, changeGraph, isDraft}. Apex may rewrite that map. Second call executes doPlaceTransaction(finalPayload).

changeGraph is a live SObjectGraphRequest — treat as opaque unless replacing it. Prefer scalar keys such as isDraft.

Hook Method 

Map<String, Object> preServiceHook(String serviceName, Map<String, Object> payload, Map<String, Object> executionContext, Map<String, Object> metadata)

Sample Apex Implementation 

1global static Map<String, Object> preServiceHook(String serviceName, Map<String, Object> payload,
2        Map<String, Object> executionContext, Map<String, Object> metadata) {
3    Map<String, Object> body = (Map<String, Object>) executionContext.get('requestBody');
4    if (body != null && body.get('dryRun') == true) {
5        return new Map<String, Object>{ 'skipCall' => true };
6    }
7    if (serviceName == 'placeSupplemental' && payload != null) {
8        payload.put('isDraft', true); // never create a supplemental order in this org
9        return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
10    }
11    return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
12}

handlePostOperation 

graphQLQueryResultAsMap is synthetic {"data": placeSupplementalResult} (not GraphQL). constructedTMFResponse is the request body. Return is discarded. A non-empty return map fires extensibility telemetry.

Hook Method 

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

Sample Apex Implementation 

1global static Map<String, Object> handlePostOperation(
2        Map<String, Object> graphQLQueryResultAsMap,
3        Map<String, Object> constructedTMFResponse,
4        Map<String, Object> context) {
5    System.debug('ProductOrder PATCH by ' + UserInfo.getName());
6    return null; // discarded; client body comes from GET re-fetch
7}

Full Implementation Example 

1global class ProductOrderPATCHExtension implements comms_apex_ext.IProductOrderPATCH {
2
3    global static Map<String, Object> transformRequest(Map<String, Object> context) {
4        Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
5        if (body != null && body.containsKey('priceBookName')) {
6            body.remove('priceBookName');
7        }
8        return context;
9    }
10
11    global static Map<String, Boolean> configureDefaultValidations(
12            Map<String, Boolean> defaultValidationConfiguration, Map<String, Object> context) {
13        return defaultValidationConfiguration;
14    }
15
16    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
17        return new Map<String, Object>();
18    }
19
20    global static Map<String, Object> customiseGraphQLQuery(
21            Map<String, Object> graphQLAsMap, Map<String, Object> context) {
22        return new Map<String, Object>(); // never invoked
23    }
24
25    global static Map<String, Object> customiseMutationPayload(
26            Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {
27        return new Map<String, Object>(); // never invoked
28    }
29
30    global static Map<String, Object> preServiceHook(String serviceName, Map<String, Object> payload,
31            Map<String, Object> executionContext, Map<String, Object> metadata) {
32        return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
33    }
34
35    global static Map<String, Object> handlePostOperation(
36            Map<String, Object> graphQLQueryResultAsMap,
37            Map<String, Object> constructedTMFResponse,
38            Map<String, Object> context) {
39        return null;
40    }
41}

See Also