# IProductOrderPOST Apex Interface

The `IProductOrderPOST` Apex interface enables customization of POST operations using Apex lifecycle hooks. One handler routes into four mutually exclusive sub-flows from `productOrderItem[].action` (and `product.id`). Implementations can validate and enrich creation, amendment, renewal, or cancellation payloads, and run side effects after the pipeline, 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 POST. If implemented, the methods are required to compile, but they are never invoked (`constructGraphQL` / `constructMutationGraphQL` return null).

- `customiseGraphQLQuery`
- `customiseMutationPayload`

Rewrite the outbound payload in `preServiceHook` via `modifiedPayload`.

## Sub-flows

Mixed actions (`add`+`renew`, `cancel`+anything, and similar) are rejected by `validateSingleActionType` **before any Apex hook**.

| Sub-flow               | How it is detected                                                                 | Pipeline steps                                                                                  |
| ---------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Standard create/submit | Default when no amend/renew/cancel action                                          | `orderIngestion` → `activateOrder` → `submitOrder` (last two skipped if the org has Quotes Tax) |
| Amendment              | Any item `action=amend`, or a non-cancel/non-renew item with existing `product.id` | `initiateAmendment` → `activateOrder` → `submitOrder`                                           |
| Renewal                | Any item `action=renew` (checked first)                                            | `initiateRenewal` → `activateOrder` → `submitOrder`                                             |
| Cancellation           | Any item `action=cancel`                                                           | `initiateCancellation` → `activateOrder` → `submitOrder`                                        |

`preServiceHook` fires **up to three times per request** (once per step) using finalPayload re-invocation: build payload → Apex → execute with `modifiedPayload`. Switch on `serviceName`.

## Creation Lifecycle Use Cases and Hook Mapping

The following table lists common use cases for the POST operation.

| Use Case                           | Hook(s)                                | Description                                                                                                                                                    | Example Scenarios                                                                                            | Benefit                                                    |
| ---------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| Force a sub-flow                   | `transformRequest`                     | Runs **before** sub-flow detection. Mutating `productOrderItem[].action` (or injecting `product.id`) changes which pipeline is built.                          | • Set `action=amend` when `product.id` is present<br>• Route cancel vs renew vs amend to a single action     | Avoid mixed-action rejection and pick the correct pipeline |
| Enforce create rules               | `applyCustomValidations`               | Only loud HTTP error path. Return **top-level** `validationStatus` / `validationMessage`. Nested `result` is silently dropped.                                 | • Require `requestedStartDate` on add items<br>• Duplicate amendment/renewal checks the handler does not run | Prevents invalid orders before service calls               |
| Inject ingestion preferences       | `preServiceHook` (`orderIngestion`)    | Add scalar keys on the initiating payload. `orderGraph` is a live Java object — prefer scalars.                                                                | • `pricingPref`, `taxPref`                                                                                   | Channel-specific pricing/tax without forking the handler   |
| Override amendment quantity deltas | `preServiceHook` (`initiateAmendment`) | Mutate `quantityChanges` before execute.                                                                                                                       | • Org rounding rules                                                                                         | Keep quantity math consistent                              |
| Hold activated but unsubmitted     | `preServiceHook` (`submitOrder`)       | `skipCall: true` is **not** an HTTP error. Skipping an **initiating** step leaves a null order id. Skipping **submitOrder** is the only coherent partial skip. | • Nightly batch submit                                                                                       | Partial pipeline without a broken order                    |
| Audit after POST                   | `handlePostOperation`                  | Return is discarded. Client body is GET re-fetch. A non-empty map fires telemetry (up to 3× from `preServiceHook`).                                            | • Platform events, correlation ids                                                                           | Side effects without shaping the HTTP body                 |

## transformRequest

This hook transforms the request context before processing any operation. It is invoked before sub-flow detection.

### 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.get('productOrderItem') instanceof List<Object>)) {
        return context;
    }
    for (Object itemObj : (List<Object>) body.get('productOrderItem')) {
        Map<String, Object> item = (Map<String, Object>) itemObj;
        Map<String, Object> product = (Map<String, Object>) item.get('product');
        if (product != null && product.get('id') != null && item.get('action') == null) {
            item.put('action', 'amend');
        }
    }
    return context;
}
```

## configureDefaultValidations

Default spec (all true): `orderLevelAttributesValidation`, `relatedPartyValidation`, `productOrderItemsValidation`. Return a map toggling those flags.

Handler `validateAmendmentItems` / `validateRenewalItems` / `validateCancellationItems` do **not** run in the normal request flow (`isXRequest` is still false during validation). Duplicate critical checks in `applyCustomValidations`. Missing `product.id` is enforced later in payload builders as HTTP 400.

### Hook Method

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

## applyCustomValidations

This hook validates custom business logic before the service-call pipeline. 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.
- The API request continues normally.

**Failure**

- Return a **top-level** map with `validationStatus` set to `"fail"`. Nested `"result"` is silently dropped.
- The API request is terminated immediately (`ValidationException`). Default mapping is HTTP 422 unless `validationDetails` overrides.

### 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) {
        return new Map<String, Object>();
    }
    Object items = body.get('productOrderItem');
    if (items instanceof List<Object> && body.get('requestedStartDate') == null) {
        for (Object itemObj : (List<Object>) items) {
            Map<String, Object> item = (Map<String, Object>) itemObj;
            if ('add' == item.get('action') || item.get('action') == null) {
                return new Map<String, Object>{
                    'validationStatus'  => 'fail',
                    'validationMessage' => 'requestedStartDate is required when adding new items'
                };
            }
        }
    }
    return new Map<String, Object>();
}
```

## preServiceHook

This hook runs once per pipeline step (up to three times). Return `{skipCall, modifiedPayload}`. Exceptions are swallowed per step (WARNING); the original payload is used. `skipCall` is not an HTTP error.

| serviceName          | Payload shape                                                                       | Notes                                                     |
| -------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------- |
| orderIngestion       | `{orderGraph}`                                                                      | Standard create. Prefer scalar keys such as `pricingPref` |
| initiateAmendment    | `{amendAssetIds, amendStartDate, amendOutputType, itemQuantities, quantityChanges}` | Amendment initiating step                                 |
| initiateRenewal      | `{renewAssetIds, renewStartDate, renewOutputType, renewEndDate?}`                   | Renewal initiating step                                   |
| initiateCancellation | `{cancelAssetIds, cancelStartDate, cancelOutputType}`                               | Cancellation initiating step                              |
| activateOrder        | `{orderId, orderNumber}`                                                            | Shared. Skipped on standard flow when org has Quotes Tax  |
| submitOrder          | `{orderId, orderNumber}`                                                            | Shared. `skipCall` here is the only coherent partial skip |

### 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) {
    if (serviceName == 'orderIngestion' && payload != null) {
        payload.put('pricingPref', 'CatalogRates');
        payload.put('taxPref', 'SkipSync');
        return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
    }
    if (serviceName == 'initiateAmendment' && payload != null
            && payload.get('quantityChanges') instanceof Map<String, Object>) {
        Map<String, Object> changes = (Map<String, Object>) payload.get('quantityChanges');
        payload.put('quantityChanges', changes);
        return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
    }
    if (serviceName == 'submitOrder') {
        return new Map<String, Object>{ 'skipCall' => true }; // hold for nightly batch
    }
    return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
}
```

## handlePostOperation

`graphQLQueryResultAsMap` is synthetic `{"data": flowResult}` from `orderIngestionResult` / `initiateAmendmentResult` / `initiateRenewalResult` / `initiateCancellationResult`. `constructedTMFResponse` is the **request body**. Return is **discarded**. The client representation is GET `retrieveResource`.

### 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 POST completed by ' + UserInfo.getName());
    return null; // discarded; client body is GET re-fetch
}
```

## Full Implementation Example

```
global class ProductOrderPOSTExtension implements comms_apex_ext.IProductOrderPOST {

    global static Map<String, Object> transformRequest(Map<String, Object> context) {
        Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
        if (body == null || !(body.get('productOrderItem') instanceof List<Object>)) {
            return context;
        }
        for (Object itemObj : (List<Object>) body.get('productOrderItem')) {
            Map<String, Object> item = (Map<String, Object>) itemObj;
            Map<String, Object> product = (Map<String, Object>) item.get('product');
            if (product != null && product.get('id') != null && item.get('action') == null) {
                item.put('action', 'amend');
            }
        }
        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) {
        if (serviceName == 'orderIngestion' && payload != null) {
            payload.put('pricingPref', 'CatalogRates');
            return new Map<String, Object>{ 'skipCall' => false, 'modifiedPayload' => payload };
        }
        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)
- [IProductOrderPATCH Apex Interface](/docs/industries/communications/guide/IProductOrderPATCH.md)
- [IProductOrderDELETE Apex Interface](/docs/industries/communications/guide/IProductOrderDELETE.md)
