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