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 Case
Hook(s)
Description
Example Scenarios
Benefit
Resolve a PO- number to OrderNumber
transformRequest
Mutations to id and requestBody are written back before validation and the pipeline.
• PO-* → Order.OrderNumber via PoNumber
Canonical id before placeSupplemental
Strip a locked field
transformRequest
Remove fields before the change graph is built. preServiceHook sees an already-built changeGraph.
• Drop priceBookName
Prevents updates to protected catalog fields
Require description when cancelling
applyCustomValidations
Only PATCH hook that surfaces an HTTP error. Return top-levelvalidationStatus = "fail". Nested result is silently dropped.
• state = Cancelled with blank description
Fail closed before the service call
Force in-place draft ingest
preServiceHook
serviceName is always placeSupplemental. Payload: {relatedTransactionId, changeGraph, isDraft}.
• Set isDraft true so a supplemental order is never created
Org-wide routing policy
Dry-run without placing the transaction
preServiceHook
skipCall: true is not an HTTP error. Exceptions are swallowed (WARNING).
• dryRun on request body
Preview without persistence
Audit after PATCH
handlePostOperation
Return discarded. constructedTMFResponse is the request body, not the patched order. Client body is GET re-fetch.
• Telemetry, correlation ids
Side 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.
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 executesdoPlaceTransaction(finalPayload).
changeGraph is a live SObjectGraphRequest — treat as opaque unless replacing it. Prefer scalar keys such as isDraft.
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 org9 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.