---
schema_version: "1"
id: da24685c6b9684664b47a41e060528419e59be687af26d22c3f23ef6645c6c5a
title: IProductOrderGET Apex Interface
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/industries-integrations/comms/guides/Extensibility/TMF622/IProductOrderGET.md
canonical_url: https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderGET.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: fc0225ce0456376d9460ccfe210c7dfce5ef97de94cdfea587ecd9c3591c08c9
products:
  - Communications Cloud TM Forum API
  - Industries Integrations
---
# IProductOrderGET Apex Interface

The `IProductOrderGET` Apex interface provides extensibility for TMF622 GET operations through Apex pre- and post-hooks and GraphQL query customization. Implementations can validate incoming requests, modify the primary read query, and refine or overwrite the response to support business-specific retrieval rules while maintaining TMF-compliant behavior.

This interface supports the following hooks.

- transformRequest
- applyCustomValidations
- customiseGraphQLQuery
- handlePostOperation

**Note**

The `configureDefaultValidations` hook is not used for Product Order GET. `getDefaultValidationSpec()` returns `{}`. If implemented, return the map unchanged.

## Retrieval Lifecycle Use Cases and Hook Mapping

This table lists common use cases and the hooks required for the GET operation.

| Use Case                                | Hook(s)                                     | Description                                                                                                                  | Example Scenarios                                                                                  | Benefit                                                            |
| --------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Resolve a legacy or alias order id      | `transformRequest`                          | Replace `context["id"]` with the canonical Order identifier before GraphQL construction.                                     | • Map `LEGACY-*` / `PoNumber` to `Order.Id`<br>• Accept a partner order id and resolve OrderNumber | Fetch the canonical Order without changing the client URL contract |
| Stash an entitlement flag               | `transformRequest`                          | Put extra keys on the shared `context` map for later hooks.                                                                  | • `isPremiumTenant` for validation and post-processing                                             | Cross-hook coordination without extra round-trips                  |
| Register a custom sub-resource          | `transformRequest`, `customiseGraphQLQuery` | Put `customSubResourceMappings` on context and inject the matching GraphQL node. Both steps are required.                    | • Serve `GET /productOrder/{id}/orderAdjustmentGroup`                                              | Extend GET with org-specific child resources                       |
| Reject the request with a business rule | `applyCustomValidations`                    | Only GET hook that surfaces an HTTP error. Return top-level `validationStatus = "fail"`.                                     | • Cap filter criteria<br>• Forbid jeopardy-alert fields for non-premium tenants                    | Stops invalid retrievals before GraphQL                            |
| Add a custom field to the Order query   | `customiseGraphQLQuery`                     | Return `nodes` targeting handler **aliases** (`productOrder`, `productOrder.productOrderItem`, …).                           | • Add `NegotiatedPrice__c { value }` at the root Order                                             | Shape the primary read without forking the handler                 |
| Filter or sort nested order items       | `customiseGraphQLQuery`                     | `addFilters` / `orderBy` on `productOrder.productOrderItem`. URL `sort` still wins over Apex `orderBy`.                      | • Quantity greater than 1<br>• EffectiveDate DESC                                                  | One API supports channel-specific query shape                      |
| Overwrite the GET body                  | `handlePostOperation`                       | GET **applies** the return when `payloadOverwritten` is true. Single GET: Map. List GET: `recordList` or a raw List of maps. | • Add `customStatusLabel`<br>• Drop Cancelled rows from list GET                                   | Refine or filter the client-visible TMF payload                    |

## transformRequest

This hook transforms the request context before processing any operation. It is invoked early in the request lifecycle, allowing implementers to adjust the context for subsequent extensibility hooks.

Mutating `context["id"]` is written back via `updateIdFromContext`. If the Apex result throws, the exception is caught and logged as WARNING — the original id is kept and GET continues.

### Hook Method

`Map<String, Object> transformRequest(Map<String, Object> context)`

### Sample Apex Implementation

```
global static Map<String, Object> transformRequest(Map<String, Object> context) {
    if (context == null) {
        return context;
    }

    context.put('isPremiumTenant', checkPremiumEntitlement());

    String requestedId = (String) context.get('id');
    if (requestedId != null && requestedId.startsWith('LEGACY-')) {
        Order o = [SELECT Id FROM Order WHERE PoNumber = :requestedId LIMIT 1];
        context.put('id', o.Id);
    }
    return context;
}
```

**Caveat.** Appending to `context["fields"]` does **not** change the primary GraphQL query. This handler hardcodes the query in `buildPrimaryGraphQLQuery`. Use `customiseGraphQLQuery`.

## applyCustomValidations

This hook validates custom business logic before retrieving 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.
- The API request continues normally.
- Other fields in the map are logged but not used.

**Failure**

- Return a map with `validationStatus` set to `"fail"` (case-insensitive).
- The API request is terminated immediately.
- Raise a `ValidationException` containing:
  - message: value from `validationMessage` (or “Validation failed” by default)
  - details: value from `validationDetails` (optional `statusCode` / `errorCode`)

### Hook Method

`Map<String, Object> applyCustomValidations(Map<String, Object> context)`

### Sample Apex Implementation

```
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
    Boolean isPremium = (Boolean) context.get('isPremiumTenant');
    List<Object> fields = (List<Object>) context.get('fields');

    if (isPremium != Boolean.TRUE && fields != null
            && fields.contains('productorderjeopardyalert')) {
        return new Map<String, Object>{
            'validationStatus'  => 'fail',
            'validationMessage' => 'Jeopardy alerts require a premium entitlement.',
            'validationDetails' => new Map<String, Object>{ 'statusCode' => 'FORBIDDEN' }
        };
    }
    return new Map<String, Object>();
}
```

## customiseGraphQLQuery

This hook modifies the **primary** GraphQL read query before execution. GET is the only Product Order verb that builds an initial read query.

Return `{"nodes": [ QueryTransformationNode, ... ]}`. The `graphQLAsMap` argument is `{"query": "<base query>"}` — informational only. There is no supported way to hand back a rewritten query string.

**Node shape**

```
{
  "path": "productOrder.productOrderItem",
  "addFields": ["CustomField__c { value }"],
  "addFilters": { "clearExistingFilters": false, "filterGraphQlSnippet": "Status: {eq: \"Active\"}" },
  "orderBy": { "clearExistingOrderBy": false, "orderGraphQlSnippet": "CreatedDate: {order: DESC}" },
  "insertGraphQlSnippet": "customField: CustomObject__r { Id Name { value } }"
}
```

`path` addresses **aliases**, not Salesforce relationship names. Addressable paths:

| Alias path                                                               | What it addresses                              |
| ------------------------------------------------------------------------ | ---------------------------------------------- |
| productOrder                                                             | Root Order alias in `buildPrimaryGraphQLQuery` |
| productOrder.relatedPartyAccount                                         | Account                                        |
| productOrder.orderDeliveryGroups                                         | OrderDeliveryGroups                            |
| productOrder.productOrderItem                                            | OrderItems                                     |
| productOrder.productOrderItem.itemOrderAction                            | OrderAction                                    |
| productOrder.productOrderItem.itemProduct                                | Product2                                       |
| productOrder.productOrderItem.itemPricebookEntry                         | PricebookEntry                                 |
| productOrder.productOrderItem.itemPricebookEntry.itemProductSellingModel | ProductSellingModel                            |

An unmatched path silently produces no transformation for that node (WARNING log). Auxiliary queries (jeopardy alerts, order-item attributes, relationships, tax lines) run **inside** `executeGraphQL` after this hook and cannot be transformed here.

### Hook Method

`Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context)`

### Sample Apex Implementation

```
global static Map<String, Object> customiseGraphQLQuery(
        Map<String, Object> graphQLAsMap, Map<String, Object> context) {
    return new Map<String, Object>{
        'nodes' => new List<Object>{
            new Map<String, Object>{
                'path'      => 'productOrder',
                'addFields' => new List<Object>{ 'NegotiatedPrice__c { value }' }
            },
            new Map<String, Object>{
                'path'       => 'productOrder.productOrderItem',
                'addFilters' => new Map<String, Object>{
                    'clearExistingFilters' => false,
                    'filterGraphQlSnippet' => 'Quantity: {gt: 1}'
                }
            }
        }
    };
}
```

## handlePostOperation

This hook post-processes the API response after GraphQL execution and TMF conversion. Unlike POST, PATCH, and DELETE, GET **applies** this return value when `payloadOverwritten` is true.

**Parameters**

- `graphQLQueryResultAsMap` — raw GraphQL `{data, errors, extensions}`
- `constructedTMFResponse` — already-TMF622-shaped output. Single GET: flat map. List GET: `{"recordList": [...]}`.
- `context` — the same map from earlier hooks

**Return value processing**

- `{ "payloadOverwritten": false }` (or omit the key) — observe only; output unchanged.
- `{ "payloadOverwritten": true, "updatedPayload": ... }` — replace the response.
  - Single-entity: `updatedPayload` must be a **Map**.
  - List: `updatedPayload` must contain `recordList` as a List, or be a **raw List** of maps.
- Wrong type logs WARNING and no-ops. The caller receives the original response.

### 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) {
    if (constructedTMFResponse.containsKey('recordList')) {
        List<Object> recordList = (List<Object>) constructedTMFResponse.get('recordList');
        List<Object> filtered = new List<Object>();
        for (Object rec : recordList) {
            Map<String, Object> recMap = (Map<String, Object>) rec;
            if (recMap.get('state') != 'Cancelled') {
                filtered.add(recMap);
            }
        }
        return new Map<String, Object>{
            'payloadOverwritten' => true,
            'updatedPayload'     => new Map<String, Object>{ 'recordList' => filtered }
        };
    }

    Map<String, Object> updated = new Map<String, Object>(constructedTMFResponse);
    updated.put('customStatusLabel', deriveFriendlyStatus((String) updated.get('state')));
    return new Map<String, Object>{
        'payloadOverwritten' => true,
        'updatedPayload'     => updated
    };
}
```

## Full Implementation Example

```
global class ProductOrderGETExtension implements comms_apex_ext.IProductOrderGET {

    global static Map<String, Object> transformRequest(Map<String, Object> context) {
        context.put('isPremiumTenant', checkPremiumEntitlement());
        String requestedId = (String) context.get('id');
        if (requestedId != null && requestedId.startsWith('LEGACY-')) {
            Order o = [SELECT Id FROM Order WHERE PoNumber = :requestedId LIMIT 1];
            context.put('id', o.Id);
        }
        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) {
        Boolean isPremium = (Boolean) context.get('isPremiumTenant');
        List<Object> fields = (List<Object>) context.get('fields');
        if (isPremium != Boolean.TRUE && fields != null
                && fields.contains('productorderjeopardyalert')) {
            return new Map<String, Object>{
                'validationStatus'  => 'fail',
                'validationMessage' => 'Jeopardy alerts require a premium entitlement.',
                'validationDetails' => new Map<String, Object>{ 'statusCode' => 'FORBIDDEN' }
            };
        }
        return new Map<String, Object>();
    }

    global static Map<String, Object> customiseGraphQLQuery(
            Map<String, Object> graphQLAsMap, Map<String, Object> context) {
        return new Map<String, Object>{
            'nodes' => new List<Object>{
                new Map<String, Object>{
                    'path'      => 'productOrder',
                    'addFields' => new List<Object>{ 'NegotiatedPrice__c { value }' }
                }
            }
        };
    }

    global static Map<String, Object> handlePostOperation(
            Map<String, Object> graphQLQueryResultAsMap,
            Map<String, Object> constructedTMFResponse,
            Map<String, Object> context) {
        System.debug('ProductOrder GET by ' + UserInfo.getName());
        return new Map<String, Object>{ 'payloadOverwritten' => false };
    }
}
```

## GraphQL Query Transformations

Query transformations enhance GraphQL queries by adding fields, applying filters, or modifying sort order, while preserving the original query definition.

**Note**

For Product Order GET, use handler aliases such as `productOrder` and `productOrder.productOrderItem`. Do not use Salesforce relationship names or `uiapi.query.Order`.

### Query Enhancements: Add Fields

**Original Query (GraphQL):** primary Order selection from `buildPrimaryGraphQLQuery`.

**Transformation Instructions (JSON):**

```
{
  "nodes": [{
    "path": "productOrder",
    "addFields": ["NegotiatedPrice__c { value }"]
  }]
}
```

The field is appended as a sibling of the scalar fields on the root Order alias.

### Query Enhancements: Nested Filter and Root orderBy

**Transformation Instructions (JSON):**

```
{
  "nodes": [
    {
      "path": "productOrder",
      "orderBy": {
        "clearExistingOrderBy": true,
        "orderGraphQlSnippet": "EffectiveDate: {order: DESC}"
      }
    },
    {
      "path": "productOrder.productOrderItem",
      "addFilters": {
        "clearExistingFilters": false,
        "filterGraphQlSnippet": "Quantity: {gt: 1}"
      }
    }
  ]
}
```

URL-level `sort` takes precedence over Apex `orderBy`. A trailing `Id ASC` tiebreaker is always appended.

## See Also

- [Extensibility for TMF622 v5 Product Ordering Management API](https://developer.salesforce.com/docs/industries/communications/guide/TMF622_extensibility.md)
- [IProductOrderPOST Apex Interface](https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderPOST.md)
- [IProductOrderPATCH Apex Interface](https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderPATCH.md)
- [IProductOrderDELETE Apex Interface](https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderDELETE.md)
