Blog

Software tips, techniques, and news.

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.

youtube-preview

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:

salesforce cpq example pricing 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:

salesforce cpq edit quote example.

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

salesforce cpq quote example.

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:

salesforce cpq amended quote incorrect example.

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

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:    

salesforce cpq price action rule.
salesforce cpq price conditions.
salesforce cpq price action.

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"];
        Object.defineProperty(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.  

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!

Need help with your Salesforce digital transformation? Contact us to discuss Salesforce consulting, implementation, development, and support!

Download the Salesforce CPQ Quote Calculator Plugin

Please complete the form below to download your FREE Salesforce file.

Salesforce Experience *
Terms of Use *
OPT-IN: I agree that I am downloading a completely free file with no strings attached. This file is unlocked, and I may use it for my business or organization as I see fit. Because I am downloading a free file, I agree that I should receive occasional marketing. I understand that I can OPT-OUT of these emails at anytime.
Matt Clark headshot.
Matt Clark

Matt is a reliable and precise Salesforce developer who is motivated by providing the highest-quality results to clients. His commitment to quality, consistency, and organization mean he is skilled at problem-solving and catching errors or flaws in design.