Monday, March 4, 2024

Salesforce Apex: Template Method Pattern

The Template Method design pattern is used to define the skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure. In Salesforce Apex, particularly in the context of CPQ, this pattern can be useful for setting up a standard process for quote calculation with certain steps that vary depending on the context or product.

A common functionality in CPQ where the Template Method pattern can be applied is in the customization of quote or quote line calculations. CPQ has a standard calculation process, but businesses often need to apply additional, context-specific logic, such as industry-specific pricing adjustments, geographical tax calculations, or promotional discounts.

Let's design a Template Method pattern to implement a customized pricing algorithm that allows for different types of adjustments to be made to the quote lines.

Step 1: Define the abstract base class with a template method

public abstract class QuoteCalculatorTemplate {
    // The template method defines the skeleton of the algorithm.
    public final void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        applyStandardPricing(quoteLines);
        applyCustomAdjustments(quoteLines);
        applyTaxes(quoteLines);
        finalizeCalculations(quoteLines);
    }
    
    // These methods are common and have a standard implementation.
    private void applyStandardPricing(List<SBQQ__QuoteLine__c> quoteLines) {
        // Apply the standard CPQ pricing logic.
    }
    
    private void applyTaxes(List<SBQQ__QuoteLine__c> quoteLines) {
        // Apply standard tax logic.
    }
    
    private void finalizeCalculations(List<SBQQ__QuoteLine__c> quoteLines) {
        // Finalize the calculations, e.g., rounding off the prices.
    }
    
    // These methods are placeholders for custom logic and should be overridden.
    protected abstract void applyCustomAdjustments(List<SBQQ__QuoteLine__c> quoteLines);
}

Step 2: Create concrete subclasses to implement the varying steps

public class IndustrySpecificQuoteCalculator extends QuoteCalculatorTemplate {
    // Override the necessary methods to provide custom behavior.
    protected override void applyCustomAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            // Apply industry-specific pricing adjustments.
            // For example, apply a discount based on the product's industry segment.
        }
    }
}

public class PromotionalQuoteCalculator extends QuoteCalculatorTemplate {
    protected override void applyCustomAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            // Apply promotional discounts based on certain criteria.
            // For example, check if the product is part of a current promotion.
        }
    }
}

Step 3: Use the concrete classes in the CPQ process

public with sharing class QuoteLinePriceManager {
    public static void calculatePrices(List<SBQQ__QuoteLine__c> quoteLines) {
        // Determine the context and select the appropriate calculator.
        QuoteCalculatorTemplate calculator;
        
        if (isIndustrySpecificContext()) {
            calculator = new IndustrySpecificQuoteCalculator();
        } else if (isPromotionalPeriod()) {
            calculator = new PromotionalQuoteCalculator();
        } else {
            // Fallback to a default calculator if needed
            calculator = new DefaultQuoteCalculator();
        }
        
        // Execute the template method to perform the calculations.
        calculator.calculate(quoteLines);
        
        // Bulk update the quote lines with the calculated prices.
        update quoteLines;
    }

    private static Boolean isIndustrySpecificContext() {
        // Implement your logic to determine if the context is industry-specific.
        return false; // Simplified for example purposes.
    }

    private static Boolean isPromotionalPeriod() {
        // Implement your logic to determine if it's a promotional period.
        return true; // Simplified for example purposes.
    }
}

In this example, QuoteCalculatorTemplate provides the structure for the quote calculation process. It defines the template method calculate, which outlines the steps for calculating quotes. The steps that are common across all calculations, such as applying standard pricing and taxes, are implemented directly within this class. applyCustomAdjustments is an abstract method that subclasses must implement to provide custom pricing logic.

IndustrySpecificQuoteCalculator and PromotionalQuoteCalculator are concrete implementations that provide specific algorithms for their respective scenarios. When calculatePrices is called, it determines the context and chooses the appropriate subclass of QuoteCalculatorTemplate to use. The chosen calculator's calculate method is then called to perform the pricing calculation, and the quote lines are updated in bulk.

Share This:    Facebook Twitter

Salesforce Apex: Command Pattern

The Command design pattern encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations. It also allows for the support of undoable operations. In Salesforce Apex, you can use the Command pattern to encapsulate all the details of an operation in a single object.

A common functionality in Salesforce CPQ where the Command pattern might be useful is when performing different actions on a Quote object, such as applying discounts, recalculating prices, or generating documents. These operations can be encapsulated as command objects, making them easy to manage and extend.

Here's an example of how you might implement the Command pattern in Salesforce CPQ:

// The Command interface declares a method for executing a command.
public interface ICommand {
    void execute();
}

// Concrete Commands implement various kinds of requests.
public class ApplyDiscountCommand implements ICommand {
    private Quote quote;
    private Decimal discountRate;
    
    public ApplyDiscountCommand(Quote quote, Decimal discountRate) {
        this.quote = quote;
        this.discountRate = discountRate;
    }
    
    public void execute() {
        // Logic to apply discount to the quote
        quote.TotalPrice *= (1 - discountRate);
        update quote;
    }
}

public class RecalculatePricesCommand implements ICommand {
    private Quote quote;
    
    public RecalculatePricesCommand(Quote quote) {
        this.quote = quote;
    }
    
    public void execute() {
        // Logic to recalculate prices for the quote
        // This might involve complex logic and multiple steps
    }
}

public class GenerateDocumentCommand implements ICommand {
    private Quote quote;
    
    public GenerateDocumentCommand(Quote quote) {
        this.quote = quote;
    }
    
    public void execute() {
        // Logic to generate a document for the quote
    }
}

// The Invoker class is associated with one or several commands. It sends a request to the command.
public class CommandInvoker {
    private ICommand command;
    
    public void setCommand(ICommand command) {
        this.command = command;
    }
    
    public void executeCommand() {
        command.execute();
    }
}

// Usage
CommandInvoker invoker = new CommandInvoker();
Quote currentQuote = [SELECT Id, TotalPrice FROM Quote WHERE Id = :someQuoteId];

// Applying a discount
ICommand applyDiscount = new ApplyDiscountCommand(currentQuote, 0.1); // 10% discount
invoker.setCommand(applyDiscount);
invoker.executeCommand();

// Recalculating prices
ICommand recalculatePrices = new RecalculatePricesCommand(currentQuote);
invoker.setCommand(recalculatePrices);
invoker.executeCommand();

// Generating a document
ICommand generateDocument = new GenerateDocumentCommand(currentQuote);
invoker.setCommand(generateDocument);
invoker.executeCommand();

In this example, we have an ICommand interface with an execute method, which must be implemented by all concrete command classes. We implement three concrete commands (ApplyDiscountCommand, RecalculatePricesCommand, and GenerateDocumentCommand) that encapsulate the request details and the operations to be performed on the Quote object.

The CommandInvoker class is used to execute these commands. It allows you to set the command to be executed and then calls the execute method on the command object.

By applying the Command pattern, you can add new commands easily without changing the existing code, which adheres to the Open/Closed Principle. It also allows you to queue, log, or rollback operations if necessary.

2nd Example

Let's say we have different post-price calculation operations that need to be applied to quote lines after the main pricing logic of CPQ has been run. We can define a command interface and create multiple command classes, each encapsulating the logic for a specific operation.

Step 1: Define the Command interface

public interface IQuoteLineCommand {
    void execute(List<SBQQ__QuoteLine__c> quoteLines);
}

Step 2: Implement specific Commands

public class ApplyBulkDiscountCommand implements IQuoteLineCommand {
    private Decimal discountRate;
    
    public ApplyBulkDiscountCommand(Decimal discountRate) {
        this.discountRate = discountRate;
    }
    
    public void execute(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            line.SBQQ__NetPrice__c *= (1 - discountRate);
        }
    }
}

public class ApplyTaxCommand implements IQuoteLineCommand {
    private Decimal taxRate;
    
    public ApplyTaxCommand(Decimal taxRate) {
        this.taxRate = taxRate;
    }
    
    public void execute(List<SBQQ__QuoteLine__c> quoteLines) {
        for (SBQQ__QuoteLine__c line : quoteLines) {
            line.SBQQ__NetPrice__c *= (1 + taxRate);
        }
    }
}

Step 3: Use the Commands in your CPQ logic

public class QuoteLinePriceProcessor {
    private List<IQuoteLineCommand> commands = new List<IQuoteLineCommand>();

    public void addCommand(IQuoteLineCommand command) {
        commands.add(command);
    }

    public void processQuoteLines(List<SBQQ__QuoteLine__c> quoteLines) {
        for (IQuoteLineCommand command : commands) {
            command.execute(quoteLines);
        }
    }
}

public with sharing class QuoteLineCalculator {

    public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        QuoteLinePriceProcessor processor = new QuoteLinePriceProcessor();
        
        // Define the commands to be executed
        processor.addCommand(new ApplyBulkDiscountCommand(0.05)); // 5% bulk discount
        processor.addCommand(new ApplyTaxCommand(0.07)); // 7% tax
        
        // Process all quote lines with the defined commands
        processor.processQuoteLines(quoteLines);
        
        // Bulk update the quote lines with changes
        update quoteLines;
    }
}

The Command pattern is used here to encapsulate post-price calculation operations into objects (ApplyBulkDiscountCommand and ApplyTaxCommand). Each command has an execute method that applies a specific operation to a list of SBQQ__QuoteLine__c records.

The QuoteLinePriceProcessor class acts as an invoker that keeps a list of commands and can execute them in order. This allows for adding, removing, or reordering commands without changing the core logic in the QuoteLineCalculator class.

The QuoteLineCalculator class demonstrates how the commands can be utilized in a bulkified manner to process quote lines. It adds the necessary commands to the processor and then executes them, performing all operations in bulk to prevent hitting governor limits.

The Command pattern provides flexibility and extensibility to the pricing logic in CPQ. You can easily add new commands for additional operations, or modify existing ones without affecting other parts of the system.

This pattern is helpful for organizing complex sets of operations, and when you need an undo feature, you can extend the command interface to include an undo method, allowing you to reverse the operation if necessary.

Share This:    Facebook Twitter

Salesforce Apex: Observer Pattern

The Observer Pattern is a behavioral design pattern where an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. This pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This is particularly useful for creating a publish-subscribe mechanism to enable loose coupling between the system components.

In the context of CPQ, the Observer Pattern can be useful in scenarios where changes to certain objects (like a Quote or a Product) need to trigger updates in related objects or execute certain business logic (such as recalculating prices or applying discounts).

Here is an example illustrating the Observer Pattern in Salesforce Apex:

Step 1: Create an observer interface.

public interface IQuoteObserver {
    void update(Quote quote);
}

Step 2: Create the subject class that observers can subscribe to.

public class Quote {
    private List<IQuoteObserver> observers = new List<IQuoteObserver>();
    private Decimal totalPrice;

    // Constructor
    public Quote(Decimal totalPrice) {
        this.totalPrice = totalPrice;
    }

    // Method to subscribe observers
    public void subscribe(IQuoteObserver observer) {
        observers.add(observer);
    }

    // Method to unsubscribe observers
    public void unsubscribe(IQuoteObserver observer) {
        observers.remove(observer);
    }

    // Method to notify observers of changes
    public void notifyObservers() {
        for (IQuoteObserver observer : observers) {
            observer.update(this);
        }
    }

    // Method to change the price and notify observers
    public void setTotalPrice(Decimal newPrice) {
        this.totalPrice = newPrice;
        notifyObservers();
    }

    // Getter for totalPrice
    public Decimal getTotalPrice() {
        return totalPrice;
    }
}

Step 3: Create concrete observers that react to changes.

public class DiscountApplier implements IQuoteObserver {
    private Decimal discountRate;

    public DiscountApplier(Decimal discountRate) {
        this.discountRate = discountRate;
    }

    public void update(Quote quote) {
        Decimal newPrice = quote.getTotalPrice() * (1 - discountRate);
        System.debug('Applying discount: New Price is ' + newPrice);
        // Discount logic can be applied here, and the quote can be updated accordingly
    }
}

public class TaxCalculator implements IQuoteObserver {
    private Decimal taxRate;

    public TaxCalculator(Decimal taxRate) {
        this.taxRate = taxRate;
    }

    public void update(Quote quote) {
        Decimal taxAmount = quote.getTotalPrice() * taxRate;
        System.debug('Calculating tax: Total Tax is ' + taxAmount);
        // Tax calculation logic can be applied here, and the quote can be updated accordingly
    }
}

Step 4: Use the Quote class and attach observers.

Quote quote = new Quote(1000.00);

// Create observers
DiscountApplier discountApplier = new DiscountApplier(0.1); // 10% discount
TaxCalculator taxCalculator = new TaxCalculator(0.15); // 15% tax

// Subscribe observers to the quote
quote.subscribe(discountApplier);
quote.subscribe(taxCalculator);

// Change the quote's price and notify observers
quote.setTotalPrice(1200.00); // This will automatically notify the observers

In the example above, we have a Quote class, which is the subject that maintains a list of observers. The DiscountApplier and TaxCalculator are observers that implement the IQuoteObserver interface. When the setTotalPrice method on the Quote is called, it triggers the notifyObservers method, which in turn calls the update method on each observer, allowing them to react to the price change.

The Observer Pattern is widely used in Salesforce CPQ for keeping various parts of the application in sync. It helps in situations where you have multiple pieces of logic that need to respond to changes in the data model, facilitating a clean separation of concerns.

2nd Example

In Salesforce CPQ, a common use case that can benefit from the Observer pattern is when multiple aspects of a Quote need to be recalculated or updated based on changes to Quote Line Items. For example, you might need to update the Quote's total price, apply discounts, and recalculate taxes when a Quote Line Item is modified.

Let's create an example with multiple observers that handle different updates: one for recalculating the total price, another for applying discounts, and a third one for recalculating taxes.

Firstly, define the Observer interface:

public interface QuoteObserver {
    void calculateUpdates(List<Id> quoteIds);
}

Create a Subject class that Quote Line Items can notify:

public class QuoteLineItemSubject {
    private static List<QuoteObserver> observers = new List<QuoteObserver>();

    public static void attach(QuoteObserver observer) {
        observers.add(observer);
    }

    public static void notifyObservers(List<Id> quoteIds) {
        for (QuoteObserver observer : observers) {
            observer.calculateUpdates(quoteIds);
        }
    }
}

Now, implement different Observers for each aspect of the Quote that needs to be updated:

public class QuoteTotalPriceCalculator implements QuoteObserver {
    public void calculateUpdates(List<Id> quoteIds) {
        // Calculate total price for each Quote and bulkify the update
        // Logic to calculate and update total price
    }
}

public class QuoteDiscountHandler implements QuoteObserver {
    public void calculateUpdates(List<Id> quoteIds) {
        // Apply discounts to each Quote and bulkify the update
        // Logic to calculate and update discounts
    }
}

public class QuoteTaxCalculator implements QuoteObserver {
    public void calculateUpdates(List<Id> quoteIds) {
        // Recalculate taxes for each Quote and bulkify the update
        // Logic to calculate and update taxes
    }
}

In a trigger on the Quote Line Item object, attach the observers and trigger the notifications:

trigger QuoteLineItemTrigger on QuoteLineItem (after insert, after update, after delete, after undelete) {
    // Collect the affected Quote IDs
    Set<Id> quoteIds = new Set<Id>();
    for (QuoteLineItem item : Trigger.isDelete ? Trigger.old : Trigger.new) {
        quoteIds.add(item.QuoteId);
    }

    // Attach the observers
    QuoteLineItemSubject.attach(new QuoteTotalPriceCalculator());
    QuoteLineItemSubject.attach(new QuoteDiscountHandler());
    QuoteLineItemSubject.attach(new QuoteTaxCalculator());
    
    // Notify observers with the updated quote IDs
    QuoteLineItemSubject.notifyObservers(new List<Id>(quoteIds));
}

Each observer's calculateUpdates method should handle bulk operations and should be implemented to query the necessary Quote and Quote Line Item records, perform the calculations, and update the Quotes in a bulkified manner.

Here is an example for the QuoteTotalPriceCalculator:

public class QuoteTotalPriceCalculator implements QuoteObserver {
    public void update(List<Id> quoteIds) {
        // Aggregate the total prices from Quote Line Items
        Map<Id, Decimal> quoteTotals = new Map<Id, Decimal>();
        for (AggregateResult ar : [
            SELECT QuoteId, SUM(TotalPrice) total
            FROM QuoteLineItem
            WHERE QuoteId IN :quoteIds
            GROUP BY QuoteId
        ]) {
            Id quoteId = (Id)ar.get('QuoteId');
            Decimal total = (Decimal)ar.get('total');
            quoteTotals.put(quoteId, total);
        }

        // Update the Quotes with the new totals
        List<Quote> quotesToUpdate = new List<Quote>();
        for (Id quoteId : quoteTotals.keySet()) {
            Quote quote = new Quote(Id = quoteId, TotalPrice = quoteTotals.get(quoteId));
            quotesToUpdate.add(quote);
        }

        // Perform a bulk update
        update quotesToUpdate;
    }
}

This approach ensures that whenever a Quote Line Item triggers an update, all related aspects of the Quote are automatically and efficiently updated through their respective observers. The pattern allows for easy extension, as you can add more observers to handle additional functionalities without modifying existing code. This makes the system more maintainable and scalable.

Aggregating the changes from all observers in a centralized manner

To ensure DML operations are only performed in one place, you can aggregate the changes from all observers in a centralized manner and perform the update once all observers have contributed their modifications. This can be accomplished by collecting the results of each observer's calculations and then applying those changes in bulk after all observers have processed.

Here's how you can modify the code:

First, modify the QuoteObserver interface to return a Map<Id, Quote> which contains the Quote Id and the modified Quote record.

public interface QuoteObserver {
    Map<Id, Quote> calculateUpdates(List<Id> quoteIds);
}

Each observer will now implement the calculateUpdates method to return the map of changes without performing any DML operations:

public class QuoteTotalPriceCalculator implements QuoteObserver {
    public Map<Id, Quote> calculateUpdates(List<Id> quoteIds) {
        // Logic to calculate total prices
        // Return a map of Quote records with updated total prices
    }
}

// Implement similar logic for QuoteDiscountHandler and QuoteTaxCalculator

In the Subject class, modify the notifyObservers method to collect all changes:

public class QuoteLineItemSubject {
    private static List<QuoteObserver> observers = new List<QuoteObserver>();

    // ...

    public static Map<Id, Quote> notifyObservers(List<Id> quoteIds) {
        Map<Id, Quote> quotesToUpdate = new Map<Id, Quote>();

        for (QuoteObserver observer : observers) {
            Map<Id, Quote> observerUpdates = observer.calculateUpdates(quoteIds);
            for (Id quoteId : observerUpdates.keySet()) {
                Quote updatedQuote = observerUpdates.get(quoteId);
                if (quotesToUpdate.containsKey(quoteId)) {
                    // Merge changes with existing updates, ensuring no overwrites
                    // This could include summing totals, recalculating taxes, etc.
                    // You'll define the merge logic based on your business requirements
                } else {
                    quotesToUpdate.put(quoteId, updatedQuote);
                }
            }
        }

        return quotesToUpdate;
    }
}

Now, the trigger will call notifyObservers and then perform the DML operation with the collected changes:

trigger QuoteLineItemTrigger on QuoteLineItem (after insert, after update, after delete, after undelete) {
    // Collect the affected Quote IDs and attach observers
    Set<Id> quoteIds = new Set<Id>();
    for (QuoteLineItem item : Trigger.isDelete ? Trigger.old : Trigger.new) {
        quoteIds.add(item.QuoteId);
    }

    QuoteLineItemSubject.attach(new QuoteTotalPriceCalculator());
    QuoteLineItemSubject.attach(new QuoteDiscountHandler());
    QuoteLineItemSubject.attach(new QuoteTaxCalculator());

    // Notify observers and collect updates
    Map<Id, Quote> quotesToUpdate = QuoteLineItemSubject.notifyObservers(new List<Id>(quoteIds));

    // Perform a bulk update with the collected changes
    if (!quotesToUpdate.isEmpty()) {
        update quotesToUpdate.values();
    }
}

With this approach, each observer calculates the necessary updates and returns them. The Subject class aggregates these updates, ensuring that there are no conflicts or overwrites. Finally, the trigger performs a single DML operation with all the aggregated changes. This pattern keeps DML operations consolidated in one place, making the code cleaner, easier to maintain, and more efficient.

Share This:    Facebook Twitter

Salesforce Apex: Composite Pattern

The Composite Pattern is a structural pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.

In the context of CPQ, the Composite Pattern can be used to model product bundles, where a bundle can contain individual products or other bundles. This pattern is useful when creating a product hierarchy that includes complex combinations of products and services.

Here is an example illustrating the Composite Pattern in Salesforce Apex:

Step 1: Define a common component interface.

public interface IProductComponent {
    Decimal getPrice();
    String getDescription();
}

Step 2: Create leaf objects that represent individual products.

public class Product implements IProductComponent {
    private Decimal price;
    private String description;

    public Product(Decimal price, String description) {
        this.price = price;
        this.description = description;
    }

    public Decimal getPrice() {
        return this.price;
    }

    public String getDescription() {
        return this.description;
    }
}

Step 3: Create a composite object that can contain other products or product bundles.

public class ProductBundle implements IProductComponent {
    private List<IProductComponent> children = new List<IProductComponent>();
    private String description;

    public ProductBundle(String description) {
        this.description = description;
    }

    public void add(IProductComponent component) {
        children.add(component);
    }

    public void remove(IProductComponent component) {
        children.remove(component);
    }

    public List<IProductComponent> getChildren() {
        return children;
    }

    public Decimal getPrice() {
        Decimal total = 0;
        for (IProductComponent child : children) {
            total += child.getPrice();
        }
        return total;
    }

    public String getDescription() {
        return this.description;
    }
}

Step 4: Use the composite in a CPQ scenario.

ProductBundle mainBundle = new ProductBundle('Main Product Bundle');

// Individual products
Product product1 = new Product(100.00, 'Product 1');
Product product2 = new Product(200.00, 'Product 2');
mainBundle.add(product1);
mainBundle.add(product2);

// Sub-bundle
ProductBundle subBundle = new ProductBundle('Sub Product Bundle');
Product subProduct1 = new Product(50.00, 'Sub Product 1');
Product subProduct2 = new Product(75.00, 'Sub Product 2');
subBundle.add(subProduct1);
subBundle.add(subProduct2);

// Add sub-bundle to the main bundle
mainBundle.add(subBundle);

// Calculating the total price of the main bundle
System.debug(mainBundle.getDescription() + ' Total Price: ' + mainBundle.getPrice());

In the example above, Product represents a leaf node, and ProductBundle represents a composite node that can have child nodes. Both implement the IProductComponent interface, allowing clients to treat individual products and bundles of products uniformly. When calculating the total price of a bundle, the composite iterates over its children and sums the prices, whether they are individual products or nested bundles.

Share This:    Facebook Twitter

Salesforce Apex: Decorator Pattern

The Decorator design pattern is a structural design pattern used to dynamically add behavior to an object without altering its structure. In Salesforce Apex, this can be particularly useful when you want to enhance the functionality of an object without changing its existing code, which is a common scenario when dealing with managed packages like Salesforce CPQ (formerly Steelbrick CPQ), where you cannot modify the source code.

A common functionality in Salesforce CPQ is the pricing calculation of QuoteLineItems, where you might need to apply different pricing strategies based on certain criteria. The Decorator pattern allows you to dynamically adjust the pricing calculations without modifying the original QuoteLineItem code.

Here's a simplified example demonstrating how we could use the Decorator pattern in Salesforce Apex to apply different pricing strategies to CPQ QuoteLineItems:

First, let's create an interface that will define the common functionality for our Quote line items:

public interface IQuoteLineItem {
    void calculatePrice();
    Decimal getPrice();
}

Next, we have a basic implementation of our Quote line item:

public class BasicQuoteLineItem implements IQuoteLineItem {
    private Decimal basePrice;
    
    public BasicQuoteLineItem(Decimal price) {
        this.basePrice = price;
    }
    
    public void calculatePrice() {
        // Basic price calculation logic
    }
    
    public Decimal getPrice() {
        return basePrice;
    }
}

Now, we can create our decorators to extend the functionality. Each decorator implements the same interface and takes in an instance of IQuoteLineItem.

public abstract class QuoteLineItemDecorator implements IQuoteLineItem {
    protected IQuoteLineItem decoratedItem;
    
    public QuoteLineItemDecorator(IQuoteLineItem item) {
        this.decoratedItem = item;
    }
    
    public void calculatePrice() {
        decoratedItem.calculatePrice();
    }
    
    public Decimal getPrice() {
        return decoratedItem.getPrice();
    }
}

public class DiscountDecorator extends QuoteLineItemDecorator {
    private Decimal discountRate;
    
    public DiscountDecorator(IQuoteLineItem item, Decimal discountRate) {
        super(item);
        this.discountRate = discountRate;
    }
    
    public void calculatePrice() {
        super.calculatePrice();
        applyDiscount();
    }
    
    private void applyDiscount() {
        Decimal price = decoratedItem.getPrice();
        decoratedItem.getPrice() * (1 - discountRate);
    }
}

public class TaxDecorator extends QuoteLineItemDecorator {
    private Decimal taxRate;
    
    public TaxDecorator(IQuoteLineItem item, Decimal taxRate) {
        super(item);
        this.taxRate = taxRate;
    }
    
    public void calculatePrice() {
        super.calculatePrice();
        addTax();
    }
    
    private void addTax() {
        Decimal price = decoratedItem.getPrice();
        decoratedItem.getPrice() * (1 + taxRate);
    }
}

Finally, we can use these decorators to dynamically add behavior to our QuoteLineItem objects:

List<IQuoteLineItem> quoteLineItems = new List<IQuoteLineItem>();

for (QuoteLineItem qli : [SELECT Id, UnitPrice FROM QuoteLineItem WHERE QuoteId = :someQuoteId]) {
    IQuoteLineItem item = new BasicQuoteLineItem(qli.UnitPrice);
    
    // Apply discounts only if the item qualifies
    if (meetsDiscountCriteria(qli)) {
        item = new DiscountDecorator(item, 0.10); // 10% discount
    }
    
    // Apply tax to all items
    item = new TaxDecorator(item, 0.08); // 8% tax
    
    item.calculatePrice(); // Calculate final price
    quoteLineItems.add(item);
}

In the example above, we query all the QuoteLineItem records outside of the loop and then process them in bulk. However, you would need to add additional logic to handle updates to these records back to the database, ensuring that you collect all the updated items and perform a single DML update outside of the loop.

2nd Example

One common requirement might be to apply additional discounts or fees based on complex business rules that are not covered by standard CPQ functionality. For instance, you might need to apply volume discounts, promotional discounts, or special fees that depend on the combination of products and customer attributes.

Here's an example of how you could use the Decorator pattern to add custom discounting logic to CPQ quote lines in a bulkified way:

Step 1: Define the interface

public interface IQuoteLineCalculator {
    void calculate(List<SBQQ__QuoteLine__c> quoteLines);
}

Step 2: Implement the base calculator

public class BaseQuoteLineCalculator implements IQuoteLineCalculator {
    public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // Here you would apply the base CPQ pricing logic
        // For simplicity, let's assume this is a no-op, since CPQ calculates base prices
    }
}

Step 3: Implement the decorators

public abstract class QuoteLineCalculatorDecorator implements IQuoteLineCalculator {
    protected IQuoteLineCalculator innerCalculator;
    
    public QuoteLineCalculatorDecorator(IQuoteLineCalculator inner) {
        this.innerCalculator = inner;
    }
    
    public void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // Delegate to the wrapped calculator
        innerCalculator.calculate(quoteLines);
    }
}

public class VolumeDiscountDecorator extends QuoteLineCalculatorDecorator {
    public VolumeDiscountDecorator(IQuoteLineCalculator inner) {
        super(inner);
    }
    
    public override void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // First, let the inner calculator do its work
        super.calculate(quoteLines);
        
        // Then apply volume discounts
        for (SBQQ__QuoteLine__c line : quoteLines) {
            Integer quantity = line.SBQQ__Quantity__c;
            // Example volume discount logic
            if (quantity > 100) {
                line.SBQQ__AdditionalDiscount__c = 0.1; // 10% discount
            }
        }
    }
}

public class PromotionalDiscountDecorator extends QuoteLineCalculatorDecorator {
    public PromotionalDiscountDecorator(IQuoteLineCalculator inner) {
        super(inner);
    }
    
    public override void calculate(List<SBQQ__QuoteLine__c> quoteLines) {
        // Apply the inner calculator's logic
        super.calculate(quoteLines);
        
        // Then apply promotional discounts
        for (SBQQ__QuoteLine__c line : quoteLines) {
            // Example promotional discount logic
            if (isPromotionalProduct(line.SBQQ__Product__c)) {
                line.SBQQ__AdditionalDiscount__c += 0.05; // Additional 5% discount
            }
        }
    }
    
    private Boolean isPromotionalProduct(Id productId) {
        // Implement logic to determine if the product is on promotion
        return true; // Simplified for example purposes
    }
}

Step 4: Use the decorators in your CPQ plugin or custom logic

public with sharing class CustomQuoteCalculator {

    public void calculateQuoteLineAdjustments(List<SBQQ__QuoteLine__c> quoteLines) {
        IQuoteLineCalculator calculator = new BaseQuoteLineCalculator();
        
        // Wrap the calculator with decorators as needed
        calculator = new VolumeDiscountDecorator(calculator);
        calculator = new PromotionalDiscountDecorator(calculator);
        
        // Perform calculations
        calculator.calculate(quoteLines);
        
        // Update the quote lines in bulk
        update quoteLines;
    }

}

This example demonstrates how the Decorator pattern can be applied in Salesforce CPQ to compose different pricing behaviors dynamically. Each decorator adds its layer of logic on top of the existing calculations. The BaseQuoteLineCalculator represents the standard CPQ pricing logic, and each decorator adds additional adjustments like volume discounts or promotional discounts.

The CustomQuoteCalculator class demonstrates how these decorators can be used in a CPQ plugin or custom logic, ensuring that all calculations are done in bulk to avoid governor limits. The list of SBQQ__QuoteLine__c records is passed through each decorator, with each decorator applying its specific adjustments.

This pattern offers a clean and maintainable structure for extending CPQ logic, enabling you to add or remove decorators as business requirements evolve without modifying the underlying classes.

Share This:    Facebook Twitter

Total Pageviews

My Social Profiles

View Sonal's profile on LinkedIn

Tags

__proto__ $Browser Access Grants Accessor properties Admin Ajax AllowsCallouts Apex Apex Map Apex Sharing AssignmentRuleHeader AsyncApexJob Asynchronous Auth Provider AWS Callbacks Connected app constructor Cookie CPU Time CSP Trusted Sites CSS Custom settings CustomLabels Data properties Database.Batchable Database.BatchableContext Database.query Describe Result Destructuring Dynamic Apex Dynamic SOQL Einstein Analytics enqueueJob Enterprise Territory Management Enumeration escapeSingleQuotes featured Flows geolocation getGlobalDescribe getOrgDefaults() getPicklistValues getRecordTypeId() getRecordTypeInfosByName() getURLParameters Google Maps Governor Limits hasOwnProperty() Heap Heap Size IIFE Immediately Invoked Function Expression Interview questions isCustom() Javascript Javascript Array jsForce Lightning Lightning Components Lightning Events lightning-record-edit-form lightning:combobox lightning:icon lightning:input lightning:select LockerService Lookup LWC Manual Sharing Map Modal Module Pattern Named Credentials NodeJS OAuth Object.freeze() Object.keys() Object.preventExtensions() Object.seal() Organization Wide Defaults Override PDF Reader Performance performance.now() Permission Sets Picklist Platform events Popup Postman Primitive Types Profiles Promise propertyIsEnumerable() prototype Query Selectivity Queueable Record types Reference Types Regex Regular Expressions Relationships Rest API Rest Operator Revealing Module Pattern Role Hierarchy Salesforce Salesforce Security Schema.DescribeFieldResult Schema.DescribeSObjectResult Schema.PicklistEntry Schema.SObjectField Schema.SObjectType Security Service Components Shadow DOM Sharing Sharing Rules Singleton Slots SOAP API SOAP Web Services SOQL SOQL injection Spread Operator Star Rating stripInaccessible svg svgIcon Synchronous this Token Triggers uiObjectInfoApi Upload Files VSCode Web Services XHR
Scroll To Top