Introduction
I want to increase the Price of a product renewed, how to do it?
Apex
CreatePriceRaiseQuote
// This class contains methods to process and update Salesforce CPQ data, particularly Contracts, Quotes, Opportunities.
public class CreatePriceRaiseQuote {
// Resets fields on Contract objects and updates them in Salesforce.
public static void resetContracts(List<Contract> contracts, Map<Id, Id> contractsIdAndOpportunityId) {
// Iterating over each contract to reset certain fields.
for (Contract c : contracts) {
c.SBQQ__RenewalForecast__c = false; // Resets the Renewal Forecast field.
c.SBQQ__RenewalOpportunity__c = null; // Clears the Renewal Opportunity field.
c.SBQQ__RenewalQuoted__c = false; // Resets the Renewal Quoted field.
}
update contracts; // Updates the contract records in Salesforce.
// Iterating again to set new values for the previously reset fields.
for (Contract c : contracts) {
c.SBQQ__RenewalForecast__c = true; // Sets the Renewal Forecast field to true.
c.SBQQ__RenewalOpportunity__c = contractsIdAndOpportunityId.get(c.Id); // Sets the Renewal Opportunity field with a corresponding Opportunity ID.
c.SBQQ__RenewalQuoted__c = true; // Sets the Renewal Quoted field to true.
}
update contracts; // Updates the contract records again with new values.
}
// Updates a list of Quotes based on associated Accounts and Opportunities.
public static List<SBQQ__Quote__c> updateQuotes(List<Account> accounts, List<Opportunity> opportunities) {
Set<String> accountIds = new Set<String>();
// Extracting Account IDs from the provided Accounts list.
for (Account acc : accounts) {
accountIds.add(acc.Id);
}
// Query to fetch Quotes linked to the collected Account IDs.
List<SBQQ__Quote__c> quotes = [
SELECT Id, SBQQ__BillingCity__c, SBQQ__BillingCountry__c, SBQQ__BillingName__c,
SBQQ__BillingPostalCode__c, SBQQ__BillingState__c, SBQQ__BillingStreet__c, SBQQ__Opportunity2__c, SBQQ__Primary__c, SBQQ__SalesRep__c,
SBQQ__ShippingCity__c, SBQQ__ShippingCountry__c, SBQQ__ShippingName__c, SBQQ__ShippingPostalCode__c, SBQQ__ShippingState__c, SBQQ__ShippingStreet__c,
SBQQ__SubscriptionTerm__c, SBQQ__Account__c, SBQQ__Account__r.ShippingStreet, SBQQ__Account__r.ShippingCity, SBQQ__Account__r.Name, SBQQ__Account__r.ShippingPostalCode,
SBQQ__Account__r.Website, SBQQ__Account__r.BillingCity, SBQQ__Account__r.BillingCountry,
SBQQ__Account__r.BillingPostalCode, SBQQ__Account__r.BillingState, SBQQ__Account__r.BillingStreet, SBQQ__Account__r.ShippingCountry, SBQQ__Account__r.ShippingState
FROM SBQQ__Quote__c
WHERE SBQQ__Primary__c = TRUE
AND SBQQ__Type__c = 'Renewal'
AND SBQQ__Opportunity2__r.StageName = 'Draft'
AND SBQQ__Account__c IN :accountIds
];
// Associating each Quote with the corresponding Opportunity.
for (SBQQ__Quote__c q : quotes) {
Opportunity opportunityToQuote = new Opportunity();
// Matching each Quote to a an Opportunity based on Account ID.
for (Opportunity o : opportunities) {
if (q.SBQQ__Account__c == o.AccountId) {
opportunityToQuote = o;
break;
}
}
// Update individual Quote record with specific field values.
updateQuote(q, opportunityToQuote);
}
return quotes; // Return the updated list of Quotes.
}
// Helper method to set specific fields on an SBQQ__Quote__c object.
public static void updateQuote(SBQQ__Quote__c q, Opportunity o) {
// Assigning various billing and shipping details from the Account to the Quote.
q.SBQQ__BillingCity__c = q.SBQQ__Account__r.BillingCity;
q.SBQQ__BillingCountry__c = q.SBQQ__Account__r.BillingCountry;
q.SBQQ__BillingName__c = q.SBQQ__Account__r.Name;
q.SBQQ__BillingPostalCode__c = q.SBQQ__Account__r.BillingPostalCode;
q.SBQQ__BillingState__c = q.SBQQ__Account__r.BillingState;
q.SBQQ__BillingStreet__c = q.SBQQ__Account__r.BillingStreet;
q.SBQQ__ShippingCity__c = q.SBQQ__Account__r.ShippingCity;
q.SBQQ__ShippingCountry__c = q.SBQQ__Account__r.ShippingCountry;
q.SBQQ__ShippingName__c = q.SBQQ__Account__r.Name;
q.SBQQ__ShippingPostalCode__c = q.SBQQ__Account__r.ShippingPostalCode;
q.SBQQ__ShippingState__c = q.SBQQ__Account__r.ShippingState;
q.SBQQ__ShippingStreet__c = q.SBQQ__Account__r.ShippingStreet;
q.SBQQ__SubscriptionTerm__c = 12; // Setting the Subscription Term to 12 months.
q.SBQQ__Opportunity2__c = o.Id; // Linking the Quote to the Opportunity.
}
// Updates a list of Opportunities with specific values and links them to Contracts.
public static void updateOpportunities(List<Opportunity> opportunities, List<Contract> contracts){
for(Opportunity o: opportunities){
o.SBQQ__Renewal__c = true; // Marking the Opportunity as a renewal.
// Linking the Opportunity to the corresponding Contract.
for(Contract c: contracts){
if(c.SBQQ__RenewalOpportunity__c == o.Id){
o.SBQQ__RenewedContract__c = c.Id; // Setting the Renewed Contract field on the Opportunity.
break;
}
}
}
update opportunities; // Update the Opportunities in Salesforce.
}
}
CreatePriceRaiseQuoteLinesWrapper
// A global class implementing Salesforce's batch and stateful interfaces for processing Quote Line records.
global class CreatePriceRaiseQuoteLinesWrapper implements Database.Batchable<SObject>, Database.Stateful {
// Holds the IDs of the quotes to be processed.
public Set<String> quotesIds = new Set<String>();
// Flag to determine if automatic price raise options should be applied.
public Boolean autoRaiseOptions;
// Constructor: Initializes the class with a set of Quote IDs and a boolean for auto raise options.
global CreatePriceRaiseQuoteLinesWrapper(Set<String> quotesIds, Boolean autoRaiseOptions) {
this.quotesIds = quotesIds;
this.autoRaiseOptions = autoRaiseOptions;
}
// Called at the start of the batch process; returns a query locator for Quotes to be processed.
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([SELECT Id FROM SBQQ__Quote__c WHERE Id IN :quotesIds]);
}
// Called for each batch of records; handles the business logic for updating Quote Lines.
global void execute(Database.BatchableContext bc, List<SBQQ__Quote__c> scope) {
// Query to fetch all related Quote Lines for the current batch of Quotes.
List<SBQQ__QuoteLine__c> allQuoteLinesToProcess = [
SELECT Id, Name, SBQQ__StartDate__c, SBQQ__RenewedSubscription__c,
SBQQ__RenewedSubscription__r.NewDiscountAmount__c, SBQQ__AdditionalDiscountAmount__c, SBQQ__ListPrice__c, SBQQ__PricebookEntryId__c,
SBQQ__RequiredBy__c, SBQQ__RequiredBy__r.SBQQ__RequiredBy__c, SBQQ__Quote__c,
SBQQ__RenewedSubscription__r.SBQQ__SpecialPrice__c, SBQQ__RenewedSubscription__r.SBQQ__AdditionalDiscountAmount__c,
SBQQ__Product__c
FROM SBQQ__QuoteLine__c
WHERE SBQQ__Quote__c IN :scope
];
// Initialize collections for processing.
Set<String> pricebookEntryId = new Set<String>();
Set<SBQQ__QuoteLine__c> relatedQuoteLines = new Set<SBQQ__QuoteLine__c>();
Set<Id> risePricesIds = new Set<Id>();
Set<String> relatedParentIds = new Set<String>();
List<SBQQ__QuoteLine__c> quoteLinesToUpdate = new List<SBQQ__QuoteLine__c>();
// Collect Pricebook Entry IDs from all quote lines.
for(SBQQ__QuoteLine__c ql : allQuoteLinesToProcess){
pricebookEntryId.add(ql.SBQQ__PricebookEntryId__c);
}
// Fetch Pricebook Entry details for price calculations.
Map<Id, PricebookEntry> priceBookEntries = getPriceBookEntries(pricebookEntryId);
// Main logic to process each Quote Line and apply necessary updates.
for (SBQQ__QuoteLine__c ql : allQuoteLinesToProcess) {
if (String.isNotBlank(ql.SBQQ__RenewedSubscription__c) && ql.SBQQ__RenewedSubscription__r.NewDiscountAmount__c != null) {
ql.SBQQ__AdditionalDiscountAmount__c = ql.SBQQ__RenewedSubscription__r.NewDiscountAmount__c;
if (ql.SBQQ__PricebookEntryId__c != null) {
ql.SBQQ__ListPrice__c = priceBookEntries.get(ql.SBQQ__PricebookEntryId__c).UnitPrice;
} else {
PricebookEntry pricebook = [SELECT Id, UnitPrice FROM PricebookEntry WHERE Product2Id =: ql.SBQQ__Product__c];
ql.SBQQ__ListPrice__c = pricebook.UnitPrice;
}
ql.SBQQ__StartDate__c = Date.today();
relatedQuoteLines.add(ql.SBQQ__RequiredBy__r);
risePricesIds.add(ql.Id);
quoteLinesToUpdate.add(ql);
}
}
// Process related parent Quote Lines if any exist.
if (relatedQuoteLines.size() > 0) {
for (SBQQ__QuoteLine__c ql : relatedQuoteLines) {
relatedParentIds.add(ql.Id);
}
}
// Fetch and process related parent Quote Lines.
List<SBQQ__QuoteLine__c> relatedParentQuoteLinesToUpdate = [
SELECT ID, SBQQ__ListPrice__c
FROM SBQQ__QuoteLine__c
WHERE Id =: relatedParentIds
AND ID NOT IN: risePricesIds
];
// Update start dates for related parent Quote Lines and add them to the update list.
if (relatedParentQuoteLinesToUpdate.size() > 0) {
for (SBQQ__QuoteLine__c quoteLine : relatedParentQuoteLinesToUpdate) {
quoteLine.SBQQ__StartDate__c = Date.today();
quoteLinesToUpdate.add(quoteLine);
}
}
// Fetch and process related child Quote Lines.
List<SBQQ__QuoteLine__c> relatedChildQuoteLinesToUpdate = [
SELECT ID, SBQQ__ListPrice__c, SBQQ__RenewedSubscription__r.NewDiscountAmount__c,
SBQQ__PricebookEntryId__c, SBQQ__RenewedSubscription__r.SBQQ__SpecialPrice__c,
SBQQ__AdditionalDiscountAmount__c, SBQQ__RenewedSubscription__r.SBQQ__AdditionalDiscountAmount__c
FROM SBQQ__QuoteLine__c
WHERE SBQQ__RequiredBy__c =: relatedParentIds
AND ID NOT IN: risePricesIds
];
// Additional processing for related child Quote Lines.
for(SBQQ__QuoteLine__c ql : relatedChildQuoteLinesToUpdate){
pricebookEntryId.add(ql.SBQQ__PricebookEntryId__c);
}
// Fetch price book details for related child Quote Lines.
Map<Id, PricebookEntry> priceBookForRelatedQuoteLines = getPriceBookEntries(pricebookEntryId);
// Apply price adjustments and update start dates for related child Quote Lines.
for (SBQQ__QuoteLine__c quoteLine : relatedChildQuoteLinesToUpdate) {
if (autoRaiseOptions == true) {
if (quoteLine.SBQQ__RenewedSubscription__r.NewDiscountAmount__c == null && quoteLine.SBQQ__RenewedSubscription__c != null) {
Double newPrice = priceBookForRelatedQuoteLines.get(quoteLine.SBQQ__PricebookEntryId__c).UnitPrice;
Double oldPrice = quoteLine.SBQQ__RenewedSubscription__r.SBQQ__SpecialPrice__c;
quoteLine.SBQQ__AdditionalDiscountAmount__c = quoteLine.SBQQ__RenewedSubscription__r.SBQQ__AdditionalDiscountAmount__c + (newPrice - oldPrice);
quoteLine.SBQQ__ListPrice__c = priceBookForRelatedQuoteLines.get(quoteLine.SBQQ__PricebookEntryId__c).UnitPrice;
}
}
quoteLine.SBQQ__StartDate__c = Date.today();
quoteLinesToUpdate.add(quoteLine);
}
// Update all modified Quote Lines in the database.
update quoteLinesToUpdate;
}
// Called after the batch job is completed; schedules another batch job for further processing.
global void finish(Database.BatchableContext bc) {
System.scheduleBatch(new CreatePriceRaiseUpdatePartDiscWrapper(quotesIds), 'Price Raise Update Part Disc', 5);
}
// Helper method to fetch Pricebook Entry records for given IDs.
private Map<Id, PricebookEntry> getPriceBookEntries(Set<String> pricebookEntryId){
return new Map<Id, PricebookEntry>([SELECT Id, UnitPrice FROM PricebookEntry WHERE Id IN: pricebookEntryId]);
}
}
CreatePriceRaiseQuoteWrapper
// A global class implementing Salesforce's Schedulable, Batchable, and Stateful interfaces.
// Designed for batch processing related to price raises in Quotes, linked to Accounts and related entities.
global class CreatePriceRaiseQuoteWrapper implements Schedulable, Database.Batchable<SObject>, Database.Stateful {
// A set to hold the IDs of quotes that are processed by this batch.
private Set<String> quoteIds = new Set<String>();
// A Boolean flag indicating whether automatic price raising options should be applied.
public Boolean autoRaiseOptions;
// Constructor: Initializes the class with the autoRaiseOptions parameter.
global CreatePriceRaiseQuoteWrapper(Boolean autoRaiseOptions) {
this.autoRaiseOptions = autoRaiseOptions;
}
// The start method for the batch process. Defines the scope of records (Accounts and related entities) to be processed.
global Database.QueryLocator start(Database.BatchableContext bc) {
Date today = Date.today();
return Database.getQueryLocator([
SELECT Id, Name, BillingCity, BillingCountry, BillingPostalCode, BillingState, BillingStreet, ShippingCity, ShippingCountry, ShippingPostalCode,
ShippingState, ShippingStreet, Website,
(SELECT Id, SBQQ__RenewalForecast__c, SBQQ__RenewalOpportunity__c, SBQQ__RenewalQuoted__c FROM Contracts),
(SELECT Id, AccountId, SBQQ__Renewal__c, SBQQ__RenewedContract__c FROM Opportunities WHERE StageName = 'Draft' LIMIT 1)
FROM Account
]);
}
// The execute method is called for each batch of records. It processes the Accounts and their related entities.
global void execute(Database.BatchableContext bc, List<Account> scope) {
// List to hold accounts that meet certain criteria.
List<Account> goodAccounts = new List<Account>();
// Filtering accounts with associated contracts and opportunities.
for (Account a : scope) {
if (!a.Contracts.isEmpty() && !a.Opportunities.isEmpty()) {
goodAccounts.add(a);
}
}
// Collections for holding and processing related records.
List<Contract> contracts = new List<Contract>();
List<Opportunity> opportunities = new List<Opportunity>();
Map<Id, Id> contractsIdAndOpportunityId = new Map<Id, Id>();
// Loop through filtered accounts and collect related records.
for (Account a : goodAccounts) {
for (Contract c : a.Contracts) {
contracts.add(c);
contractsIdAndOpportunityId.put(c.Id, a.Opportunities[0].Id);
}
opportunities.add(a.Opportunities[0]);
}
// Call methods to reset contracts and update quotes based on the collected data.
CreatePriceRaiseQuote.resetContracts(contracts, contractsIdAndOpportunityId);
List<SBQQ__Quote__c> quotes = CreatePriceRaiseQuote.updateQuotes(goodAccounts, opportunities);
// Prepare a list of accounts to update.
List<Account> accountsToUpdate = new List<Account>();
// Assign corresponding accounts to quotes and add to the update list.
for (SBQQ__Quote__c quote : quotes) {
Account accountToUpdate = quote.SBQQ__Account__r;
accountsToUpdate.add(accountToUpdate);
}
// Update accounts and quotes in Salesforce.
update accountsToUpdate;
update quotes;
// Add processed quote IDs to the set for further processing.
for (SBQQ__Quote__c q : quotes) {
quoteIds.add(q.Id);
}
// Call methods to update opportunities.
CreatePriceRaiseQuote.updateOpportunities(opportunities, contracts);
}
// The finish method is called after the last batch execution. It schedules another batch job for further processing.
global void finish(Database.BatchableContext bc) {
System.scheduleBatch(new CreatePriceRaiseQuoteLinesWrapper(quoteIds, autoRaiseOptions), 'Price Raise Quote Lines', 5, 20);
}
// Execute method for the Schedulable interface; used to trigger the batch job.
global void execute(SchedulableContext ctx) {
CreatePriceRaiseQuoteWrapper scheduledCreatePriceRaiseQuote = new CreatePriceRaiseQuoteWrapper(null);
Database.executeBatch(scheduledCreatePriceRaiseQuote);
}
}
CreatePriceRaiseUpdatePartDiscWrapper
// Declares a global class that implements the Salesforce Batchable interface for processing SObjects.
global class CreatePriceRaiseUpdatePartDiscWrapper implements Database.Batchable<SObject>{
// A public set to store Quote IDs.
public Set<String> quotesIds = new Set<String>();
// Constructor for the class, initializing it with a set of Quote IDs.
global CreatePriceRaiseUpdatePartDiscWrapper(Set<String> quotesIds) {
this.quotesIds = quotesIds;
}
// The start method of the Batchable interface. This is where the batch job begins.
// It returns a QueryLocator for the batch process, selecting quotes with IDs in the quotesIds set.
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([
SELECT Id, SBQQ__PartnerDiscount__c
FROM SBQQ__Quote__c
WHERE Id IN :quotesIds
]);
}
// The execute method is called for each batch of records processed.
// It processes each Quote in the provided scope.
global void execute(Database.BatchableContext bc, List<SBQQ__Quote__c> scope) {
// Debug statements to log the size and content of the current batch scope.
System.debug(scope.size());
System.debug(scope);
// Iterates through each Quote in the scope and resets its Partner Discount to 0.
for(SBQQ__Quote__c q: scope){
q.SBQQ__PartnerDiscount__c = 0;
}
// Updates the Quote records in the database with the new Partner Discount values.
update scope;
// Queries for QuoteLine records related to the first Quote in the scope.
// Note: This query seems to be unused or incomplete in the current context.
List<SBQQ__QuoteLine__c> qls = [SELECT Id, SBQQ__ListPrice__c, SBQQ__PricebookEntryId__c, SBQQ__AdditionalDiscountAmount__c FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c =: scope[0].id];
}
// The finish method is called after the batch process is completed.
global void finish(Database.BatchableContext bc) {
}
}
Result
You need sometimes to wait, so i show you how it will work
the result should be like this:
you can take the template in github: https://github.com/AourLegacy/AourFactory/tree/Increase-Price-with-renewal-version-2
Comentários