The IProductOrderDELETE Apex interface provides extensibility for DELETE operations through Apex-based validations, mutation-payload adjustments, and post-operation processing. The handler does not issue DELETE DML against Order. It performs a soft delete: a GraphQL OrderUpdate mutation whose Order input is intentionally empty. The Apex class must inject the deleted marker (for example Status = "Cancelled") via customiseMutationPayload. Without that injection, the mutation is structurally valid but semantically empty — nothing about the order visibly changes.
This interface supports the following hooks.
transformRequest
applyCustomValidations
customiseMutationPayload
handlePostOperation
These hooks are not used for Product Order DELETE.
configureDefaultValidations — getDefaultValidationSpec() returns {}. Apex is not called when the spec is empty.
customiseGraphQLQuery — required to compile; never invoked.
Note
Availability. DELETE is interface-mandatory. If no Apex class implements IProductOrderDELETE, processRequest throws HTTP 400 before ID resolution, validation, or GraphQL. Exactly one implementor per org.
{id} is treated as an OrderNumber and resolved to Order.Id. After transformRequest, the handler honors any Apex-written id, then unconditionally re-resolves it as an OrderNumber. Write an OrderNumber into context["id"], not a Salesforce ID.
The HTTP response body is always null. handlePostOperation return is discarded.
Deletion Lifecycle Use Cases and Hook Mapping
The following table lists common use cases for the DELETE operation.
Use Case
Hook(s)
Description
Example Scenarios
Benefit
Inject cancelled Status and audit fields
customiseMutationPayload
This is the only hook that makes DELETE real. Target alias softDeleteProductOrder. Use addFields on the empty OrderUpdate.
• Status = Cancelled • Deleted_By__c, Deletion_Timestamp__c
Turns the empty mutation into a real soft-delete
Veto Activated, cascade, or permission
applyCustomValidations
Fail before mutation. Return top-levelvalidationStatus / validationMessage / validationDetails. Nested result is ignored. Default HTTP 422; details can override (404, 403, 409).
• Block Activated / InProgress • Block if active OrderItems exist • Custom permission Can_Delete_Product_Order
Database is never touched on fail
Stamp a correlation id
transformRequest
Extra keys on the shared context are visible to later hooks and platform events.
• auditTraceId
Traceability across cascade cancel and events
Seed an OrderNumber alias
transformRequest
Write OrderNumber into context["id"] before the handler lookup.
• Partner order number
Correct OrderNumber re-resolution
Cascade-cancel items and publish an event
handlePostOperation
Runs even on GraphQL errors. constructedTMFResponse is deleteResult (success, deletedId, errors). Check success before DML.
• Cancel OrderItems • Publish OrderDeleted__e
Side effects after a successful mutation
transformRequest
This hook transforms the request context before processing any operation. It is invoked early because an implementor is guaranteed on every DELETE that reaches GraphQL.
This hook validates custom business logic before constructing the mutation. If validation fails, it rejects the request and returns an error response. The database is never touched on fail. After this hook, context["id"] is already the resolved Salesforce Order Id.
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 ignored.
Default HTTP 422UNPROCESSABLE_REQUEST; validationDetails.statusCode / errorCode can override.
1global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {2 String orderId = (String) context.get('id');3 Order ord = [SELECT Id, Status FROM Order WHERE Id = :orderId LIMIT 1];45 if (ord.Status == 'Activated' || ord.Status == 'InProgress') {6 return failValidation('Cannot delete an order in status ' + ord.Status, 400, 'BAD_REQUEST');7 }8 Integer dependentItems = [9 SELECT COUNT() FROM OrderItem WHERE OrderId = :orderId AND Status != 'Cancelled'10 ];11 if (dependentItems > 0) {12 return failValidation('Cannot delete order with ' + dependentItems + ' active order items', 409, 'CONFLICT');13 }14 if (!FeatureManagement.checkPermission('Can_Delete_Product_Order')) {15 return failValidation('Insufficient permissions to delete this ProductOrder', 403, 'FORBIDDEN');16 }17 return new Map<String, Object>();18}1920private static Map<String, Object> failValidation(String message, Integer statusCode, String errorCode) {21 return new Map<String, Object>{22 'validationStatus' => 'fail',23 'validationMessage' => message,24 'validationDetails' => new Map<String, Object>{25 'statusCode' => statusCode,26 'errorCode' => errorCode27 }28 };29}
customiseMutationPayload
This hook modifies the mutation payload before the GraphQL OrderUpdate is executed. The base mutation is Order{}. Return {"nodes":[{path, addFields, modifyInput, insertGraphQlSnippet, updateGraphQlSnippet}]}. Target alias softDeleteProductOrder.
mutationGraphQLPayload is {"query": "<mutation string>"}.
This hook runs after the mutation, even if GraphQL returned errors. Use it only for side effects. Do not try to shape the HTTP body — it is always null.
Implement all six methods. Combine correlation id, state/cascade/permission veto, Status injection, and cascade-cancel on success. Return an empty map from customiseGraphQLQuery.
1global class ProductOrderDELETEExtension implements comms_apex_ext.IProductOrderDELETE {23 global static Map<String, Object> transformRequest(Map<String, Object> context) {4 context.put('auditTraceId', new Uuid().toString());5 return context;6 }78 global static Map<String, Boolean> configureDefaultValidations(9 Map<String, Boolean> defaultValidationConfiguration,10 Map<String, Object> context) {11 return defaultValidationConfiguration; // inert12 }1314 global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {15 return new Map<String, Object>();16 }1718 global static Map<String, Object> customiseGraphQLQuery(19 Map<String, Object> graphQLAsMap, Map<String, Object> context) {20 return new Map<String, Object>(); // never invoked21 }2223 global static Map<String, Object> customiseMutationPayload(24 Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {25 return new Map<String, Object>{26 'nodes' => new List<Object>{27 new Map<String, Object>{28 'path' => 'softDeleteProductOrder',29 'addFields' => new Map<String, Object>{ 'Status' => 'Cancelled' }30 }31 }32 };33 }3435 global static Map<String, Object> handlePostOperation(36 Map<String, Object> graphQLQueryResultAsMap,37 Map<String, Object> constructedTMFResponse,38 Map<String, Object> context) {39 return new Map<String, Object>();40 }41}
GraphQL Mutation — Delete Operations
Delete for Product Order is a soft OrderUpdate with an empty Order input. The primary extension mechanism is addFields (or modifyInput) on alias softDeleteProductOrder.