top of page

Streamlining the Renewal Process in Salesforce CPQ with Custom Code

Updated: Dec 1, 2023


Github Link : https://github.com/AourLegacy/AourFactory/tree/RenewaleService

Introduction Salesforce CPQ (Configure, Price, Quote) offers a standard renewal process that can be initiated from a contract, creating renewal opportunities and quotes. However, this standard process might not always meet specific business needs. For instance, it may not include all quote lines in the renewal. In this blog, we'll discuss a workaround that leverages custom coding to overcome this limitation.

The Standard Renewal Process in Salesforce CPQ Typically, Salesforce CPQ's renewal process involves:

1. Initiating from a Contract: Starting the renewal process from an existing contract.

2. Creating Renewal Opportunities and Quotes: Automatically generating new opportunities and quotes for the renewal.

However, this process might not include all quote lines, particularly when businesses have specific needs.

The Custom Workaround To address this, we can use Apex, Salesforce's proprietary programming language, to create a class with an invocable function. This function can then be triggered from a Salesforce Flow, offering more control and flexibility over the renewal process.

The Invocable Function The core of our solution is an invocable function in an Apex class. Here's how it works:

1. Triggering from a Flow: The function is called from a Salesforce Flow.

2. Input Parameter: It receives a Contract ID as a parameter.

3. Cloning Process:

- Cloning the Opportunity: The function clones the opportunity linked to the given contract.

- Cloning the Quote and Quote Lines: It also clones the quote and the quote lines associated with that quote.


Special Condition for Quote Lines The unique aspect of this function is its selective approach to cloning quote lines:

- Filtering by Product Family: The function only clones quote lines where the product's family is equal to "Service".

This selective cloning ensures that only relevant products are included in the renewal, aligning with specific business requirements.

Implementing the Solution To implement this solution:

1. Develop the Apex Class: Write an Apex class with the invocable method.

2. Create the Flow: Design a Flow in Salesforce that triggers this method.

3. Test Thoroughly: Ensure thorough testing in a sandbox environment before deploying to production.



The Apex Classe :

public class RenewaleService {


// Define a class to handle the input from the Flow

public class FlowRequest {

@InvocableVariable(required=true)

public Id contractId;

}


// Invocable method

@InvocableMethod(label='Clone Opportunity and Related Records' description='Clones an opportunity, its quote, and specific quote lines from a contract ID')

public static void cloneRelatedRecords(List<FlowRequest> requests) {

for (FlowRequest request : requests) {

// Perform cloning operations

cloner(request.contractId);

}

}


private static void cloner(Id contractId) {

// Getting the contract

Contract contract = [SELECT id, SBQQ__Opportunity__c, SBQQ__Quote__c FROM Contract WHERE Id =:contractId LIMIT 1];

SBQQ.TriggerControl.disable();

try {


List<Opportunity> oppList = [SELECT id,AccountId,CloseDate, Achievement__c, Amount, AwareCPQGoal__c, AwareCPQProd__c, AwareCPQQTCPP__c, AwareDreamOutcome__c, AwareLinkCPQTasks__c, ContactRole__c, ContractId, CreatedById, CreatedDate, Description, Fiscal, FiscalQuarter, FiscalYear, IsClosed, HasOpenActivity, ForecastCategoryName, ForecastCategory, LastCloseDateChangedHistoryId, LastModifiedById, LastModifiedDate, LastReferencedDate, LastStageChangeDate, LastViewedDate, LeadSource, Length__c, Loss_Reason__c, Market__c, LostReasonComment__c, Name, NextStep, SBQQ__AmendedContract__c, RecordTypeId, RecordType__c, Pricebook2Id, Probability, Period__c, OwnerId, Objective__c, SBQQ__QuotePricebookId__c, SBQQ__Renewal__c, SBQQ__RenewedContract__c, SBQQ__Contracted__c, SBQQ__CreateContractedPrices__c, SBQQ__Ordered__c, SBQQ__OrderGroupID__c, SolutionInterest__c, SolutionShared__c, StageName, Type__c, Type FROM Opportunity WHERE Id =:contract.SBQQ__Opportunity__c LIMIT 1 ];

Opportunity orgOpp = oppList[0];


SBQQ__Quote__c orgQuote = [SELECT Id, BillingAdresse__c, BundleDiscountAmount__c, BundleDiscountPercentage__c, DiscountAmount__c, Discount1__c, DifficultytoMaintainCustomer__c, DeltaDiscount__c, CreatedDate, CreatedById, IsDeleted, NBbundle__c, NetAfterTva__c, NombreDevisiteur__c, NombreUtilisateur__c, Objectif__c, Optional__c, OwnerId, PartnerLevel__c, Pricehold__c, PricingMethods__c, PrixNegocie__c, Process__c, SBQQ__AdditionalDiscountAmount__c, SBQQ__Account__c, Promotion__c, SBQQ__AverageCustomerDiscount__c, SBQQ__AveragePartnerDiscount__c, SBQQ__Distributor__c, SBQQ__DeliveryMethod__c, SBQQ__DefaultTemplate__c, SBQQ__DaysQuoteOpen__c, SBQQ__CustomerDiscount__c, SBQQ__CustomerAmount__c, SBQQ__ContractingMethod__c, SBQQ__ConsumptionRateOverride__c, SBQQ__DistributorDiscount__c, SBQQ__EndDate__c, SBQQ__ExpirationDate__c, SBQQ__FirstSegmentTermEndDate__c, SBQQ__MarkupRate__c, SBQQ__ListAmount__c, SBQQ__LineItemsPrinted__c, SBQQ__LineItemsGrouped__c, SBQQ__LineItemCount__c, SBQQ__MasterEvergreenContract__c, SBQQ__NetAmount__c, SBQQ__Notes__c, SBQQ__Opportunity2__c, SBQQ__OrderBy__c, SBQQ__OrderByQuoteLineGroup__c, SBQQ__Ordered__c, SBQQ__Primary__c, SBQQ__PricebookId__c, SBQQ__PriceBook__c, SBQQ__PaymentTerms__c, SBQQ__PartnerDiscount__c, SBQQ__Partner__c, SBQQ__OriginalQuote__c, SBQQ__PrimaryContact__c, SBQQ__RegularAmount__c, SBQQ__RenewalTerm__c, SBQQ__StartDate__c, SBQQ__Source__c , SBQQ__SalesRep__c, SBQQ__Status__c, SBQQ__SubscriptionTerm__c, SBQQ__Type__c, SBQQ__Uncalculated__c FROM SBQQ__Quote__c WHERE id=:contract.SBQQ__Quote__c];

// Clone and link Opportunity

Opportunity clonedOpportunity = orgOpp.clone(false, true,false,false);

clonedOpportunity.Name = 'Renewal Opportunity Service';

clonedOpportunity.SBQQ__RenewedContract__c = contractId;

// Set any other fields required for the cloned opportunity

insert clonedOpportunity;

// Clone and link Quote

SBQQ__Quote__c clonedQuote = orgQuote.clone(false,true,false,false);

clonedQuote.SBQQ__Opportunity2__c = clonedOpportunity.Id;

clonedQuote.SBQQ__Primary__c = true;

insert clonedQuote;



List<SBQQ__QuoteLine__c> originalQuoteLines = [SELECT SBQQ__MaximumPrice__c, SBQQ__Bundle__c, SBQQ__Bundled__c, SBQQ__BundledQuantity__c, SBQQ__AdditionalDiscountAmount__c, SBQQ__AdditionalDiscount__c, SBQQ__AdditionalQuantity__c, SBQQ__Product__c,SBQQ__Product__r.Family, SBQQ__Quote__c, SBQQ__NetPrice__c, SBQQ__ListPrice__c, SBQQ__ListTotal__c, SBQQ__OriginalPrice__c FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c = :contract.SBQQ__Quote__c];


List<SBQQ__QuoteLine__c> clonedQuoteLines = new List<SBQQ__QuoteLine__c>();

for (SBQQ__QuoteLine__c item : originalQuoteLines) {

if(item.SBQQ__Product__r.Family == 'Service'){

SBQQ__QuoteLine__c clonedQuoteLine = item.clone(false,true,false,false);

clonedQuoteLine.SBQQ__Quote__c = clonedQuote.Id;

clonedQuoteLines.add(clonedQuoteLine);

}

}


if(clonedQuoteLines!= null && !clonedQuoteLines.isEmpty()) insert clonedQuoteLines;

}catch (System.QueryException e) {

System.debug('Error : '+e);

}finally {

SBQQ.TriggerControl.enable();

}

}

}

Conclusion

Customizing the renewal process in Salesforce CPQ can significantly enhance its efficiency and alignment with business needs. By using a custom Apex class with an invocable function, businesses can have more control over what gets renewed and how, especially in cases where the standard process falls short. --- This structure provides a clear overview, technical details, and practical steps for implementing the custom solution.

6 views0 comments

Recent Posts

See All

Commenti

Valutazione 0 stelle su 5.
Non ci sono ancora valutazioni

Aggiungi una valutazione
bottom of page