---
schema_version: "1"
id: 45e11383e29d7d131526bff4505f891e19264cd18f642c32a24736e5f7fcb343
title: ITroubleTicketManagementPATCH Apex Interface
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/industries-integrations/comms/guides/Extensibility/ITroubleTicketManagementPATCH.md
canonical_url: https://developer.salesforce.com/docs/industries/communications/guide/ITroubleTicketManagementPATCH.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 16b6a5676951b37fa4b5141a36d3955950d2a24f9d925125dc3d6a285dbd122a
products:
  - Communications Cloud TM Forum API
  - Industries Integrations
---
# ITroubleTicketManagementPATCH Apex Interface

The ITroubleTicketManagementPATCH interface supports extensibility for PATCH operations by allowing validation, transformation, and post-processing of update requests. Implementations can enforce business rules, adjust mutation payloads, and refine the resulting responses maintaining TMF-compliant behavior.

This interface supports the following hooks.

- [transformRequest](#transformrequest)
- [applyCustomValidations](#applycustomvalidations)
- [customiseMutationPayload](#customisemutationpayload)
- [handlePostOperation](#handlepostoperation)

**note:** Following hook is not used for the Trouble Ticket Management API. If implemented, the method may be invoked, but its return values are ignored.

- `configureDefaultValidations`

## Update Lifecycle Use Cases and Hook Mapping

The following table lists common use cases and hooks required for PATCH operation.

| **Use Case**                    | **Hook(s)**                | **Description** <div style="width:290px"></div>                                                                                                                                                                   | **Example Scenarios** <div style="width:290px"></div>                                                                                                                                                                                 | **Benefit** <div style="width:290px"></div>                                                        |
| ------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Enforce Status Transitions**  | `applyCustomValidations`   | Validates requested status changes before updating the record to ensure transitions adhere to defined lifecycle rules, regulatory constraints, or business policies. Stops invalid or unauthorized state changes. | • Prevent transition from _Inactive_ → _Active_ without required approvals• Block changes to _Suspended_ unless preconditions are met• Enforce linear lifecycle progression (e.g., _Pending_ → _Active_, but not _Closed_ → _Active_) | Prevents invalid or noncompliant state transitions and preserves data integrity                    |
| **Automatic Field Population**  | `customiseMutationPayload` | Automatically populate fields like Reason or Origin during updates.                                                                                                                                               | • Add Reason and Origin fields• Track system-initiated vs. user-initiated changes                                                                                                                                                     | Provides a complete and reliable audit trail for internal governance and external compliance needs |
| **Validate Data Format**        | `applyCustomValidations`   | Ensures updated data—such as contact details, identifiers, or structured fields—meets required formatting rules before the update is processed. Prevents malformed or inconsistent data from entering the system. | • Validate phone number format• Confirm email structure and domain correctness• Ensure address fields follow standardized formats                                                                                                     | Ensures required fields are always set without requiring client to provide them                    |
| **Response Enrichment**         | `handlePostOperation`      | Automatically enriches the update payload with audit metadata before persistence. Ensures all modifications are traceable and compliant with auditing standards.                                                  | • Add “modifiedBy” and timestamp fields• Track system-initiated vs. user-initiated changes• Add audit IDs for downstream monitoring systems                                                                                           | Provides a complete and reliable audit trail for internal governance and external compliance needs |
| **CaseNumber to Id Resolution** | `transformRequest`         | Resolve CaseNumber identifiers to Salesforce Ids for API compatibility.                                                                                                                                           | • An external system sends a CaseNumber (e.g., 00001027), which is resolved to a Salesforce record ID for API processing                                                                                                              | Support external systems that use CaseNumber instead of Salesforce Id                              |

## 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.

## Hook Method

`Map<String, Object> transformRequest(Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> transformRequest(Map<String, Object> context) {
    if (context == null) {
        return context;
    }

    Object idObj = context.get('id');
    if (idObj == null || !(idObj instanceof String)) {
        return context;
    }

    String idValue = (String)idObj;

    // Check if the ID looks like a Salesforce ID (starts with 500, 00X, etc.)
    // If not, assume it's a CaseNumber and resolve it
    if (idValue.length() == 15 || idValue.length() == 18) {
        // Looks like a Salesforce ID, return context as-is
        return context;
    }

    // Query Case objects where CaseNumber matches the provided ID
    List<Case> cases = [
        SELECT Id, CaseNumber
        FROM Case
        WHERE CaseNumber = :idValue
        LIMIT 1
    ];

    // If found, replace the ID in context with the Salesforce ID
    if (!cases.isEmpty() && cases[0].CaseNumber != null) {
        context.put('id', cases[0].Id);
    }

    return context;
}

```

## applyCustomValidations

This hook validates custom business logic before updating a customer data. If validation fails, 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).
- 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 key (or "Validation failed" by default)
  - details: value from validationDetails key (optional)
- The client receives an HTTP 400 error response.

## Hook Method

`Map<String, Object> applyCustomValidations(Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
    // Input context:
    // {
    //   api: 'TroubleTicketManagement',
    //   version: '5.0',
    //   id: '00001027',
    //   requestBody: {
    //     status: 'Closed',
    //     name: 'Updated Name'
    //   }
    // }

    String troubleTicketId = (String) context.get('id');
    Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');

    if (requestBody == null || !requestBody.containsKey('status')) {
        // No status update requested, validation passes
        return null;
    }

    String newStatus = (String) requestBody.get('status');

    Map<String, Object> validationResult = new Map<String, Object>();

    try {
        // Fetch current trouble ticket status
        Case troubleTicket = [SELECT Id, Status, CaseNumber FROM Case WHERE CaseNumber = :troubleTicketId LIMIT 1];
        String currentStatus = troubleTicket.Status;

        // Validate status transition: Cannot change from Closed to any other status
        if ('Closed'.equals(currentStatus) && !'Closed'.equals(newStatus)) {
            validationResult.put('validationStatus', 'FAIL');
            validationResult.put('validationMessage', 'Cannot change status from Closed to ' + newStatus);
            validationResult.put('validationDetails', new Map<String, Object>{
                'denialReason' => 'INVALID_STATUS_TRANSITION',
                'currentStatus' => currentStatus,
                'requestedStatus' => newStatus
            });
        } else {
            // Validation passes
            validationResult.put('validationStatus', 'PASS');
            validationResult.put('validationMessage', 'Status transition is valid');
        }
    } catch (Exception e) {
        // FAIL: Error during validation
        validationResult.put('validationStatus', 'FAIL');
        validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
        validationResult.put('validationDetails', new Map<String, Object>{
            'errorType' => e.getTypeName()
        });
    }

    return validationResult;
}

```

## customiseMutationPayload

This hook modifies the mutation payload before updating a customer record. Use this to add computed fields, transform values, or apply business logic transformations.

## Hook Method

`Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> customiseMutationPayload(
    Map<String, Object> mutationGraphQLPayload,
    Map<String, Object> context
) {
    // Create mutation transformation specification
    List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();

    // Node 1: Add custom input fields to the Case mutation
    // Path: troubleTicket (the Case mutation alias)
    Map<String, Object> modifyInputNode = new Map<String, Object>{
        'path' => 'troubleTicket',
        'modifyInput' => new Map<String, Object>{
            'addInputFields' => new Map<String, Object>{
                'Reason' => 'Updated via PATCH API',
                'Origin' => 'Web'
            }
        }
    };
    transformationNodes.add(modifyInputNode);

    // Return transformation specification
    return new Map<String, Object>{
        'nodes' => transformationNodes
    };
}

```

**note:** For Trouble Ticket Management API, use path troubleTicket to target the main Case mutation

## customiseGraphQLQuery

This hook modifies the GraphQL query before execution to add fields, filters, or transformations. Returns a map with a `spec` key containing a list of `QueryTransformationNode` objects.

## Hook Method

`Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> customiseGraphQLQuery(
    Map<String, Object> graphQLAsMap,
    Map<String, Object> context
) {
    // Create transformation specification to add custom fields
    List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();

    // Add custom fields to the Case node
    // Path: troubleTicket (the Case alias in the GraphQL query)
    Map<String, Object> addFieldsNode = new Map<String, Object>{
        'path' => 'troubleTicket',
        'addFields' => new List<String>{'IsClosed', 'SlaStartDate', 'SlaExitDate'}
    };
    transformationNodes.add(addFieldsNode);

    // Return transformation specification
    return new Map<String, Object>{
        'nodes' => transformationNodes
    };
}


```

## handlePostOperation

This hook post-processes and transforms the API response after data is updated.

## Hook Method

`Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> handlePostOperation(
    Map<String, Object> graphQLQueryResultAsMap,
    Map<String, Object> constructedTMFResponse,
    Map<String, Object> context
) {
    // Input TMF response:
    // {
    //   id: '00001027',
    //   name: 'Updated Trouble Ticket',
    //   status: 'In Progress',
    //   type: 'TroubleTicket'
    // }

    String ticketId = (String) constructedTMFResponse.get('id');

    if (ticketId != null) {
        // Fetch additional data for enrichment
        Case caseRecord = [SELECT Id, CaseNumber, CreatedDate, LastModifiedDate, Status FROM Case WHERE CaseNumber = :ticketId LIMIT 1];

        // Add calculated fields
        if (caseRecord.CreatedDate != null && caseRecord.LastModifiedDate != null) {
            Long hoursSinceLastUpdate = (caseRecord.LastModifiedDate.getTime() - caseRecord.CreatedDate.getTime()) / (1000 * 60 * 60);
            constructedTMFResponse.put('hoursSinceCreation', hoursSinceLastUpdate);
        }

        // Add audit information
        constructedTMFResponse.put('lastUpdatedAt', DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
        constructedTMFResponse.put('updatedBy', UserInfo.getName());
    }

    // Return enriched response
    return constructedTMFResponse;

    // Output response:
    // {
    //   id: '00001027',
    //   name: 'Updated Trouble Ticket',
    //   status: 'In Progress',
    //   type: 'TroubleTicket',
    //   hoursSinceCreation: 48,
    //   lastUpdatedAt: '2024-01-17T10:30:00Z',
    //   updatedBy: 'John Smith'
    // }
}


```

## Full Implementation Example

Here's a complete sample Apex implementation using all supported hooks.

```
/**
 * Complete Trouble Ticket Management PATCH API Extension
 * Demonstrates all applicable lifecycle hooks with business logic
 */
public class TroubleTicketManagementPATCHExtension implements comms_apex_ext.ITroubleTicketManagementPATCH {

    /**
     * Hook 1: Transform request
     */
    public Map<String, Object> transformRequest(Map<String, Object> context) {
        if (context == null) {
            return context;
        }

        Object idObj = context.get('id');
        if (idObj != null && idObj instanceof String) {
            String idValue = (String)idObj;

            // If not a Salesforce ID, resolve CaseNumber to Id
            if (idValue.length() != 15 && idValue.length() != 18) {
                List<Case> cases = [
                    SELECT Id, CaseNumber
                    FROM Case
                    WHERE CaseNumber = :idValue
                    LIMIT 1
                ];

                if (!cases.isEmpty()) {
                    context.put('id', cases[0].Id);
                }
            }
        }

        return context;
    }

    /**
     * Hook 2: Apply custom validations
     */
    public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
        String troubleTicketId = (String) context.get('id');
        Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');

        if (requestBody == null || !requestBody.containsKey('status')) {
            return null;
        }

        String newStatus = (String) requestBody.get('status');

        try {
            Case troubleTicket = [SELECT Id, Status, CaseNumber FROM Case WHERE CaseNumber = :troubleTicketId LIMIT 1];
            String currentStatus = troubleTicket.Status;

            // Validate status transition: Cannot change from Closed to any other status
            if ('Closed'.equals(currentStatus) && !'Closed'.equals(newStatus)) {
                return new Map<String, Object>{
                    'validationStatus' => 'FAIL',
                    'validationMessage' => 'Cannot change status from Closed to ' + newStatus,
                    'validationDetails' => new Map<String, Object>{
                        'denialReason' => 'INVALID_STATUS_TRANSITION',
                        'currentStatus' => currentStatus,
                        'requestedStatus' => newStatus
                    }
                };
            }
        } catch (Exception e) {
            return new Map<String, Object>{
                'validationStatus' => 'FAIL',
                'validationMessage' => 'Validation error: ' + e.getMessage()
            };
        }

        return null;
    }

    /**
     * Hook 3: Customize GraphQL query
     */
    public Map<String, Object> customiseGraphQLQuery(
        Map<String, Object> graphQLAsMap,
        Map<String, Object> context
    ) {
        // Add custom fields to the query if needed
        List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();

        transformationNodes.add(new Map<String, Object>{
            'path' => 'troubleTicket',
            'addFields' => new List<String>{'IsClosed', 'SlaStartDate'}
        });

        return new Map<String, Object>{ 'nodes' => transformationNodes };
    }

    /**
     * Hook 4: Customize mutation payload
     */
    public Map<String, Object> customiseMutationPayload(
        Map<String, Object> mutationGraphQLPayload,
        Map<String, Object> context
    ) {
        List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();

        // Add custom input fields
        Map<String, Object> modifyInput = new Map<String, Object>{
            'addInputFields' => new Map<String, Object>{
                'Reason' => 'Updated via PATCH API',
                'Origin' => 'Web'
            }
        };

        transformationNodes.add(new Map<String, Object>{
            'path' => 'troubleTicket',
            'modifyInput' => modifyInput
        });

        return new Map<String, Object>{ 'nodes' => transformationNodes };
    }

    /**
     * Hook 5: Post-process response
     */
    public Map<String, Object> handlePostOperation(
        Map<String, Object> graphQLQueryResultAsMap,
        Map<String, Object> constructedTMFResponse,
        Map<String, Object> context
    ) {
        String ticketId = (String) constructedTMFResponse.get('id');

        if (ticketId != null) {
            try {
                Case caseRecord = [SELECT Id, CaseNumber, CreatedDate, LastModifiedDate FROM Case WHERE CaseNumber = :ticketId LIMIT 1];

                if (caseRecord.CreatedDate != null && caseRecord.LastModifiedDate != null) {
                    Long hoursSinceCreation = (caseRecord.LastModifiedDate.getTime() - caseRecord.CreatedDate.getTime()) / (1000 * 60 * 60);
                    constructedTMFResponse.put('hoursSinceCreation', hoursSinceCreation);
                }

                constructedTMFResponse.put('lastUpdatedAt', DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
                constructedTMFResponse.put('updatedBy', UserInfo.getName());
            } catch (Exception e) {
                System.debug('Error enriching trouble ticket data: ' + e.getMessage());
            }
        }

        return constructedTMFResponse;
    }

    /**
     * NOT APPLICABLE: configureDefaultValidations is not used for Trouble Ticket Management API
     * If implemented, this method will be invoked but return values will be ignored.
     */
    public Map<String, Boolean> configureDefaultValidations(
        Map<String, Boolean> defaultValidationConfiguration,
        Map<String, Object> context
    ) {
        // This hook is not applicable for Trouble Ticket Management API
        // Return the configuration unchanged
        return defaultValidationConfiguration;
    }
}

```

## Response Structure

Successful PATCH Response

```
{
  "id": "00001027",
  "name": "Updated Trouble Ticket Name",
  "type": "TroubleTicket",
  "href": "/services/data/v67.0/connect/comms/troubleticket/v5/troubleticket/00001027",
  "description": "Updated description",
  "status": "In Progress",
  "ticketType": "Network",
  "priority": "High",
  "creationDate": "2024-01-15T10:00:00Z",
  "lastUpdate": "2024-01-17T14:30:00Z"
}


```

## GraphQL Mutation - Update Operations

Update mutations modify existing records and return the updated records with the specified output fields.

### Update Enhancements: Add Output Fields

**Original Mutation (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: { Name: "Updated Name" }
    }) {
      Record {
        Id
        Name { value }
      }
    }
  }
}
```

**Transformation Instructions (JSON):**

```
{
  "nodes": [{
    "path": "uiapi.customerAccount",
    "addFields": {
      "LastModifiedDate": "2024-01-15",
      "ModifiedBy": "user123",
      "Status": "Updated"
    }
  }]
}
```

**Result (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: { Name: "Updated Name" }
    }) {
      Record {
        Id
        Name { value }
        LastModifiedDate
        ModifiedBy
        Status
      }
    }
  }
}
```

### Update Enhancements: Modify Input – Add Fields

**Original Mutation (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: { Name: "Updated Name" }
    }) {
      Record {
        Id
        Name { value }
      }
    }
  }
}
```

**Transformation Instructions (JSON - Add Input Fields):**

```
{
  "nodes": [{
    "path": "uiapi.customerAccount",
    "modifyInput": {
      "addInputFields": {
        "Industry": "Technology",
        "Status": "Active"
      }
    }
  }]
}
```

**Result (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: {
        Name: "Updated Name",
        Industry: "Technology",
        Status: "Active"
      }
    }) {
      Record {
        Id
        Name { value }
      }
    }
  }
}
```

### Update Enhancements: Modify Input and Add Output Fields

**Transformation Instructions (JSON):**

```
{
  "nodes": [{
    "path": "uiapi.customerAccount",
    "addFields": {
      "LastModifiedDate": "2024-01-15",
      "ChangeLog": "Updated via API"
    },
    "modifyInput": {
      "addInputFields": {
        "UpdateReason": "Bulk Update",
        "UpdateSource": "API"
      },
      "inputModifications": {
        "Status": "Active"
      }
    }
  }]
}
```

**Result (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: {
        Name: "Updated Name",
        Status: "Active",
        UpdateReason: "Bulk Update",
        UpdateSource: "API"
      }
    }) {
      Record {
        Id
        Name { value }
        LastModifiedDate
        ChangeLog
      }
    }
  }
}
```
