CPQ – Rollback to Here

Have you ever been testing some functionality you just built that works on renewals or amendments and you keep having to roll back to the original opp and/or quote? Does your client have a process that includes “starting over” where they manually delete renewals, amendments, and the contract so it can all be regenerated? Look no further!

This is a bit of an edge case here and it’s for a very specific usage but I needed it and so I made it.

A “Rollback to Here” button on the Opp or Quote or Contract! I think it would be most useful on the Opportunity but it was easy to make the other two buttons.

The things you’ll need to implement this are: an APEX class, three Visualforce pages, and three custom buttons.

APEX Class – RollbackHelper

public class RollbackHelper {
    public Id recordId {get; set;}
    
    // constructor to get the record id.
    public RollbackHelper(ApexPages.StandardController controller) {
        recordId = controller.getRecord().Id;
    }
    
    // page call to roll back from contract.
    public PageReference rollbackFromContractButton() {
        RollbackHelper.rollbackFromContract(new Set<Id>{recordId});
        
        return RollbackHelper.getPageReference(recordId);
    }
    
    public static void rollbackFromContract(Set<Id> contractIds) {
        // if we have contract Ids,
        if (contractIds.size() > 0) {
            // create list of ids to delete.
            List<Id> idsToDelete = new List<Id>();
            
            // create an opps map of opps that have these contracts as amended contract or renewed contract.
            Map<Id, Opportunity> oppsMap = new Map<Id, Opportunity>([select Id from Opportunity where SBQQ__AmendedContract__c in :contractIds or SBQQ__RenewedContract__c in :contractIds]);
            
            // rollback from these opps.
            RollbackHelper.rollbackFromOpportunity(oppsMap.keySet());
            
            // create a quotes map of quotes that have these opps as opportunity.
            Map<Id, SBQQ__Quote__c> quotesMap = new Map<Id, SBQQ__Quote__c>([select Id from SBQQ__Quote__c where SBQQ__Opportunity2__c in :oppsMap.keySet()]);
            
            // rollback from these quotes.
            RollbackHelper.rollbackFromQuote(quotesMap.keySet());
            
            // uncheck renewal fields on these contracts.
            List<Contract> contracts = [select Id, SBQQ__RenewalForecast__c, SBQQ__RenewalQuoted__c from Contract where Id in :contractIds];
            for (Contract c : contracts) {
                c.SBQQ__RenewalForecast__c = false;
                c.SBQQ__RenewalQuoted__c = false;
            }
            update contracts;
            
            // add list of quote ids to list of ids to delete.
            idsToDelete.addAll(quotesMap.keySet());
            
            // add list of opp ids to list of ids to delete.
            idsToDelete.addAll(oppsMap.keySet());
            
            // delete ids to delete.
            RollbackHelper.DeleteRecords(idsToDelete);
        }
    }
    
    // page call to roll back from opp.
    public PageReference rollbackFromOpportunityButton() {
        RollbackHelper.rollbackFromOpportunity(new Set<Id>{recordId});
        
        return RollbackHelper.getPageReference(recordId);
    }
    
    public static void rollbackFromOpportunity(Set<Id> oppIds) {
        // if we have opp ids,
        if (oppIds.size() > 0) {
            // create list of ids to delete.
            List<Id> idsToDelete = new List<Id>();
            
            // create map of contracts listed on this opp.
            Map<Id, Contract> contractsMap = new Map<Id, Contract>([select Id from Contract where SBQQ__Opportunity__c in :oppIds]);
            
            // rollback from contract.
            RollbackHelper.rollbackFromContract(contractsMap.keySet());
            
            // add contracts to ids to delete.
            idsToDelete.addAll(contractsMap.keySet());
            
            // delete ids to delete.
            RollbackHelper.DeleteRecords(idsToDelete);
            
            // create map of orders listed on this opp.
            Map<Id, Order> ordersMap = new Map<Id, Order>([select Id from Order where OpportunityId in :oppIds]);
            
            // add orders to ids to delete.
            idsToDelete = new List<Id>();
            idsToDelete.addAll(ordersMap.keySet());
            
            // delete ids to delete.
            RollbackHelper.DeleteRecords(idsToDelete);
            
            // uncheck contracted/ordered checkbox.
            List<Opportunity> opps = [select Id, SBQQ__Contracted__c, SBQQ__Ordered__c from Opportunity where Id in :oppIds];
            for (Opportunity o : opps) {
                o.SBQQ__Contracted__c = false;
                o.SBQQ__Ordered__c = false;
            }
            update opps;
            
            // uncheck ordered checkbox.
            List<SBQQ__Quote__c> quotes = [select Id, SBQQ__Ordered__c from SBQQ__Quote__c where SBQQ__Opportunity2__c in :oppIds];
            for (SBQQ__Quote__c q : quotes) {
                q.SBQQ__Ordered__c = false;
            }
            update quotes;
        }
    }
    
    // page call to roll back from quote.
    public PageReference rollbackFromQuoteButton() {
        RollbackHelper.rollbackFromQuote(new Set<Id>{recordId});
        
        return RollbackHelper.getPageReference(recordId);
    }
    
    public static void rollbackFromQuote(Set<Id> quoteIds) {
        // if we have quote ids,
        if (quoteIds.size() > 0) {
            // create list of ids to delete.
            List<Id> idsToDelete = new List<Id>();
            
            // create map of orders listed on these quotes.
            Map<Id, Order> ordersMap = new Map<Id, Order>([select Id from Order where SBQQ__Quote__c in :quoteIds]);
            
            // add orders to ids to delete.
            idsToDelete.addAll(ordersMap.keySet());
            
            // delete ids to delete.
            RollbackHelper.DeleteRecords(idsToDelete);
			
            // uncheck ordered checkbox.
            List<SBQQ__Quote__c> quotes = [select Id, SBQQ__Ordered__c from SBQQ__Quote__c where Id in :quoteIds];
            for (SBQQ__Quote__c q : quotes) {
                q.SBQQ__Ordered__c = false;
            }
            update quotes;
            
            // rollback from opp.
            RollbackHelper.rollbackFromOpportunity(new Map<Id, Opportunity>([select Id from Opportunity where SBQQ__PrimaryQuote__c in :quoteIds]).keySet());
        }
    }
    
    public static void rollbackFromOrder(Set<Id> orderIds) {
        // if we have order ids,
        if (orderIds.size() > 0) {
            // create list of ids to delete.
            List<Id> idsToDelete = new List<Id>();
            
            // create map of contracts listed on these orders.
            Map<Id, Contract> contractsMap = new Map<Id, Contract>([select Id from Contract where SBQQ__Order__c in :orderIds]);
            
            // rollback from contracts.
	        RollbackHelper.rollbackFromContract(contractsMap.keySet());

            // add contracts to ids to delete.
            idsToDelete.addAll(contractsMap.keySet());
            
            // delete ids to delete.
            RollbackHelper.DeleteRecords(idsToDelete);
            
            // uncheck contracted checkbox.
            List<Order> orders = [select Id, SBQQ__Contracted__c from Order where Id in :orderIds];
            for (Order o : orders) {
                o.SBQQ__Contracted__c = false;
            }
            update orders;
        }
    }
    
    public static PageReference getPageReference(Id obId) {
        PageReference pageRef = new PageReference('/'+obId);
        pageRef.setRedirect(true);
        return pageRef;
    }
    
    public static void DeleteRecords(List<Id> recordIds) {
        if (recordIds.size() > 0) {
            List<sobject> deleteRecords = new List<sobject>();
            for (string recordID : recordIds ) {
                Id rId = recordId;
                sobject sO = rId.getSobjectType().newsobject(rId);
                deleteRecords.add(sO);
            }
            
            delete deleteRecords;
        }
    }
}

APEX Test Class – RollbackHelperTester

@isTest (seealldata=false)
public class RollbackHelperTester {
    static testMethod void rollbackFromContract() {
        // set up foundation.
        Product2 p = new Product2(Name='Test', IsActive=true, SBQQ__PricingMethod__c='List', SBQQ__SubscriptionPricing__c='Fixed Price', SBQQ__SubscriptionType__c='Renewable', SBQQ__SubscriptionTerm__c=12);
        insert p;
        
        Id pbid = Test.getStandardPricebookId();
        PricebookEntry pbe = new PricebookEntry(Product2Id=p.id, Pricebook2Id=pbid, UnitPrice=1, IsActive=true);
        insert pbe;
        
        Account a = new Account(Name='Test');
        insert a;
        
        // set up first opp and quote scenario.
        Opportunity o = new Opportunity(AccountId=a.id, Name='Test', StageName='Prospecting', CloseDate=Date.today());
        insert o;
        
        SBQQ__Quote__c q = new SBQQ__Quote__c(SBQQ__Account__c=a.id, SBQQ__Opportunity2__c=o.id, SBQQ__Primary__c=true, SBQQ__StartDate__c=Date.today(), SBQQ__SubscriptionTerm__c=12);
        insert q;
        
        SBQQ__QuoteLineGroup__c qlg = new SBQQ__QuoteLineGroup__c(SBQQ__Account__c=a.id, SBQQ__Quote__c=q.id, Name='Test');
        insert qlg;
        
        SBQQ__QuoteLine__c ql = new SBQQ__QuoteLine__c(SBQQ__Quote__c=q.id, SBQQ__Group__c=qlg.id, SBQQ__Product__c=p.id, 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 ql;
        
        // set up contract and subscriptions.
        Contract c = new Contract(AccountId=a.id, SBQQ__Opportunity__c=o.id, SBQQ__Quote__c=q.id, Status='Draft', StartDate=Date.today(), ContractTerm=12, SBQQ__PreserveBundleStructureUponRenewals__c=true);
        insert c;
        
        SBQQ__Subscription__c s = new SBQQ__Subscription__c(SBQQ__Account__c=a.id, SBQQ__Contract__c=c.id, SBQQ__Product__c=p.id, SBQQ__QuoteLine__c=ql.id, SBQQ__Quantity__c=1, SBQQ__ListPrice__c=1, SBQQ__NetPrice__c=1, SBQQ__CustomerPrice__c=1, SBQQ__RenewalQuantity__c=1);
        insert s;
        
        // set up renewal opp and quote.
        Opportunity ro = new Opportunity(AccountId=a.id, Name='Test', StageName='Prospecting', CloseDate=Date.today(), SBQQ__RenewedContract__c=c.id);
        insert ro;
        
        SBQQ__Quote__c rq = new SBQQ__Quote__c(SBQQ__Account__c=a.id, SBQQ__Opportunity2__c=ro.id, SBQQ__Primary__c=true, SBQQ__StartDate__c=Date.today(), SBQQ__SubscriptionTerm__c=12, SBQQ__Type__c='Renewal');
        insert rq;
        
        SBQQ__QuoteLineGroup__c rqlg = new SBQQ__QuoteLineGroup__c(SBQQ__Account__c=a.id, SBQQ__Quote__c=rq.id, Name='Test');
        insert rqlg;
        
        SBQQ__QuoteLine__c rql = new SBQQ__QuoteLine__c(SBQQ__Quote__c=rq.id, SBQQ__Group__c=rqlg.id, SBQQ__Product__c=p.id, 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(), SBQQ__RenewedSubscription__c=s.id);
        insert rql;
        
        Test.startTest();
        
        // create renewals.
        c.SBQQ__RenewalForecast__c = true;
        c.SBQQ__RenewalQuoted__c = true;
        update c;
        
		ApexPages.StandardController std = new ApexPages.StandardController(c);
	    RollbackHelper objRollbackHelper = new RollbackHelper(std);
		
		objRollbackHelper.rollbackFromContractButton();
		objRollbackHelper.rollbackFromOpportunityButton();
		objRollbackHelper.rollbackFromQuoteButton();
 
        Test.stopTest();
    }
}

Visualforce Page – RollbackFromOpportunity

<apex:page standardController="Opportunity" extensions="RollbackHelper" action="{!rollbackFromOpportunityButton}">
    <apex:form >
        <apex:inputHidden value="{!opportunity.Id}"/>
    </apex:form>
</apex:page>

Visualforce Page – RollbackFromQuote

<apex:page standardController="SBQQ__Quote__c" extensions="RollbackHelper" action="{!rollbackFromQuoteButton}">
    <apex:form >
        <apex:inputHidden value="{!SBQQ__Quote__c.Id}"/>
    </apex:form>
</apex:page>

Visualforce Page – RollbackFromContract

<apex:page standardController="Contract" extensions="RollbackHelper" action="{!rollbackFromContractButton}">
    <apex:form >
        <apex:inputHidden value="{!contract.Id}"/>
    </apex:form>
</apex:page>

Opportunity – Rollback to Here Button and Action

Quote – Rollback to Here Button and Action

Contract – Rollback to Here Button and Action

And you’re here. At the end. You get install links!
Production | Sandbox

Also! Make sure to add these buttons to the appropriate page layouts! They don’t just appear after installing…

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.