Contracted Prices – Redo!

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 affected: Q-00210 Error type: SBQQ.ContractService.ContractServiceException Reason for error: Contracted price was not created because an attempt was made to generate a Contracted Price for a product where one is already defined. Stack Trace: (System Code)

I’m not a fan of this error. I also think that it should automatically generate the new price and set the old price’s expiration date to today. Guess what. The code for just that is below!

APEX Class

public class OpportunityTriggerHelper {
    public static void runTriggers(Map<Id, Opportunity> newOppsMap, Map<Id, Opportunity> oldOppsMap) {
        if (Trigger.isBefore) {
            if (Trigger.isUpdate) {
                OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(newOppsMap, oldOppsMap);
            }
        }
    }
    
    public static void doSetExpirationDateForExistingContractedPrices(Map<Id, Opportunity> newOppsMap, Map<Id, Opportunity> oldOppsMap) {
        // query for list of quote lines with account ids that are on these opps primary quotes and need contracted prices generated.
        List<SBQQ__QuoteLine__c> qls = [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];
        System.debug('OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(): qls size: '+qls.size());
        // create product ids by account Map.
        Map<id, List<id>> productIdsByAccountMap = new Map<Id, List<Id>>();
        // loop through these quote lines.
        for (SBQQ__QuoteLine__c ql : qls) {
            // if the old value of this opp's contracted is false.
            if (newOppsMap.get(ql.SBQQ__Quote__r.SBQQ__Opportunity2__c).SBQQ__Contracted__c == true) {
                // if this account doesn't exist in the map,
                if (!productIdsByAccountMap.containsKey(ql.SBQQ__Quote__r.SBQQ__Account__c)) {
                    // create it.
                    productIdsByAccountMap.put(ql.SBQQ__Quote__r.SBQQ__Account__c, new List<id>());
                }
                // add this quote line's product id to its account value in the map.
                productIdsByAccountMap.get(ql.SBQQ__Quote__r.SBQQ__Account__c).add(ql.SBQQ__Product__c);
            }
        }
        System.debug('OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(): productIdsByAccountMap size: '+productIdsByAccountMap.size());
        // if we have product ids by account,
        if (productIdsByAccountMap.size() > 0) {
            // make criteria list.
            string soqlCriteria = '';
            // loop through accounts in map.
            decimal i = 0;
            for (Id accId : productIdsByAccountMap.keySet()) {
                if (i > 0) soqlCriteria += ' and ';
                soqlCriteria += ' (SBQQ__Account__c = \''+accId+'\' and (SBQQ__Product__c in (\''+String.join(productIdsByAccountMap.get(accId), '\',\'')+'\'))) ';
                i++;
            }
            System.debug('OpportunityTriggerHelper.doSetExpirationDateForExistingContractedPrices(): soqlCriteria: '+soqlCriteria);
            // query contracted prices that match primary quote's opp ids, 
            List<SBQQ__ContractedPrice__c> cps = Database.query('select id from SBQQ__ContractedPrice__c where SBQQ__ExpirationDate__c = Null and ' + soqlCriteria);
            // loop through contracted prices.
            for (SBQQ__ContractedPrice__c cp : cps) {
                // set this contracted price's expiration date to yesterday.
                cp.SBQQ__ExpirationDate__c = Date.today() - 1;
            }
            // update contracted prices.
            update cps;
        }
    }
}

APEX Trigger

trigger OpportunityTriggers on Opportunity (before update) {
	OpportunityTriggerHelper.runTriggers(trigger.newMap, trigger.oldMap);
}

APEX Test Class

@isTest (seealldata=false)
public class OpportunityTriggerHelperTester {
    @testSetup static void methodName() {
        // set up foundation.
        Account a = new Account(Name='test');
        insert a;
        
        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);
        insert new List<Product2>{sp};
        
        Id pbid = Test.getStandardPricebookId();
        PricebookEntry spbe = new PricebookEntry(Product2Id=sp.id, Pricebook2Id=pbid, UnitPrice=1, IsActive=true);
        insert new List<PricebookEntry>{spbe};
        
        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);
        insert cp;
        
        // set up first opp and quote scenario.
        Opportunity o = new Opportunity(AccountId=a.id, Name='Test', StageName='Prospecting', CloseDate=Date.today(), Pricebook2Id=pbid);
        insert o;
        
        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);
        insert q;
        
        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());
        insert new List<SBQQ__QuoteLine__c>{sql};
    }
    static testMethod void doInsertMissingAmendmentLineGroups() {
        Opportunity o = [select id from Opportunity where Name='Test'];
        o.SBQQ__Contracted__c = true;
        update o;
    }
}

You did it! It’s the end of this article. You know what that means. Install links!

Sandbox | Production

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.