Relies On Product Relationships in Industries CPQ

This section provides information about Relies On relationship capability within Industries Configure, Price, Quote (CPQ).

Linear Relationship (“Relies On”) is an alternative product modeling approach to avoid the complexity of hierarchical product modeling. In hierarchical product design, related products are grouped using a parent-child relationship, where child products are implicitly dependent on other products within the hierarchy. In contrast, the Linear Design approach doesn't tightly couple products within a hierarchy. Instead, products are defined simply, with linear relationships indicating dependencies (also known as "Relies On") on other products.

  • When a Relies On product relationship is configured between two products, adding the source product will not automatically add the related product.
  • Attribute propagation for Relies On or linear relationships works only with Standard Cart APIs in Industries CPQ. When a Relies On relationship is set, the system creates an OrderProductRelationship during postCartsItems.PostInvoke. As a result, attribute propagation doesn't occur during the initial CPQ API call. However, any follow-up CPQ API call with price=true —such as getCartsItems—will propagate the attribute values. Customers can trigger this by adding a pricing call at the end of postCartsItems.PostInvoke or through a separate API call.

Note

Configure Relies On Relationship APIs 

  • Use Relies On Relationship APIs from the CPQ Cart for establishing the relationship between two related products defined using the Relies On type advanced rule.

  • Relationships are then stored in the Order Product Relationship or Quote Product Relationship depending upon the type of Cart.

  • To achieve this, perform the following.

    1. From the Developer Console, click File > New > Apex Class.
    2. Create a new class with the name CPQAppHandlerHookImplementation.
    3. Copy the following code and add it to newly created CPQAppHandlerHookImplementation class.
1* @description :
2*      This Hook is written to add support for ReliesOn Relationships APIs on CPQ Cart
3*      In this class we are writing hook on postCartsItems.PostInvoke and deleteCartsItems.PreInvoke
4*      postCartsItems.PostInvoke => it covers services like, finding relationships that are eligible to be added based on XLI added to the cart
5*                                   it also links the qualified relationhips and discards the relationships which are already added
6*                                   it also does the verification if the Relationship's max cardinality is violated, only links when its not violated
7*
8*      deleteCartsItems.PreInvoke => this hook is used to delete the linked relationship if the corresponding XLI's relationship exits and is deleted
9*
10*
11*/
12  global with sharing class CPQAppHandlerHookImplementation implements vlocity_cmt.VlocityOpenInterface
13  {
14  private vlocity_cmt.VOIInvoker invoker = vlocity_cmt.VOIInvoker.getInstance();
15  private vlocity_cmt.VlocityOpenInterface appHandler = (vlocity_cmt.VlocityOpenInterface)invoker.invoke('B2BCmexAppHandler', 'debugCreate', null, null, null);
16
17      global Boolean invokeMethod(String methodName, Map<String, Object> input, Map<String, Object> output, Map<String, Object> options)
18      {
19          try
20          {
21              System.debug('CPQAppHandlerHook method ' + methodName);
22
23              if(methodName == 'postCartsItems.PostInvoke')
24              {
25                  postCartsItemsPostInvoke(input,output,options);
26              }
27              else if(methodName == 'deleteCartsItems.PreInvoke')
28              {
29                  deleteCartsItemsPreInvoke(input,output,options);
30              }
31
32              return true;
33          }
34          catch (Exception ex)
35          {
36              System.debug('--- Exception: ' + ex.getMessage());
37              System.debug('--- Stack Trace: ' + ex.getStackTraceString());
38              throw ex;
39          }
40      }
41
42      public void deleteCarts Items PreInvoke (Map<String, Object> input, Map<String, Object> output, Map<String, Object> options)
43      {
44            System.debug('deleteCarts Items PreInvoke method ');
45            Map<String, Object> inputDelRel = new Map<String, Object>();
46            Map<String, Object> outputDelRel = new Map<String, Object>();
47            Map<String, Object> optionsDelRel = new Map<String, Object>();
48
49            String cartId = (String) input.get('cartId');
50            String lineItemsToDel = (String) input.get('id');
51            String methodName = 'deleteOrderItemRelationships';
52
53            for(String lineItemToDel: lineItemsToDel.split(','))
54            {
55                inputDelRel.put('cartId', cartId);
56                inputDelRel.put('lineItemId', lineItemToDel);
57                //remote action invocation
58                //vlocity_cmt.B2BCmexAppHandler
59                appHandler = new vlocity_cmt. B2BCmexAppHandler(); appHandler.invokeMethod (methodName, inputDelRel, outputDelRel, optionsDelRel);
60                System.debug(JSON.serialize(outputDelRel));
61            }
62     }
63
64      public void postCartsItemsPostInvoke(Map<String, Object> input, Map<String, Object> output, Map<String, Object> options)
65      {
66          System.debug('postCartsItemsPostInvoke method ');
67
68          Map<String, Object> inputRel = new Map<String, Object>();
69          Map<String, Object> outputRel = new Map<String, Object>();
70          Map<String, Object> optionsRel = new Map<String, Object>();
71
72          String cartId = (String)input.get('cartId');
73          String methodName = 'getCartProductRelationship';
74
75          inputRel.put('cartId', cartId);
76          //inputRel.put('source', 'ENTERPRISE_CART');
77          //inputRel.put('rootItemId', null);
78          //inputRel.put('getOnlyRootItems', 'true/false');
79
80          //remote action invocation
81          //vlocity_cmt.B2BCmexAppHandler appHandler = new vlocity_cmt.B2BCmexAppHandler();
82          appHandler.invokeMethod(methodName, inputRel, outputRel, optionsRel);
83          System.debug(JSON.serialize(outputRel));
84
85          Map<Id, OrderItem> xlisMap = new Map<Id, OrderItem>([SELECT Id, vlocity_cmt__AssetReferenceId__c, vlocity_cmt__Product2Id__c, vlocity_cmt__Product2Id__r.Name FROM OrderItem WHERE Order.Id = :cartId]);
86          List<vlocity_cmt__ProductRelationship__c> prodRelRules = (List<vlocity_cmt__ProductRelationship__c>) outputRel.get('ProductRelationship');
87
88
89          // map to hold record of existing quantity of the relationships that are already linked
90          Map<String, Integer> existingOPRelQtyMap = new Map<String, Integer>();
91
92          Map<String,vlocity_cmt__OrderItemRelationship__c> orderProdRelsMap = new Map<String,vlocity_cmt__OrderItemRelationship__c>();
93
94          //converting List of XLI Relationships to Map, and forming Map that holds quantity of relationships that are already added
95          for(vlocity_cmt__OrderItemRelationship__c opr : (List<vlocity_cmt__OrderItemRelationship__c>)outputRel.get('OrderProductRelationship'))
96          {
97              orderProdRelsMap.put(opr.vlocity_cmt__OrderItemId__c + ';' + opr.vlocity_cmt__RelatedOrderItemId__c, opr);
98              existingOPRelQtyMap.put(opr.vlocity_cmt__OrderItemId__c + ';' + opr.vlocity_cmt__RelatedOrderItemId__r.Product2Id + ';' + opr.vlocity_cmt__ProductRelationshipIdentifier__c, (opr.vlocity_cmt__RelatedOrderItemId__r.Quantity).intValue());
99          }
100          System.debug(JSON.serialize(orderProdRelsMap));
101          System.debug(JSON.serialize(existingOPRelQtyMap));
102
103          Map<Id,List<SObject>> xliToRelProdListMap = new Map<Id,List<SObject>>();
104          Map<Id, Object> relProdIdToProdRelMap = new Map<Id, Object>();
105
106          // map to hold record of the quantity of the relationships that are to be linked
107          Map<String, Integer> toBeLinkedOPRelQtyMap = new Map<String, Integer>();
108          Map<String, String> orderItemRelProdToRuleIdMap = new Map<String, String>();
109
110          for(Id xliId : xlisMap.keySet())
111          {
112              OrderItem xli = (OrderItem) xlisMap.get(xliId);
113              for(vlocity_cmt__ProductRelationship__c prodRelRule : prodRelRules)
114              {
115                  // forming "One to Many" relationship between XLI and Related Products
116                  if(xli.vlocity_cmt__Product2Id__c == prodRelRule.vlocity_cmt__Product2Id__c)
117                  {
118                      if(xliToRelProdListMap.get(xli.Id) != null)
119                      {
120                          List<SObject> prodRelRuleList = (List<SObject>) xliToRelProdListMap.get(xli.Id);
121                          prodRelRuleList.add(prodRelRule);
122                      }
123                      else
124                      {
125                          List<SObject> prodRelRuleList = new List<SObject>();
126                          prodRelRuleList.add(prodRelRule);
127                          xliToRelProdListMap.put(xli.Id, prodRelRuleList);
128                      }
129
130                      // map to hold record of the quantity of the relationships that are to be linked => will be used to validate cardinality violation
131                      toBeLinkedOPRelQtyMap.put(xliId + ';' + prodRelRule.vlocity_cmt__RelatedProductId__c + ';' + prodRelRule.Id, 1);
132                      orderItemRelProdToRuleIdMap.put(xliId + ';' + prodRelRule.vlocity_cmt__RelatedProductId__c, prodRelRule.Id);
133                  }
134
135                  // forming relationship between RelatedProduct and Related XLI to be used for Linking XLIs
136                  if(xli.vlocity_cmt__Product2Id__c == prodRelRule.vlocity_cmt__RelatedProductId__c )
137                  {
138                      relProdIdToProdRelMap.put(prodRelRule.vlocity_cmt__RelatedProductId__c, new Map<String, Object>
139                      {
140                          prodRelRule.Id => new List<Map<String, Object>>{
141                          new Map<String, Object>{
142                              'id' => xli.Id,
143                              'assetReferenceId' => xli.vlocity_cmt__AssetReferenceId__c,
144                              'relatedProductId' => prodRelRule.vlocity_cmt__RelatedProductId__c
145                          }
146                      }});
147                  }
148              }
149          }
150
151          // return if no related lineitem to link to in the cart
152          if(relProdIdToProdRelMap.size() <= 0) return;
153
154          // forming map "maxCardinalityMap" with key and value as => "orderItemId;relatedProdId;ruleId => max_qty", to know the max cardinality the rule supports
155          // e.g 802xx000001ncoeAAA;01txx0000006i8wAAA;a3Sxx0000004C92EAE => 6
156          Map<String, Integer> maxCardinalityMap = new Map<String, Integer>();
157
158          Map<String, List<Object>> lineItemListOfLinkagesMap = new Map<String, List<Object>>();
159
160          for(Id xliId : xliToRelProdListMap.keySet())
161          {
162
163              List<vlocity_cmt__ProductRelationship__c> xliRelProdList = (List<vlocity_cmt__ProductRelationship__c>) xliToRelProdListMap.get(xliId);
164
165              for(vlocity_cmt__ProductRelationship__c xliRelProd : xliRelProdList)
166              {
167                  if(relProdIdToProdRelMap.get(xliRelProd.vlocity_cmt__RelatedProductId__c) != null)
168                  {
169                      if(lineItemListOfLinkagesMap.get(xliId) != null)
170                      {
171                          List<Object> xliLinkagesList = (List<Object>) lineItemListOfLinkagesMap.get(xliId);
172                          xliLinkagesList.add(relProdIdToProdRelMap.get(xliRelProd.vlocity_cmt__RelatedProductId__c));
173                      }
174                      else
175                      {
176                          List<Object> xliLinkagesList = new List<Object>();
177                          xliLinkagesList.add(relProdIdToProdRelMap.get(xliRelProd.vlocity_cmt__RelatedProductId__c));
178                          lineItemListOfLinkagesMap.put(xliId, xliLinkagesList);
179                      }
180                  }
181
182                  //Map<String, Object> relationshipMap = (Map<String, Object>) relationship;
183                  String orderItemId = xliId;
184                  String relatedProdId = (String) xliRelProd.vlocity_cmt__RelatedProductId__c;
185                  String ruleId = (String) xliRelProd.Id;
186                  Decimal maxQuantity = (Decimal) xliRelProd.vlocity_cmt__MaxQuantity__c;
187
188                  maxCardinalityMap.put(orderItemId + ';' + relatedProdId + ';' + ruleId, maxQuantity.intValue());
189              }
190          }
191
192          System.debug(JSON.serialize(xliToRelProdListMap));
193          System.debug(JSON.serialize(relProdIdToProdRelMap));
194          System.debug(JSON.serialize(lineItemListOfLinkagesMap));
195
196          System.debug(JSON.serialize(maxCardinalityMap));
197          System.debug(JSON.serialize(existingOPRelQtyMap));
198          System.debug(JSON.serialize(toBeLinkedOPRelQtyMap));
199          System.debug(JSON.serialize(orderItemRelProdToRuleIdMap));
200
201          // linking LineItems based on defined "ReliesOn" relationships
202          for(Id xliId : lineItemListOfLinkagesMap.keySet())
203          {
204              Map<String, Object> linkItemInput = new Map<String, Object>();
205              Map<String, Object> linkItemOutput = new Map<String, Object>();
206
207              for(Object xliLinkages : lineItemListOfLinkagesMap.get(xliId))
208              {
209                  Id linkedRelXliId = (Id) ((Map<String, Object>)((List<Object>) JSON.deserializeUntyped((String)JSON.serialize(((Object) ((Map<String, Object>) JSON.deserializeUntyped((String)JSON.serialize(xliLinkages))).values().get(0))))).get(0)).get('id');
210                  Id linkedRelProdId = (Id) ((Map<String, Object>)((List<Object>) JSON.deserializeUntyped((String)JSON.serialize(((Object) ((Map<String, Object>) JSON.deserializeUntyped((String)JSON.serialize(xliLinkages))).values().get(0))))).get(0)).get('relatedProductId');
211                  Id ruleId = orderItemRelProdToRuleIdMap.get(xliId + ';' + linkedRelProdId);
212
213                  // Add relationship only when relationship is not already linked also check if the Rule's max cardinality is violated or not,
214                  // link only when it's not violated, else throw soft warning message in the response
215                  if(!(orderProdRelsMap.get(xliId + ';' + linkedRelXliId) != null))
216                  {
217                      String key =  xliId + ';' + linkedRelProdId + ';' + ruleId;
218                      Integer maxCardinality = maxCardinalityMap.get(key) == null ? 0 : maxCardinalityMap.get(key);
219                      Integer existingCardinality = existingOPRelQtyMap.get(key) == null ? 0 : existingOPRelQtyMap.get(key);
220                      Integer toBeAddedCardinality = toBeLinkedOPRelQtyMap.get(key) == null ? 0 : toBeLinkedOPRelQtyMap.get(key);
221
222                      System.debug('maxCardinality => ' + maxCardinality + ' existingCardinality => '+ existingCardinality + 'toBeAddedCardinality => ' + toBeAddedCardinality);
223
224                      if(existingCardinality + toBeAddedCardinality > maxCardinality)
225                      {
226                          System.debug('Max cardinality limit for the rule : ' + ruleId + ' is violated for OrderItem : ' + xliId + ' and Related OrderItem' + linkedRelXliId);
227                          vlocity_cmt.JSONResult result = (vlocity_cmt.JSONResult) output.get('result');
228
229                          String message = 'Max cardinality limit for the rule : ' + ruleId + ' is violated for OrderItem : ' + xliId + ' and Related OrderItem : ' + linkedRelXliId;
230                          //vlocity_cmt.JSONMessage jsonMessage = new vlocity_cmt.JSONMessage('422', vlocity_cmt.JSONMessage.Severity.WARN, message);
231                          //result.messages.add(jsonMessage);
232                      }
233                      else
234                      {
235                          System.debug('Qualified to be added : ' + xliId + ';' + linkedRelXliId );
236                          Map<String, Object> lineItemLinkageMap = new Map<String, Object>{xliId => xliLinkages};
237
238                          // Setting remote parameters
239                          linkItemInput.put('cartId', cartId);
240                          linkItemInput.put('LineItemLinkage', lineItemLinkageMap);
241
242                          //remote action invocation
243                          appHandler.invokeMethod('linkProductsForOrder', linkItemInput, linkItemOutput, new Map<String, Object>());
244                      }
245                  }
246                  else {
247                      System.debug('Disqualified to be added, as relationship already exists : ' + xliId + ';' + linkedRelXliId );
248                  }
249              }
250          }
251      }
252
253  }
  1. Create a new interface with the name CpqAppHandlerHookInterface and with the following configuration.

CPQ Handler

The CPQAppHandlerHookImplementation class invokes the following methods.

  • postCartsItems.PostInvoke This method is invoked whenever a new offer is added to the cart. Based on the newly added product and already existing products in the cart, following variables are important.

    • orderProdRelsMap: Contains existing OrderProductRelationship that are already linked and ProductRelationship list that can be linked.
    • xliToRelProdListMap: Contains against each lineitem, which all "Related Product(s)" exist in the Cart.
    • lineItemListOfLinkagesMap: Contains the relationships that are eligible to be added if not already added.
  • deleteCartsItems.PreInvoke This method is invoked whenever the LineItem is deleted, and if that LineItem contains any added Relies On Relationship in the cart already, then respective relationship is deleted.

    • lineItemToDel: Contains the LineItem on which delete operation is invoked and for which existing relationship(s) needs to be deleted.

Remote Methods 

Following remote methods are used.

GetCartProductRelationship 

This method fetches relationships based on XLIs present in the Order/Quote. This response contains the following.

  • OrderProductRelationship: Linked Order Product Relationships
  • QuoteProductRelationship: Linked Quote Item Relationships
  • ProductRelationship: List of eligible Relies On Relationships that can be linked
1Map<String, Object> input = new Map<String, Object>();
2Map<String, Object> output = new Map<String, Object>();
3Map<String, Object> options = new Map<String, Object>();
4// Setting remote parameters
5String methodName = 'getCartProductRelationship';
6input.put('cartId','801xx000003GssBAAS');
7//input.put('getOnlyRootItems', 'true/false'); => optional
8//remote action invocation
9vlocity_cmt.B2BCmexAppHandler appHandler = new vlocity_cmt.B2BCmexAppHandler();
10// output map will contains the response
11appHandler.invokeMethod(methodName, input, output, options);

LinkProducts 

This method inks relationships between XLIs and related XLIs present in the Order/Quote. This response contains the linked relationship, based on the type of Cart (Order/Quote).

  • Method Name for Order type Cart: linkProductsForOrder
  • Method Name for Quote type Cart: linkProducts

Sample Request

1{
2    "LineItemLinkage": {
3        "802SG0000030Ctt": { // LineItem => order product with rule
4        "a4FSG00000IPkII": [    // Product Relationship Rule Id
5           {
6               "id": "802SG0000030Ctv", // LineItem => order product with rule => aplies to
7               "assetReferenceId": "d30901b4-08eb-46c2-87be-4c51338b9156"
8           }
9        ]
10     }
11  },
12  "cartId": "801SG00000Dq4w5"
13}

Sample Response

1{
2"Response": {
3"error": "OK",
4"errorCode": "INVOKE-200",
5"relationsDeleted": {},
6"relationsAdded": [
7{
8"devopsimpkg19__RelatedOrderItemId__r": {
9"Product2": {
10"RecordTypeId": "012SG000001MSuGYAW",
11"Id": "01tSG000000NCtLYAW",
12"Name": "PRODUCT-ABP-CHILD2",
13"attributes": {
14"url": "/services/data/v61.0/sobjects/Product2/01tSG000000NCtLYAW",
15"type": "Product2"
16   }
17},
18"Id": "802SG0000030CtvYAE",
19"Product2Id": "01tSG000000NCtLYAW",
20"attributes": {
21"url": "/services/data/v61.0/sobjects/OrderItem/802SG0000030CtvYAE",
22"type": "OrderItem"
23    }
24},
25"Id": "a3cSG0000007bNpYAI",
26"devopsimpkg19__RelatedAssetReferenceId__c": "d30901b4-08eb-46c2-87be-4c51338b9156",
27"devopsimpkg19__RelationshipType__c": "ReliesOn",
28"devopsimpkg19__RelatedOrderItemId__c": "802SG0000030CtvYAE",
29"devopsimpkg19__OrderItemId__c": "802SG0000030CttYAE",
30"devopsimpkg19__OrderId__c": "801SG00000Dq4w5YAB",
31"devopsimpkg19__ProductRelationshipIdentifier__c": "a4FSG00000IPkII",
32"Name": "a3cSG0000007bNp",
33"attributes": {
34"url": "/services/data/v61.0/sobjects/devopsimpkg19__OrderItemRelationship__c/a3cSG0000007bNpYAI",
35"type": "devopsimpkg19__OrderItemRelationship__c"
36      }
37   }
38  ]
39  }
40}

GetAddedRelatedLineItemQuantity 

This method fetches already linked rules quantity for a given XLI present in the Order/Quote. This response contains the following.

  • Method Name for Order type Cart: getAddedRelatedLineItemQuantityForOrder
  • Method Name for Quote type Cart: getAddedRelatedLineItemQuantity
1Map<String, Object> input = new Map<String, Object>();
2Map<String, Object> output = new Map<String, Object>();
3Map<String, Object> options = new Map<String, Object>();
4// Setting remote parameters
5String methodName = 'getAddedRelatedLineItemQuantityForOrder';
6input.put('cartId','801xx000003GssBAAS');
7input.put('orderItemId', 'xliId'); //802SG0000030CsHYAU
8//remote action invocation
9vlocity_cmt.B2BCmexAppHandler appHandler = new vlocity_cmt.B2BCmexAppHandler();
10appHandler.invokeMethod(methodName, input, output, options);

Response contains the information same as what is inserted in sObjects OrderProductRelationship / QuoteProductRelationship.

1{"ruleIdToQuantityMap":{"a3Sxx0000004EHIEA2":1.0}}

DeleteOrderItemRelationship 

This method deletes the already linked relationships when XLI is deleted from the Order/Quote. This response contains the following.

  • Method Name for Order type Cart: deleteOrderItemRelationships
  • Method Name for Quote type Cart: deleteQuoteLineItemRelationships
1Map<String, Object> input = new Map<String, Object>();
2Map<String, Object> output = new Map<String, Object>();
3Map<String, Object> options = new Map<String, Object>();
4// Setting remote parameters
5String methodName = 'deleteOrderItemRelationships';
6input.put('cartId','801xx000003GssBAAS');
7input.put('lineItemId', 'xliId'); //802SG0000030CsHYAU
8
9//remote action invocation
10vlocity_cmt.B2BCmexAppHandler appHandler = new vlocity_cmt.B2BCmexAppHandler();
11appHandler.invokeMethod(methodName, input, output, options);