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