{"id":1292,"date":"2022-02-22T21:02:18","date_gmt":"2022-02-22T21:02:18","guid":{"rendered":"https:\/\/morecpq.com\/?p=1292"},"modified":"2022-02-22T21:07:18","modified_gmt":"2022-02-22T21:07:18","slug":"contracted-prices-redo","status":"publish","type":"post","link":"https:\/\/morecpq.com\/index.php\/2022\/02\/22\/contracted-prices-redo\/","title":{"rendered":"Contracted Prices &#8211; Redo!"},"content":{"rendered":"\n<p>Anyone ever have the situation where you need to regenerate a new contracted price for a product for a customer but it fails when you try to generate it the second time?  I get the error: <em>The following error occurred during contracted prices creation. Please provide the following information to your Salesforce CPQ Admin: <strong>Quotes affected: <\/strong>Q-00210 <strong>Error type: <\/strong>SBQQ.ContractService.ContractServiceException <strong>Reason for error: <\/strong>Contracted price was not created because an attempt was made to generate a Contracted Price for a product where one is already defined. <strong>Stack Trace: <\/strong>(System Code)<\/em><\/p>\n\n\n\n<p>I&#8217;m not a fan of this error.  I also think that it should automatically generate the new price and set the old price&#8217;s expiration date to today.  Guess what.  The code for just that is below!<\/p>\n\n\n\n<p><strong>APEX Class<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:12px\"><code>public class OpportunityTriggerHelper {\n    public static void runTriggers(Map&lt;Id, Opportunity> newOppsMap, Map&lt;Id, Opportunity> oldOppsMap) {\n        if (Trigger.isBefore) {\n            if (Trigger.isUpdate) {\n                OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(newOppsMap, oldOppsMap);\n            }\n        }\n    }\n    \n    public static void doSetExpirationDateForExistingContractedPrices(Map&lt;Id, Opportunity> newOppsMap, Map&lt;Id, Opportunity> oldOppsMap) {\n        \/\/ query for list of quote lines with account ids that are on these opps primary quotes and need contracted prices generated.\n        List&lt;SBQQ__QuoteLine__c> qls = &#91;select id, SBQQ__Product__c, SBQQ__Quote__r.SBQQ__Account__c, SBQQ__Quote__r.SBQQ__Opportunity2__c from SBQQ__QuoteLine__c where SBQQ__Quote__r.SBQQ__Opportunity2__r.SBQQ__Contracted__c = False and SBQQ__Quote__r.SBQQ__Opportunity2__c in :newOppsMap.keySet() and SBQQ__GenerateContractedPrice__c != Null];\n        System.debug('OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(): qls size: '+qls.size());\n        \/\/ create product ids by account Map.\n        Map&lt;id, List&lt;id>> productIdsByAccountMap = new Map&lt;Id, List&lt;Id>>();\n        \/\/ loop through these quote lines.\n        for (SBQQ__QuoteLine__c ql : qls) {\n            \/\/ if the old value of this opp's contracted is false.\n            if (newOppsMap.get(ql.SBQQ__Quote__r.SBQQ__Opportunity2__c).SBQQ__Contracted__c == true) {\n                \/\/ if this account doesn't exist in the map,\n                if (!productIdsByAccountMap.containsKey(ql.SBQQ__Quote__r.SBQQ__Account__c)) {\n                    \/\/ create it.\n                    productIdsByAccountMap.put(ql.SBQQ__Quote__r.SBQQ__Account__c, new List&lt;id>());\n                }\n                \/\/ add this quote line's product id to its account value in the map.\n                productIdsByAccountMap.get(ql.SBQQ__Quote__r.SBQQ__Account__c).add(ql.SBQQ__Product__c);\n            }\n        }\n        System.debug('OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(): productIdsByAccountMap size: '+productIdsByAccountMap.size());\n        \/\/ if we have product ids by account,\n        if (productIdsByAccountMap.size() > 0) {\n            \/\/ make criteria list.\n            string soqlCriteria = '';\n            \/\/ loop through accounts in map.\n            decimal i = 0;\n            for (Id accId : productIdsByAccountMap.keySet()) {\n                if (i > 0) soqlCriteria += ' and ';\n                soqlCriteria += ' (SBQQ__Account__c = \\''+accId+'\\' and (SBQQ__Product__c in (\\''+String.join(productIdsByAccountMap.get(accId), '\\',\\'')+'\\'))) ';\n                i++;\n            }\n            System.debug('OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(): soqlCriteria: '+soqlCriteria);\n            \/\/ query contracted prices that match primary quote's opp ids, \n            List&lt;SBQQ__ContractedPrice__c> cps = Database.query('select id from SBQQ__ContractedPrice__c where SBQQ__ExpirationDate__c = Null and ' + soqlCriteria);\n            \/\/ loop through contracted prices.\n            for (SBQQ__ContractedPrice__c cp : cps) {\n                \/\/ set this contracted price's expiration date to yesterday.\n                cp.SBQQ__ExpirationDate__c = Date.today() - 1;\n            }\n            \/\/ update contracted prices.\n            update cps;\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>APEX Trigger<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:12px\"><code>trigger OpportunityTriggers on Opportunity (before update) {\n\tOpportunityTriggerHelper.runTriggers(trigger.newMap, trigger.oldMap);\n}<\/code><\/pre>\n\n\n\n<p><strong>APEX Test Class<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:12px\"><code>@isTest (seealldata=false)\npublic class OpportunityTriggerHelperTester {\n    @testSetup static void methodName() {\n        \/\/ set up foundation.\n        Account a = new Account(Name='test');\n        insert a;\n        \n        Product2 sp = new Product2(Name='Test Subscription', IsActive=true, SBQQ__PricingMethod__c='List', SBQQ__SubscriptionPricing__c='Fixed Price', SBQQ__SubscriptionType__c='Renewable', SBQQ__SubscriptionTerm__c=12);\n        insert new List&lt;Product2>{sp};\n        \n        Id pbid = Test.getStandardPricebookId();\n        PricebookEntry spbe = new PricebookEntry(Product2Id=sp.id, Pricebook2Id=pbid, UnitPrice=1, IsActive=true);\n        insert new List&lt;PricebookEntry>{spbe};\n        \n        SBQQ__ContractedPrice__c cp = new SBQQ__ContractedPrice__c(SBQQ__Account__c=a.id, SBQQ__Product__c=sp.id, SBQQ__Price__c=5, SBQQ__EffectiveDate__c=Date.today()-365);\n        insert cp;\n        \n        \/\/ set up first opp and quote scenario.\n        Opportunity o = new Opportunity(AccountId=a.id, Name='Test', StageName='Prospecting', CloseDate=Date.today(), Pricebook2Id=pbid);\n        insert o;\n        \n        SBQQ__Quote__c q = new SBQQ__Quote__c(SBQQ__Account__c=a.id, SBQQ__Opportunity2__c=o.id, SBQQ__Type__c='Quote', SBQQ__Primary__c=true, SBQQ__Pricebook__c=pbid, SBQQ__StartDate__c=Date.today(), SBQQ__SubscriptionTerm__c=12);\n        insert q;\n        \n        SBQQ__QuoteLine__c sql = new SBQQ__QuoteLine__c(SBQQ__Quote__c=q.id, SBQQ__Product__c=sp.id, SBQQ__GenerateContractedPrice__c='Price', SBQQ__Quantity__c=1, SBQQ__SubscriptionPricing__c='Fixed Price', SBQQ__ProductSubscriptionType__c='Renewable', SBQQ__SubscriptionType__c='Renewable', SBQQ__DefaultSubscriptionTerm__c=12, SBQQ__SubscriptionTerm__c=12, SBQQ__StartDate__c=Date.today());\n        insert new List&lt;SBQQ__QuoteLine__c>{sql};\n    }\n    static testMethod void doInsertMissingAmendmentLineGroups() {\n        Opportunity o = &#91;select id from Opportunity where Name='Test'];\n        o.SBQQ__Contracted__c = true;\n        update o;\n    }\n}<\/code><\/pre>\n\n\n\n<p>You did it!  It&#8217;s the end of this article.  You know what that means.  Install links!<\/p>\n\n\n\n<p><a href=\"https:\/\/test.salesforce.com\/packaging\/installPackage.apexp?p0=04t4x000000lyuA&amp;isdtp=p1\" target=\"_blank\" rel=\"noreferrer noopener\">Sandbox<\/a> | <a href=\"https:\/\/login.salesforce.com\/packaging\/installPackage.apexp?p0=04t4x000000lyuA&amp;isdtp=p1\" target=\"_blank\" rel=\"noreferrer noopener\">Production<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Anyone ever have the situation where you need to regenerate a new contracted price for a product for a customer but it fails when you try to generate it the second time? I get the error: The following error occurred during contracted prices creation. Please provide the following information to your Salesforce CPQ Admin: Quotes &hellip;<br \/><a href=\"https:\/\/morecpq.com\/index.php\/2022\/02\/22\/contracted-prices-redo\/\" class=\"more-link pen_button pen_element_default pen_icon_arrow_double\">Continue reading <span class=\"screen-reader-text\">Contracted Prices &#8211; Redo!<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1292","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1292","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/comments?post=1292"}],"version-history":[{"count":5,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1292\/revisions"}],"predecessor-version":[{"id":1298,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1292\/revisions\/1298"}],"wp:attachment":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/media?parent=1292"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/categories?post=1292"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/tags?post=1292"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}