Salesforce CPQ: Fixing Percent of Total Calculations for Amended Quotes

Salesforce users looking to enhance their product-quoting processes can take advantage of Salesforce Configure, Price, Quote Software, or CPQ. Salesforce CPQ allows users to create complex pricing structures through a range of new functionality, including the ability to dynamically price an item based on a percentage of the total quote amount. This Percent of Total pricing, or POT, works well when creating a new quote, but users may be disappointed to find that it breaks down when they attempt to amend an existing quote. However, with some JavaScript and a little TLC we can bridge this gap in functionality, allowing you to amend quotes to your heart's content. Let's take a closer look - https://youtu.be/cmyLlZ_mmOY

The Problem

Let's suppose your company sells electronics, and you want to provide an optional support package for your customers. Because the amount of work involved varies based on the amount of hardware they purchase, you decide to price the support package as a percentage of the total cost of the purchased hardware. This works well for larger accounts, but you decide it's not worthwhile to maintain smaller accounts that might only pay, say, $50 a month. To avoid this, you add a minimum amount of $200 to the support price calculation, and your shiny new "Hardware Support Package" is configured something like this:

Hardware Support Package

So far, so good! You whip up a couple of quotes to make sure the Percent of Total price calculation still works correctly with your minimum price constraint. Here is an example of a quote where the support price is 25% of the software total, since this amount is greater than the $200 minimum:

software total

And an example of a quote where the support price defaults to the $200 minimum:

edit quote

It works exactly as expected, and your confidence quickly grows. You officially implement this pricing structure, and you begin sending quotes like those above to your customers. You receive good feedback from your sales reps, until one of them tries to amend one of these quotes in a previously accepted contract. Let's use the smaller quote from above, and assume the customer now needs a total of three charge controllers. The additional cost should push the new Percent of Total calculation above the $200 minimum, and the amendment quote should reflect the additional cost of the support package. However, our new quote seems to have missed this memo:

sales reps

Needless to say, this behavior can lead to lost profits on amended contracts. We have a problem.

dont miss out iconDon't forget to check out: Discount Categories in Salesforce CPQ

The Solution

According to the official documentation, "Salesforce CPQ doesn’t support the Percent of Total Constraint field on amendment quotes." Rather than letting unaware users face bizarre or inconsistent results when attempting to amend a quote, CPQ simply ignores any such POT fields. Thus, to get the calculation to function properly, we first have to override this default behavior; then, we can implement our own solution.

CPQ's default behavior works roughly as follows: if a quote item on an amended quote uses Percent of Total pricing and that item has either a minimum or maximum price constraint, then CPQ automatically sets the unit price of that item to zero. Thus, one way to circumvent this behavior is to get rid of the price constraint of a POT item whenever it appears on an amended quote. (Astute readers may note that this sounds like a bad idea, given that our whole goal is to preserve the price constraint; this will be accounted for in our next step.)

Step 1: The Price Rule

One of the easiest ways to automatically adjust price calculations is to create a price rule, so let's try that. The entry conditions for our new rule should essentially mimic CPQ's default behavior outlined above; we want our conditions to apply only to quote lines on an amended quote which have constrained POT pricing.  Setting the calculator evaluation event to "On Initialization;Before Calculate" allows our rule to run before this particular bit of CPQ default behavior, and adding two new Price Actions to the rule allows us to zero out the pricing constraints:

price calculations
price conditions
price conditions

dont miss out iconCheck out another amazing blog by DB Services here: Saving Journey History in Marketing Cloud

Step 2: The Plugin

Now that we have circumvented CPQ's default behavior, we are free to create our own solution. Salesforce CPQ allows users to extend its functionality through custom scripts, and for our situation, we want to create a Quote Calculator Plugin. As the name suggests, this plugin will run every time a quote is (re)calculated, which allows us to define the logic for calculating our amended POT price.

In order for our plugin to calculate the price correctly, it needs data from three different types of records: Quote, Quote Line, and Subscription. These objects all have fields that reference the "original quote," which allow us to directly compare the new quote data against the data from the quote being amended.

With this information in hand, we can calculate the desired price for our item and set the list price accordingly. The code snippet included below is excerpted from our Quote Calculator Plugin:

quoteLineModels.forEach(function (line) {
    if (line.record["SBQQ__SubscriptionPricing__c"] === "Percent Of Total") {
        line.record["_SBQQ__ListPrice__c"] = line.record["SBQQ__ListPrice__c"];
        get: function () {
            return this["_SBQQ__ListPrice__c"];
        },
        set : function (newValue) {
            if (!this["potCalculatedPriceHasBeenSet"]) {
                this["PoT_Calculated_List_Unit_Price__c"] = newValue;
                this["potCalculatedPriceHasBeenSet"] = true;
            }
            this["_SBQQ__ListPrice__c"] = newValue;
            let aggregatedSubData = quoteModel.record.attributes["aggregatedSubData"];
            let originalQuoteLinePoTSettings =
            aggregatedSubData && quoteModel.record.attributes["aggregatedSubData"][line.record["Id"]]
            ? quoteModel.record.attributes["aggregatedSubData"][line.record["Id"]].originalQuoteLinePoTSettings
            : "";
            let potSubscriptionsCalculatedListUnitPrice =
            aggregatedSubData && quoteModel.record.attributes["aggregatedSubData"][line.record["Id"]]
            ? quoteModel.record.attributes["aggregatedSubData"][line.record["Id"]]
            .potSubscriptionsCalculatedListUnitPrice
            : "";
            if (
                quoteModel.record["SBQQ__Type__c"] === "Amendment" &&
                this["SBQQ__PriorQuantity__c"] &&
                originalQuoteLinePoTSettings
            ) {
                let { minimumUnitPrice, maximumUnitPrice } = originalQuoteLinePoTSettings;
                let totalListUnit = potSubscriptionsCalculatedListUnitPrice;
                if (minimumUnitPrice) {
                    let totalAboveConstraint = (minimumUnitPrice - (totalListUnit + newValue)) * -1;
                    let constraintWasExceededPriorToQuote = minimumUnitPrice - totalListUnit <= 0;
                    if (totalAboveConstraint > 0 && !constraintWasExceededPriorToQuote) {
                        this["_SBQQ__ListPrice__c"] = totalAboveConstraint;
                    } else if (totalAboveConstraint <= 0) {
                        this["_SBQQ__ListPrice__c"] = 0;
                    }
                } else if (maximumUnitPrice) {
                    let totalAboveConstraint = (maximumUnitPrice - (totalListUnit + newValue)) * -1;
                    if (totalAboveConstraint >= 0) {
                        this["_SBQQ__ListPrice__c"] = 0;
                    }
                }
            }
        }
    });
}

This code is part of our onAfterPriceRules() method, which is executed during the second round of calculations. The most important part to note is that we are updating the setter for each quote line item's list price attribute to use our custom logic. Also, it may be helpful to note that the above attribute "aggregatedSubData" is used to store the "original quote" info returned by a SOQL query earlier in the script. The exact details of your implementation will depend on how your org is configured, and more details and examples can be found in the CPQ Documentation - https://developer.salesforce.com/docs/atlas.en-us.cpq_dev_api.meta/cpq_dev_api/cpq_dev_jsqcp_parent.htm

The Conclusion

Salesforce CPQ provides great tools for businesses to tailor their pricing practices to best suit their own needs. If you need to further customize your process to add Percent of Total Calculations on Amended Quotes reach out to us at DB Services and we'd be happy to help!

Responses

Popular Salesforce Blogs