# 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 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-level** `validationStatus = "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.

### Hook Method

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

### Sample Apex Implementation

```
global static Map<String, Object> transformRequest(Map<String, Object> context) {
    Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
    if (body != null && body.containsKey('priceBookName')) {
        body.remove('priceBookName');
    }
    String requestedId = (String) context.get('id');
    if (requestedId != null && requestedId.startsWith('PO-')) {
        Order o = [SELECT OrderNumber FROM Order WHERE PoNumber = :requestedId LIMIT 1];
        context.put('id', o.OrderNumber);
    }
    return context;
}
```

## 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

```
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
    Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
    if (body != null && body.get('state') == 'Cancelled'
            && String.isBlank((String) body.get('description'))) {
        return new Map<String, Object>{
            'validationStatus'  => 'fail',
            'validationMessage' => 'A description is required when cancelling an order via PATCH'
        };
    }
    return new Map<String, Object>();
}
```

## 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

```
global static Map<String, Object> preServiceHook(String serviceName, Map<String, Object> payload,
        Map<String, Object> executionContext, Map<String, Object> metadata) {
    Map<String, Object> body = (Map<String, Object>) executionContext.get('requestBody');
    if (body != null && body.get('dryRun') == true) {
        return new Map<String, Object>{ 'skipCall' => true };
    }
    if (serviceName == 'placeSupplemental' && payload != null) {
        payload.put('isDraft', true); // never create a supplemental order in this org
        return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
    }
    return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
}
```

## 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

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

## Full Implementation Example

```
global class ProductOrderPATCHExtension implements comms_apex_ext.IProductOrderPATCH {

    global static Map<String, Object> transformRequest(Map<String, Object> context) {
        Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
        if (body != null && body.containsKey('priceBookName')) {
            body.remove('priceBookName');
        }
        return context;
    }

    global static Map<String, Boolean> configureDefaultValidations(
            Map<String, Boolean> defaultValidationConfiguration, Map<String, Object> context) {
        return defaultValidationConfiguration;
    }

    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
        return new Map<String, Object>();
    }

    global static Map<String, Object> customiseGraphQLQuery(
            Map<String, Object> graphQLAsMap, Map<String, Object> context) {
        return new Map<String, Object>(); // never invoked
    }

    global static Map<String, Object> customiseMutationPayload(
            Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {
        return new Map<String, Object>(); // never invoked
    }

    global static Map<String, Object> preServiceHook(String serviceName, Map<String, Object> payload,
            Map<String, Object> executionContext, Map<String, Object> metadata) {
        return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
    }

    global static Map<String, Object> handlePostOperation(
            Map<String, Object> graphQLQueryResultAsMap,
            Map<String, Object> constructedTMFResponse,
            Map<String, Object> context) {
        return null;
    }
}
```

## See Also

- [Extensibility for TMF622 v5 Product Ordering Management API](/docs/industries/communications/guide/TMF622\_extensibility.md)
- [IProductOrderGET Apex Interface](/docs/industries/communications/guide/IProductOrderGET.md)
- [IProductOrderPOST Apex Interface](/docs/industries/communications/guide/IProductOrderPOST.md)
- [IProductOrderDELETE Apex Interface](/docs/industries/communications/guide/IProductOrderDELETE.md)
