Loading
알아보기
목차
필터 선택

          결과 없음
          결과 없음
          몇 가지 검색 팁

          키워드의 맞춤법을 확인하십시오.
          더 일반적인 검색 용어를 사용하십시오.
          필터 수를 줄여 검색 범위를 확장하십시오.

          전체 Salesforce 도움말 검색
          Apex Hooks로 절차 계획 사용자 정의

          Apex Hooks로 절차 계획 사용자 정의

          고유한 가격 책정 시나리오를 지원하려면 가격 책정 절차 계획에 사용자 정의 Apex 논리를 추가합니다. Apex 후크를 사용하여 견적 라인이 구성된 후 가격 책정 컨텍스트를 수정하는 사용자 정의 비즈니스 논리를 적용할 수 있습니다. Apex 프리후크를 사용하여 가격 책정 전에 제품 특성을 기반으로 가격을 조정하고 Apex 포스트후크를 사용하여 가격 책정 후 그룹 및 기타 견적서 개체 요소의 가격 변경을 처리합니다. 세일즈 담당자가 제품을 구성하거나 견적서 행 항목 그룹을 변경하면 가격 책정 절차 계획이 Apex 지침에 따라 가격을 변경합니다.

          필수 Edition

          제공 제품: Lightning Experience
          지원 제품: Salesforce 가격 책정이 활성화된 Revenue Cloud의 Enterprise, UnlimitedDeveloper Edition
          필요한 사용자 권한
          가격 책정 절차 및 절차 계획 만들기, 업데이트, 삭제: Salesforce 가격 책정 설계 시간 사용자 또는 절차 계획 액세스
          가격 책정 절차 사용: Salesforce 가격 책정 실행 시간 사용자
          Apex 클래스의 버전 설정 정의, 편집, 삭제, 보안 설정 및 설정 작성자 Apex
          중요
          중요
          • Revenue Cloud 프로세스 유형을 사용하는 경우 추가하는 Apex 논리가 절차 계획 실행 순서의 첫 번째 또는 마지막 요소여야 합니다.
          • Apex 호크의 외부 콜아웃은 Salesforce 사용자 인터페이스 또는 Place Sales Transaction API를 통해 세일즈 트랜잭션 수행 요청이 트리거되는 경우에만 지원됩니다. Apex 또는 Flow에서 요청이 트리거되면 지원되지 않습니다.
          • Double Persist 모드가 활성화된 경우 Apex 후크의 외부 콜아웃이 지원되지 않습니다. 추가 지침은 Salesforce 고객 지원에 문의하십시오.
          • Apex 후크의 외부 콜아웃은 성능에 영향을 미칠 수 있습니다. 예측 가능한 서비스 수준 목표(SLO)는 보장할 수 없습니다.
          1. 가격 책정을 위한 절차 계획 오케스트레이션이 설정되어 있는지 확인합니다.
            1. 설정에서 빠른 찾기 상자에 매출 설정을 입력한 다음, 매출 설정을 선택합니다.
            2. 필요한 경우 가격 책정을 위한 절차 계획 오케스트레이션 설정을 찾아서 활성화합니다.
            3. 기본 및 세일즈 트랜잭션 유형 가격 책정 절차 제외 설정을 비활성화된 상태로 둡니다.
          2. 가격 책정 절차에 추가할 Apex 후크의 클래스를 정의합니다.
            1. 설정에서 빠른 찾기 상자에 Apex를 입력한 다음, Apex 클래스를 선택합니다.
            2. 새 Apex 클래스를 만들려면 새로 만들기를 선택합니다.
            3. 클래스 편집기에서 클래스 정의를 입력합니다.

              예를 들어 이 프리후크(ApexDmAttributePreHook)는 표시 크기에 따라 이름 Display_Size을 사용하여 동적 특성의 값을 업데이트합니다. 이 단계의 끝에 있는 Apex 가격 책정 후크의 샘플 클래스에 더 많은 샘플 클래스가 표시됩니다.

              global class ApexDmlAttributePreHook implements RevSignaling.SignalingApexProcessor {
              
                  public virtual class BaseException extends Exception {}
                  public class OtherException extends BaseException {}
                  public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                      System.debug('Executing PREHOOK');
                      
                      String contextId = request.ctxInstanceId;
                      Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                      
                      // STEP 1 - Query SalesTransactionItemAttribute and extract Display_Size values
                      Map<String, Object> inputQueryItemAttr = new Map<String, Object>{
                          'contextId' => contextId,
                          'tags' => new List<String>{ 'SalesTransactionItemAttribute' }
                      };
                      Map<String, Object> itemAttrQueryOutput = industriesContext.queryTags(inputQueryItemAttr);
                      Map<String, Object> itemAttrQueryResult = (Map<String, Object>) itemAttrQueryOutput.get('queryResult');
                      List<Object> itemAttrData = (List<Object>) itemAttrQueryResult.get('SalesTransactionItemAttribute');
              
                      Map<String, Decimal> parentCtxIdToDisplaySize = new Map<String, Decimal>();
              
                      for (Object attrObj : itemAttrData) {
                          Map<String, Object> attrNode = (Map<String, Object>) attrObj;
                          Map<String, Object> tagMap = (Map<String, Object>) attrNode.get('tagValue');
              
                          String attributeName = null;
                          String attributeValueStr = null;
                          String parentCtxId = null;
              
                          if (tagMap.containsKey('Attribute')) {
                              attributeName = (String)((Map<String, Object>) tagMap.get('Attribute')).get('tagValue');
                          }
                          if (tagMap.containsKey('AttributeValue')) {
                              attributeValueStr = (String)((Map<String, Object>) tagMap.get('AttributeValue')).get('tagValue');
                          }
                          if (tagMap.containsKey('SalesTransactionItemAttrParent')) {
                              parentCtxId = (String)((Map<String, Object>) tagMap.get('SalesTransactionItemAttrParent')).get('tagValue');
                          }
              
                          if (attributeName == 'Display_Size' && attributeValueStr != null && parentCtxId != null) {
                              Decimal sizeValue = Decimal.valueOf(attributeValueStr.split(' ')[0]);
                              parentCtxIdToDisplaySize.put(parentCtxId, sizeValue);
                              System.debug('DisplaySize=' + sizeValue);
                              System.debug('Matched itemCtxId=' + parentCtxId);
                          }
                      }
              
                      // STEP 2 - Query SalesTransactionItem nodes
                      Map<String, Object> inputQueryItem = new Map<String, Object>{
                          'contextId' => contextId,
                          'tags' => new List<String>{ 'SalesTransactionItem' }
                      };
                      Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                      Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                      List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
              
                      // STEP 3 - Build update list
                      List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
              
                      for (Object itemObj : itemData) {
                          Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                          List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                          System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
              
                          Boolean matched = false;
                          for (String ctxKey : parentCtxIdToDisplaySize.keySet()) {
                              if (dataPath.contains(ctxKey)) {
                                  Decimal newPrice = parentCtxIdToDisplaySize.get(ctxKey);
                                  System.debug('DisplaySize match found for item ' + ctxKey);
                                  dataPath.remove(0); // Remove contextId
              
                                  itemNodeUpdates.add(new Map<String, Object>{
                                      'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                                      'attributes' => new List<Object>{
                                          new Map<String, Object>{
                                              'attributeName' => 'UnitPrice',
                                              'attributeValue' => newPrice
                                          }
                                      }
                                  });
                                  matched = true;
                                  break;
                              }
                          }
              
                          if (!matched) {
                              String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                              System.debug('No DisplaySize match found for item ' + itemCtxId);
                          }
                      }
              
                      // STEP 4 - Submit context update
                      if (!itemNodeUpdates.isEmpty()) {
                          Map<String, Object> updateInput = new Map<String, Object>{
                              'contextId' => contextId,
                              'nodePathAndAttributes' => itemNodeUpdates
                          };
              
                          System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                          System.debug(JSON.serializePretty(updateInput));
                          industriesContext.updateContextAttributes(updateInput);
                      }
              
                      RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                      response.status = RevSignaling.TransactionStatus.SUCCESS;
                      //response.status = RevSignaling.TransactionStatus.FAILED;
                      //response.message = 'An error occurred during the processing...';
                      return response;
                  }
                  
              }
            4. 클래스 정의를 저장합니다.
          3. 설정에서 빠른 찾기 상자에 절차 계획을 입력한 다음, 절차 계획 정의를 선택합니다.
          4. 정의 이름 열에서 편집할 절차 계획 정의를 선택합니다.
          5. 절차 계획 정의의 절차 계획 섹션에서 추가를 선택하여 새 섹션을 추가합니다.
            1. 표준을 선택합니다.
            2. 섹션의 이름을 지정합니다(예: Apex 사전 후크의 경우 Pre Apex).
            3. 섹션 유형에서 Apex저장을 차례로 선택합니다.
            4. 새 섹션을 확대합니다.
            5. 단계의 경우 가격 책정을 선택하고 해상도 유형의 경우 기본값을 선택합니다.
            6. 표시되는 Apex 선택란에 위에 정의한 Apex 클래스(예: ApexDmlAttributePreHook)를 입력한 다음, 저장을 선택합니다.
            7. 필요에 따라 추가 사전후크 및 사후후크를 추가합니다.
          6. 섹션 관리를 선택하여 가격 책정 절차 섹션 위에 있는 사전 후크와 아래에 있는 후크를 다시 정렬합니다.
          7. 절차 계획 정의에 대한 변경 사항을 저장합니다.
          예
          Apex 가격 책정 후크의 샘플 클래스

          사전 후크: 컨텍스트의 모든 행(예: 견적서 행)에서 할인율을 2%로 업데이트합니다.

          
          global class ApexDmlDiscountUpdatePreHook implements RevSignaling.SignalingApexProcessor {
          
              public virtual class BaseException extends Exception {}
              public class OtherException extends BaseException {}
              
              public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                  System.debug('Executing PREHOOK');
                  
                  String contextId = request.ctxInstanceId;
                  Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                  
                  Integer randomDiscountPercentage = 2;
          
                  // STEP 2 - Query SalesTransactionItem nodes
                  Map<String, Object> inputQueryItem = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItem' }
                  };
                  Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                  Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                  List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
          
                  System.debug('QLI itemData=' + itemData);
          
                  // STEP 3 - Build update list
                  List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
          
                  for (Object itemObj : itemData) {
                      Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                      List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                      System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
          
                      Boolean matched = false;
                      dataPath.remove(0); // Remove contextId
          
                      itemNodeUpdates.add(new Map<String, Object>{
                          'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                          'attributes' => new List<Object>{
                              new Map<String, Object>{
                                  'attributeName' => 'Discount',
                                  'attributeValue' => randomDiscountPercentage
                              }
                          }
                      });
                      matched = true;
          
                      if (!matched) {
                          String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                          System.debug('No DisplaySize match found for item ' + itemCtxId);
                      }
                  }
          
                  // STEP 4 - Submit context update
                  if (!itemNodeUpdates.isEmpty()) {
                      Map<String, Object> updateInput = new Map<String, Object>{
                          'contextId' => contextId,
                          'nodePathAndAttributes' => itemNodeUpdates
                      };
          
                      System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                      System.debug(JSON.serializePretty(updateInput));
                      industriesContext.updateContextAttributes(updateInput);
                  }
          
                  RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                  response.status = RevSignaling.TransactionStatus.SUCCESS;
                  //response.status = RevSignaling.TransactionStatus.FAILED;
                  //response.message = 'An error occurred during the processing...';
                  return response;
              }
          }

          사전 후크: 외부 자원을 호출하여 수량 값을 가져옵니다.

          
          global class ApexDmlQuantityCalloutPreHook implements RevSignaling.SignalingApexProcessor {
          
              public virtual class BaseException extends Exception {}
              public class OtherException extends BaseException {}
              
              public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                  System.debug('Executing PREHOOK');
                  
                  String contextId = request.ctxInstanceId;
                  Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                  
                  // STEP 1 - External Callout Test
                  Integer randomNumber = getRandomNumber();
                  System.debug(' Random Number from API: ' + randomNumber);
          
                  // STEP 2 - Query SalesTransactionItem nodes
                  Map<String, Object> inputQueryItem = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItem' }
                  };
                  Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                  Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                  List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
          
                  System.debug('QLI itemData=' + itemData);
          
                  // STEP 3 - Build update list
                  List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
          
                  for (Object itemObj : itemData) {
                      Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                      List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                      System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
          
                      Boolean matched = false;
                      dataPath.remove(0); // Remove contextId
              
                      itemNodeUpdates.add(new Map<String, Object>{
                          'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                          'attributes' => new List<Object>{
                              new Map<String, Object>{
                                  'attributeName' => 'Quantity',
                                  'attributeValue' => randomNumber
                              }
                          }
                      });
                      matched = true;
          
                      if (!matched) {
                          String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                          System.debug('No DisplaySize match found for item ' + itemCtxId);
                      }
                  }
          
                  // STEP 4 - Submit context update
                  if (!itemNodeUpdates.isEmpty()) {
                      Map<String, Object> updateInput = new Map<String, Object>{
                          'contextId' => contextId,
                          'nodePathAndAttributes' => itemNodeUpdates
                      };
          
                      System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                      System.debug(JSON.serializePretty(updateInput));
                      industriesContext.updateContextAttributes(updateInput);
                  }
          
                  RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                  response.status = RevSignaling.TransactionStatus.SUCCESS;
                  //response.status = RevSignaling.TransactionStatus.FAILED;
                  //response.message = 'An error occurred during the processing...';
                  return response;
              }
              
              // External callout
              private Integer getRandomNumber() {
                  String endpoint = 'https://www.random.org/integers/?num=1&min=1&max=100&col=1&base=10&format=plain&rnd=new';
                  Http http = new Http();
                  HttpRequest req = new HttpRequest();
                  req.setEndpoint(endpoint);
                  req.setMethod('GET');
                  req.setTimeout(5000);
          
                  try {
                      HttpResponse res = http.send(req);
                      if (res.getStatusCode() == 200) {
                          System.debug('Fetched prices from external service');
                          return Integer.valueOf(res.getBody().trim());
                      } else {
                          System.debug(' Callout failed: ' + res.getStatus());
                      }
                  } catch (Exception ex) {
                      System.debug(' Exception during callout: ' + ex.getMessage());
                  }
                  return 10;
              }
          }

          Prehook: 속성 이름이 디스플레이이고 속성 값이 1080p 내장형 디스플레이이거나 속성 이름이 프린터이고 속성 값이 레이저인 경우 제품 가격에 대한 업데이트를 트리거합니다.

          
          global class ApexDmlMultiAttributePreHook implements RevSignaling.SignalingApexProcessor {
          
              public virtual class BaseException extends Exception {}
              public class OtherException extends BaseException {}
              public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                  System.debug('Executing PREHOOK');
                  
                  String contextId = request.ctxInstanceId;
                  Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                  
                  // STEP 1 - Query SalesTransactionItemAttribute and extract Display_Size values
                  Map<String, Object> inputQueryItemAttr = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItemAttribute' }
                  };
                  Map<String, Object> itemAttrQueryOutput = industriesContext.queryTags(inputQueryItemAttr);
                  Map<String, Object> itemAttrQueryResult = (Map<String, Object>) itemAttrQueryOutput.get('queryResult');
                  List<Object> itemAttrData = (List<Object>) itemAttrQueryResult.get('SalesTransactionItemAttribute');
          
                  Map<String, Decimal> parentCtxIdToAttribute = new Map<String, Decimal>();
          
                  for (Object attrObj : itemAttrData) {
                      Map<String, Object> attrNode = (Map<String, Object>) attrObj;
                      Map<String, Object> tagMap = (Map<String, Object>) attrNode.get('tagValue');
          
                      String attributeName = null;
                      String attributeValueStr = null;
                      String parentCtxId = null;
          
                      System.debug('TagMap ' + tagMap);
                      if (tagMap.containsKey('Attribute')) {
                          attributeName = (String)((Map<String, Object>) tagMap.get('Attribute')).get('tagValue');
                      }
                      if (tagMap.containsKey('AttributeValue')) {
                          attributeValueStr = (String)((Map<String, Object>) tagMap.get('AttributeValue')).get('tagValue');
                      }
                      if (tagMap.containsKey('SalesTransactionItemAttrParent')) {
                          parentCtxId = (String)((Map<String, Object>) tagMap.get('SalesTransactionItemAttrParent')).get('tagValue');
                      }
                      
                      if (attributeName == 'Display' && attributeValueStr == '1080p Built-in Display' && parentCtxId != null) {
                         Decimal defaultDisplayCost = 1000.00;
                         parentCtxIdToAttribute.put(parentCtxId, defaultDisplayCost);
                         System.debug('Display=' + defaultDisplayCost);
                         System.debug('Matched itemCtxId=' + parentCtxId);
                      }
                      
                      if (attributeName == 'Printer' && attributeValueStr == 'Laser' && parentCtxId != null) {
                         Decimal defaultLaserPrinterCost = 500.00;
                         parentCtxIdToAttribute.put(parentCtxId, defaultLaserPrinterCost);
                         System.debug('Printer=' + defaultLaserPrinterCost);
                         System.debug('Matched itemCtxId=' + parentCtxId);
                      }
                  }
          
                  // STEP 2 - Query SalesTransactionItem nodes
                  Map<String, Object> inputQueryItem = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItem' }
                  };
                  Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                  Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                  List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
          
                  // STEP 3 - Build update list
                  List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
          
                  for (Object itemObj : itemData) {
                      Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                      List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                      System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
          
                      Boolean matched = false;
                      for (String ctxKey : parentCtxIdToAttribute.keySet()) {
                          if (dataPath.contains(ctxKey)) {
                              Decimal newPrice = parentCtxIdToAttribute.get(ctxKey);
                              System.debug('Attribute match found for item ' + ctxKey);
                              System.debug('Attribue with new price ' + newPrice);
                              dataPath.remove(0); // Remove contextId
          
                              itemNodeUpdates.add(new Map<String, Object>{
                                  'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                                  'attributes' => new List<Object>{
                                      new Map<String, Object>{
                                          'attributeName' => 'UnitPrice',
                                          'attributeValue' => newPrice
                                      }
                                  }
                              });
                              matched = true;
                              break;
                          }
                      }
          
                      if (!matched) {
                          String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                          System.debug('No DisplaySize match found for item ' + itemCtxId);
                      }
                  }
          
                  // STEP 4 - Submit context update
                  if (!itemNodeUpdates.isEmpty()) {
                      Map<String, Object> updateInput = new Map<String, Object>{
                          'contextId' => contextId,
                          'nodePathAndAttributes' => itemNodeUpdates
                      };
          
                      System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                      System.debug(JSON.serializePretty(updateInput));
                      industriesContext.updateContextAttributes(updateInput);
                  }
          
                  RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                  response.status = RevSignaling.TransactionStatus.SUCCESS;
                  //response.status = RevSignaling.TransactionStatus.FAILED;
                  //response.message = 'An error occurred during the processing...';
                  return response;
              }
              
          }
          
          

          Prehook: 컨텍스트의 모든 행에 임의의 할인율을 적용합니다.

          
          global class ApexDmlRandomDiscountPreHook implements RevSignaling.SignalingApexProcessor {
          
              public virtual class BaseException extends Exception {}
              public class OtherException extends BaseException {}
              
              public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                  System.debug('Executing PREHOOK');
                  
                  String contextId = request.ctxInstanceId;
                  Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                  
                  Integer randomDiscountPercentage = getRandomNumber();
          
                  // STEP 2 - Query SalesTransactionItem nodes
                  Map<String, Object> inputQueryItem = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItem' }
                  };
                  Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                  Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                  List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
          
                  System.debug('QLI itemData=' + itemData);
          
                  // STEP 3 - Build update list
                  List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
          
                  for (Object itemObj : itemData) {
                      Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                      List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                      System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
          
                      Boolean matched = false;
                      dataPath.remove(0); // Remove contextId
          
                      itemNodeUpdates.add(new Map<String, Object>{
                          'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                          'attributes' => new List<Object>{
                              new Map<String, Object>{
                                  'attributeName' => 'Discount',
                                  'attributeValue' => randomDiscountPercentage
                              }
                          }
                      });
                      matched = true;
          
                      if (!matched) {
                          String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                          System.debug('No DisplaySize match found for item ' + itemCtxId);
                      }
                  }
          
                  // STEP 4 - Submit context update
                  if (!itemNodeUpdates.isEmpty()) {
                      Map<String, Object> updateInput = new Map<String, Object>{
                          'contextId' => contextId,
                          'nodePathAndAttributes' => itemNodeUpdates
                      };
          
                      System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                      System.debug(JSON.serializePretty(updateInput));
                      industriesContext.updateContextAttributes(updateInput);
                  }
          
                  RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                  response.status = RevSignaling.TransactionStatus.SUCCESS;
                  //response.status = RevSignaling.TransactionStatus.FAILED;
                  //response.message = 'An error occurred during the processing...';
                  return response;
              }
                  // External callout
              public static Integer getRandomNumber() {
                  String endpoint = 'https://www.random.org/integers/?num=1&min=1&max=100&col=1&base=10&format=plain&rnd=new';
                  Http http = new Http();
                  HttpRequest req = new HttpRequest();
                  req.setEndpoint(endpoint);
                  req.setMethod('GET');
                  req.setTimeout(5000);
          
                  try {
                      HttpResponse res = http.send(req);
                      if (res.getStatusCode() == 200) {
                          System.debug('Fetched prices from external service');
                          return Integer.valueOf(res.getBody().trim());
                      } else {
                          System.debug(' Callout failed: ' + res.getStatus());
                      }
                  } catch (Exception ex) {
                      System.debug(' Exception during callout: ' + ex.getMessage());
                  }
                  return 10;
              }
          
          }

          사전 후크: 콜아웃에서 외부 자원으로 임의의 값으로 동적 특성 값을 업데이트합니다.

          
          global class ApexDmlAttributeExternalCalloutPreHook implements RevSignaling.SignalingApexProcessor {
          
              public virtual class BaseException extends Exception {}
              public class OtherException extends BaseException {}
              public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                  System.debug('Executing PREHOOK');
                  
                  String contextId = request.ctxInstanceId;
                  Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                  
                  // STEP 1 - Query SalesTransactionItemAttribute and extract Display_Size values
                  Map<String, Object> inputQueryItemAttr = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItemAttribute' }
                  };
                  Map<String, Object> itemAttrQueryOutput = industriesContext.queryTags(inputQueryItemAttr);
                  Map<String, Object> itemAttrQueryResult = (Map<String, Object>) itemAttrQueryOutput.get('queryResult');
                  List<Object> itemAttrData = (List<Object>) itemAttrQueryResult.get('SalesTransactionItemAttribute');
          
                  Map<String, Decimal> parentCtxIdToDisplaySize = new Map<String, Decimal>();
          
                  for (Object attrObj : itemAttrData) {
                      Map<String, Object> attrNode = (Map<String, Object>) attrObj;
                      Map<String, Object> tagMap = (Map<String, Object>) attrNode.get('tagValue');
          
                      String attributeName = null;
                      String attributeValueStr = null;
                      String parentCtxId = null;
          
                      if (tagMap.containsKey('Attribute')) {
                          attributeName = (String)((Map<String, Object>) tagMap.get('Attribute')).get('tagValue');
                      }
                      if (tagMap.containsKey('AttributeValue')) {
                          attributeValueStr = (String)((Map<String, Object>) tagMap.get('AttributeValue')).get('tagValue');
                      }
                      if (tagMap.containsKey('SalesTransactionItemAttrParent')) {
                          parentCtxId = (String)((Map<String, Object>) tagMap.get('SalesTransactionItemAttrParent')).get('tagValue');
                      }
          
                      if (attributeName == 'Display_Size' && attributeValueStr != null && parentCtxId != null) {
                          Decimal sizeValue = getRandomNumber();
                          parentCtxIdToDisplaySize.put(parentCtxId, sizeValue);
                          System.debug('DisplaySize=' + sizeValue);
                          System.debug('Matched itemCtxId=' + parentCtxId);
                      }
                  }
          
                  // STEP 2 - Query SalesTransactionItem nodes
                  Map<String, Object> inputQueryItem = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItem' }
                  };
                  Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                  Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                  List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
          
                  // STEP 3 - Build update list
                  List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
          
                  for (Object itemObj : itemData) {
                      Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                      List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                      System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
          
                      Boolean matched = false;
                      for (String ctxKey : parentCtxIdToDisplaySize.keySet()) {
                          if (dataPath.contains(ctxKey)) {
                              Decimal newPrice = parentCtxIdToDisplaySize.get(ctxKey);
                              System.debug('DisplaySize match found for item ' + ctxKey);
                              dataPath.remove(0); // Remove contextId
          
                              itemNodeUpdates.add(new Map<String, Object>{
                                  'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                                  'attributes' => new List<Object>{
                                      new Map<String, Object>{
                                          'attributeName' => 'UnitPrice',
                                          'attributeValue' => newPrice
                                      }
                                  }
                              });
                              matched = true;
                              break;
                          }
                      }
          
                      if (!matched) {
                          String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                          System.debug('No DisplaySize match found for item ' + itemCtxId);
                      }
                  }
          
                  // STEP 4 - Submit context update
                  if (!itemNodeUpdates.isEmpty()) {
                      Map<String, Object> updateInput = new Map<String, Object>{
                          'contextId' => contextId,
                          'nodePathAndAttributes' => itemNodeUpdates
                      };
          
                      System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                      System.debug(JSON.serializePretty(updateInput));
                      industriesContext.updateContextAttributes(updateInput);
                  }
          
                  RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                  response.status = RevSignaling.TransactionStatus.SUCCESS;
                  //response.status = RevSignaling.TransactionStatus.FAILED;
                  //response.message = 'An error occurred during the processing...';
                  return response;
              }
              
                  // External callout
              private Integer getRandomNumber() {
                  String endpoint = 'https://www.random.org/integers/?num=1&min=1&max=100&col=1&base=10&format=plain&rnd=new';
                  Http http = new Http();
                  HttpRequest req = new HttpRequest();
                  req.setEndpoint(endpoint);
                  req.setMethod('GET');
                  req.setTimeout(5000);
          
                  try {
                      HttpResponse res = http.send(req);
                      if (res.getStatusCode() == 200) {
                          System.debug('Fetched prices from external service');
                          return Integer.valueOf(res.getBody().trim());
                      } else {
                          System.debug(' Callout failed: ' + res.getStatus());
                      }
                  } catch (Exception ex) {
                      System.debug(' Exception during callout: ' + ex.getMessage());
                  }
                  return 10;
              }
          }

          사후후크: 컨텍스트에서 각 행에 대한 설명을 업데이트합니다.

          
          global class ApexDmlDescriptionPostHook implements RevSignaling.SignalingApexProcessor {
          
              public virtual class BaseException extends Exception {}
              public class OtherException extends BaseException {}
              
              public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                  System.debug('Executing POSTHOOK');
                  
                  String contextId = request.ctxInstanceId;
                  Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                  
                  String randomDescription = 'via Post Apex Pricing Hook';
          
                  // STEP 2 - Query SalesTransactionItem nodes
                  Map<String, Object> inputQueryItem = new Map<String, Object>{
                      'contextId' => contextId,
                      'tags' => new List<String>{ 'SalesTransactionItem' }
                  };
                  Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                  Map<String, Object> itemQueryResult = (Map<String, Object>) itemQueryOutput.get('queryResult');
                  List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
          
                  System.debug('QLI itemData=' + itemData);
          
                  // STEP 3 - Build update list
                  List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
          
                  for (Object itemObj : itemData) {
                      Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                      List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                      System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
          
                      Boolean matched = false;
                      dataPath.remove(0); // Remove contextId
          
                      itemNodeUpdates.add(new Map<String, Object>{
                          'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                          'attributes' => new List<Object>{
                              new Map<String, Object>{
                                  'attributeName' => 'SalesTrxnItemDescription',
                                  'attributeValue' => randomDescription
                              }
                          }
                      });
                      matched = true;
          
                      if (!matched) {
                          String itemCtxId = dataPath.size() > 1 ? String.valueOf(dataPath[1]) : 'UNKNOWN';
                          System.debug('No DisplaySize match found for item ' + itemCtxId);
                      }
                  }
          
                  // STEP 4 - Submit context update
                  if (!itemNodeUpdates.isEmpty()) {
                      Map<String, Object> updateInput = new Map<String, Object>{
                          'contextId' => contextId,
                          'nodePathAndAttributes' => itemNodeUpdates
                      };
          
                      System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                      System.debug(JSON.serializePretty(updateInput));
                      industriesContext.updateContextAttributes(updateInput);
                  }
          
                  RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                  response.status = RevSignaling.TransactionStatus.SUCCESS;
                  //response.status = RevSignaling.TransactionStatus.FAILED;
                  //response.message = 'An error occurred during the processing...';
                  return response;
              }
          }
           
          로드 중
          Salesforce Help | Article