Cross-Functional Excellence in Pricing with Salesforce CPQ

Cross-Functional Excellence in Pricing with Salesforce CPQ

Originally, this was going to be an article about scenarios necessitating Lookup Query-driven Price Rules in Salesforce CPQ (based on a request I received in the community), and while it will accomplish that, I think there is a lot more to discuss about how this VERY powerful capability can help to combine the perspectives of multiple departments in your organization when it comes to the prices you charge your customers. Consider the following business case:

  • A pricing team sets the "MSRP" of the actively sold products on an annual basis according to market research, in conjunction with revenue growth and profitability targets established by the C-suite.
  • The business is just getting off the ground, so as of now, they don't differentiate their offerings according to market segment. But it's more expensive to fulfill enterprise contracts than small business ones, and global operations have varying levels of efficiency as compared to NAMER, and they'd like the ability to adjust the price accordingly based on some assumptions which can be adjusted in the future.
  • Marketing is tasked with increasing the customer base, and their biggest initiative at-present is running advertisements which give new customers a discount if they mention the promotion to their sales rep prior to purchase.
  • Sales reps have the ability to apply a discount to a purchase at their discretion, within a certain range of authority. They also may request bigger discounts for large/strategic deals which must be approved by their management hierarchy.
  • Finance is extremely focused on price controls, wanting to ensure that efforts to obtain customers and close deals do not result in selling product at a loss.
  • IT operations is concerned about system performance, person-hours to administer it, and deploying a system that is overly complex to the sales team as it will result in more "howto" tickets and training cost.

That's a long list of asks that seem to be mostly unrelated, or even at times in conflict with one another. Can Salesforce CPQ handle the job?

The answer is yes, if you can add one qualifying statement - The Salesforce platform with CPQ can handle the job.

I've already let the cat out of the bag in terms of one part of the solution - Price Rules w/Lookup Queries. I will not rehash the Salesforce Help articles, but I will summarize what goes into their construction to help understand the intent. Typical Price Rules include a "header" record that defines when and in what order to fire, to which one or multiple Price Condition records (telling the rule under what conditions to fire) and one or more Price Action records (telling the rule what to do when it fires, in what sequence) are related.

Lookup Query-based rules add 2 more components: A custom object called a "lookup object" - defined at the header-level, which stores your query data in database form ("columns" are custom fields and "rows" are records); and one or more Lookup Query records (telling the rule which record on your lookup object has the data you want).

Functionally, this is not exactly revolutionary - Salesforce developers have been using custom objects, or custom settings, or custom metadata types along with Apex triggers and classes with SOQL queries to perform these kinds of operations as long as the core tools have existed. The killer feature here is the fact that no-code admins (with a bit of dataloader) can create and maintain these data tables and automations within CPQ, too! And CPQ already knows how to treat these rules and provides the guardrails necessary to ensure that they reliably work, with the exception of preventing you from loading too much data. But that is in general very challenging to do, and there are techniques that can be used to address larger data volumes that I will address.

Ok, so with the background out of the way, let's start tackling the requirements, one-by-one.

  • Pricing "MSRP" - Price books and entries

This is table stakes for any CPQ or Sales Cloud admin, so I won't belabor this point. The biggest implication for CPQ is that we are assuming any products covered by this exercise have a "Pricing Method" of "List". This will also work for "Cost"-priced products with some additional and slightly different configuration. But the key here is that my job as Salesforce Admin/Architect is to take the price lists from the pricing team and load them into Salesforce accordingly, so they are ready for use when relevant. If you are not clear on that, I would respectfully suggest to spend some time on Trailhead at this point, because the rest of this will not make any sense.

  • Pricing variance according to Account's Market Segment and Global Region

This is going to be the first of two requirements addressed by Lookup Query-based Price Rules. But given that my Salesforce environment is new and I'm still in the process of configuring CPQ, there are a number of things I need to do to make sure all the data I need is available, consistent, and able to be copied and transformed where necessary. In the process of building out the functionality, I encountered some quirky limitations in core Salesforce and CPQ both, which necessitated certain design decision I will address where relevant.

No alt text provided for this image

Notice the 3 highlighted fields:

  • "Employees" is a standard field (API name: NumberOfEmployees) which, you guessed it, stores the number of employees who work for an account.
  • "Market Segment" is a text formula field in which I define a value according to the number of employees. Here's the logic:
IF(
     NumberOfEmployees < 101 , "Small" ,
     IF(
          NumberOfEmployees < 501 , "Mid-Market" ,
          IF(
               NumberOfEmployees < 1001 , "Commercial" ,
               "Enterprise"
          )
     )
)

  • "Global Region" is a picklist, required at the field-level, which includes NAMER, LATAM, EMEA, and JAPAC. I configured it as a Global Value Set so that I can ensure picklist value consistency across objects, and eliminate repetitious field configuration down the line.

Now I have the attribution necessary to classify an account in different ways, which is part of the battle, but to reference these values in any kind of CPQ rule, they need to exist on the Quote or Quote Line object. Q: How are we going to do that?

A: Populate fields on the Quote record using a Screen Flow (similar to this) attached to a Quick Action on the Opportunity record page (or Quote related list on that page).

"Whoa, that's pretty heavy to copy some field values!" you might be saying to yourself. There are multiple reasons to choose this method:

  • Flow is the most powerful tool the no-code Admin has in their arsenal. Starting with a Screen Flow ensures future requirements can be incorporated, up to the limits at which time a developer must be called to build a Lightning Web Component (LWC).
  • Flow Builder is in general preferable to Process Builder at this point, as Flow Builder is where all of the workflow automation feature enhancements are going in each release, and nearly all of the Process Builder capabilities have been ported over at this point. It also uses the same underlying technology as Process Builder, now called "Salesforce Flow".
  • Workflow Rules (WFR's) have been de-emphasized for years, and cannot create records, so at best you would be creating a record some other way and triggering one or multiple rules from the Quote object. WFR's are limited in cross-object references and field updates per rule, so they aren't very scalable even in the best of circumstances.
  • A Quick Action without the Screen Flow was my original idea, as it can create records and even populate predefined field values, but I encountered a limitation where it would not allow me to reference the "Market Segment" formula field on the associated Account; only a non-formula field from Account (which would require additional automation or manual input to keep up-to-date), or a formula field on Opportunity (which would require additional automation to replicate - you may need this anyway, in which case this isn't a limitation for you, but I didn't). I was already contemplating the Flow option and this sealed the deal.
  • Many might consider a cross-object formula field looking up to Account, but since it is evaluated on every page refresh, it is less performant than a Flow which we can configure to fire only at record creation. They are similarly limited as with WFR's for the number of cross-object references, and as I later determined, the type of Price Rules we are building are not able to reference formula fields as the source.

In the "Create Records" element of my Flow, I populate the fields I need on the Quote record I'm creating, referencing the fields from the Account related to the Opportunity from which I initialized my Flow. While I'm at it, I populate 5 additional fields with values from elsewhere inside and outside of my Flow, which will save clicks for my sales reps - again, read the link above for more detail on this. Also, Ignore the "Campaign" field for now, the purpose of that will be described in a later step.

No alt text provided for this image

Now we are all set in terms of having the data needed in the Quote. But to this point, everything we've been talking about is "Sales data". Didn't I write earlier that the pricing team was going to have some say in the proceedings beyond providing the starting price list? That's the next set of steps.

To prepare for future configuration, now is the time to create the Price Rule "header", even though you are not yet ready to create the related records. Mine looks like this:

No alt text provided for this image

I will discuss the importance and relevance of the fields displayed when we get to the bulk of the rule configuration, and the highlighted 18-digit Record ID in the URL will be relevant to some of the forthcoming UI configuration shown. But for now, making sure that this record exists is as far as you need to take it; you can leave the rule inactive if you are working in an environment where having an incomplete rule like this could cause confusion. Now we need to create the database this rule will reference through its Lookup Queries, as defined by the "Lookup Object" field.

In this particular case, the lookup object you define will be the place to store the pricing data which goes above and beyond the Price Book, and gets specific according to the attribution one might need. Since there are 4 Market Segments, and 4 Global Regions, we're going to configure the object so that it is able to store a price-modifying percentage (down OR up) for each combination of segment and region, resulting in a total of 16 (4x4) records for this use case.

The first decision to make is which object to use. While you may be thinking that you don't have a custom object purpose-built for this, by nature of the fact that you installed CPQ, you do: SBQQ__LookupData__c. Using this object for at least some of your source rule data comes with a few benefits:

  1. It already contains the fields you would otherwise need to create for Lookup Query-based Product Rules.
  2. The object API is already populated on the "Lookup Object" field on Price and Product rules, so that is one less picklist to edit.
  3. If your environment is at or close to the max for number of custom objects, you can use this (non-optional) part of the CPQ package install instead of creating another.

This resolves where to start, but not necessarily where to end. If you are not concerned with the custom object limit, and/or don't intend on creating any Product Rules, you may find it preferable to use one or even multiple custom objects for your Price Rules use cases. I try to stick with this as my one and only object for the selfish reason that it allows me to maintain all of my tables in a single spreadsheet, with which I can update the system via a single dataloader upsert operation. Well-designed Lookup Query-based Price Rules have no problem working with tens of thousands of object records, or even millions in most cases. Choosing to do so raises some usability concerns that should be mitigated:

  • I created separate record types and layouts for the 3 rule types: Price Rules, Product Rules of the "Validation" type, and Product Rules of the "Selection" type, allowing for easier-to-understand table record creation in the UI when necessary since I can now exclude unnecessary fields.
  • I created separate list views for each rule by creating lookups to the Price and Product Rule objects, creating a validation rule which ensures one or the other but not both is populated, and then creating the following text formula field (which I called "Rule ID") to reference as list view filter criteria:
IF( 
     ISBLANK( Price_Rule__c ) , 
     CASESAFEID( Product_Rule__c ) , 
     CASESAFEID( Price_Rule__c ) 
)

Using the "CASESAFEID()" function allows me to convert 15-digit Salesforce record Id's (what's stored in lookup fields) to 18-digit (what's used in record URL's). Then I can filter my list view just by evaluating whether that field contains a record ID I copy and paste from my browser window, like this:

No alt text provided for this image

Why am I filtering by Record Type, isn't that redundant? From the perspective of displaying only the desired records, yes, but applying that additional filter allows me to enable inline editing from the list view, another UI time saver.

Now that I have built the UI elements which will make the object easier to work with, it's time to create the attributes which will drive the business logic of my rule. This is a pretty simple case as you may recall:

  • Pricing variance according to Account's Market Segment and Global Region

All I need to create on my lookup object are the "Market Segment" and "Global Region" fields, and a "Price Modifier" field to store the percentage of modification from Price Book relevant to the combination of those previous two attributes.

Since I have no source or context from which to derive the attribution values, but I'd still like to ensure consistent and quality data values, I'm going to create both as picklists this time. For "Global Region" this is easy, I choose the same Global Value Set I previously created and used on the Account object version of this field. For "Market Segment", I went back and created a Global Value Set at this point, because I used a formula field on Account and a plaintext field (populated by Flow) on Quote, but this way if I need this picklist to exist on another object moving forward, I have the Global Value Set. Then I create a "Percent(16,2)" field I'm calling "Price Modifier", leaving it as generic as possible in hopes that I may be able to use it for additional use cases in the future.

I also create a few additional fields while I'm at it:

  • "Record Name" (Text,255): Because for some reason, the default "Name" field was configured as auto-number instead of text, which serves no purpose here. I configured as an external ID and require uniqueness so that I can easily cross-reference rows in my spreadsheet to records in multiple sandbox and production environments as-necessary.
  • "Rule Name" (Formula Text): Similar to "Rule ID", this instead is a field for export only which tells me which rule is in play for a given table record, which helps to locate (manually or using VLOOKUP) the correct source when cloning existing rows to create new ones with slightly different values.
  • "Record Type Name" (Formula Text): Similar to "Rule Name", this helps me to confirm I am populating the right Record Type ID in cloned rows.

The end result looks like this in my spreadsheet:

No alt text provided for this image

The "Record Name" field is easy to auto-create, by using CONCAT to combine "Rule Name + Global Region + Market Segment", ensuring I have a unique value and consistent format. As you can see, I have a variety of positive, negative, and 0 "Price Modifiers" for my use case.

After performing an INSERT operation using my dataloader tool of choice, my list view is filled with my new table records, which looks like this:

No alt text provided for this image

As you can see, I have the standard name field to the left which allows me to click in to individual records, and I've included creation and modification identity/date fields to assist in a multi-admin environment, where a person may be most interested in the records they've created, but may need to see all, and also track who modified a particular record/when.

Now I have everything I need to complete the Price Rule configuration, aside from adding the API names for the various fields I created to the appropriate picklists, which is a typical part of the process that I will call out where necessary.

As a reminder, here's my rule:

No alt text provided for this image
  • The name is somewhat important in this case, as I am using it as a part of the naming convention for my lookup object records.
  • It seems elementary, but when troubleshooting a rule that doesn't seem to be firing, ensure that its "Active" checkbox is set to true.
  • "Evaluation Scope" tells us that this record is evaluated within the Quote Line Editor (Calculator), as opposed to during bundle configuration (Configurator).
  • The "Calculator Evaluation Event" is a field which allows you to tell the code in CPQ at which stage of the calculation sequence to evaluate this rule. Best practice is to use the earliest stage in which the source data will be available. In this case, I chose "Before Calculate" rather than "On Initialization", because the values I needed to reference were not available in the latter.
  • "Evaluation Order" optionally tells CPQ the order in which to evaluate each rule, in cases of precedence, or subsequently referencing values calculated by a prior rule - which we will be doing later, so I set it to "1" here.

Our rule header is in order, so now it is time to define the Price Conditions. Here I'm only configuring one, which will allow me to target my rule against a specific product as a PoC:

No alt text provided for this image

In this case, I didn't even need to add any fields to the "Field" picklist, because "Product Code" is one of the default values. In a full implementation, you would likely configure "product" as one of your lookup object attributes, which in this case would necessitate creating table records for 16 x "the number of products to which this rule is relevant". That is likely a lot of records, but using standard Excel techniques to clone and interpolate data values will allow you to create this very quickly, and it doesn't take much longer to load 160,000 records than it does to load 16. In that case, you may not need a Price Condition at all (if this rule covers 100% of your catalog), or you may define a different type of condition, such as checking for a specific type of quote.

Next up are the Lookup Queries. Since I have two independent attributes, I'm going to create a matching query for each:

No alt text provided for this image
No alt text provided for this image

These are the simplest types of queries, since I am merely comparing a field on my Quote (populated by my Flow referencing the Account, as you may recall) to a field on a record on my lookup object. When you have multiple queries, they operate as an AND, so this rule is now going to find one and only one lookup object record which meets the conditions. I did need to add my field API name to the "Tested Field" and "Lookup Field" picklists, representing the Quote and lookup object versions of this field, respectively.

In cases where you need more attributes to get to "one and only one", and/or you have a large number of records in general, you may consider combining two attributes you would otherwise specify in "Tested Field" as one field on the source object. For example, I could have created one field that returned Region + Segment, such as "NAMER Enterprise", then a corresponding concatenated attribute on my lookup object. "Your Mileage May Vary", but reducing the number of rules and the number of queries in those rules is one of the first places to check if you are running into performance problems with your CPQ implementation.

Finally, it's time to create the Price Actions. While it may seem that you have enough data to "complete this in one", two constraining factors mean you actually need two here:

  1. A Price Action can only act upon a single field.
  2. When using the formula function, the data you reference must all exist in fields in the Quote / Quote Line objects.

So even though we now have enough data to "know" which lookup object record we need for a given Quote, and that record has the Price Modifier on it which we can reference directly, we need to store that value on the Quote Line first so that we can apply it against the base price in a formula, resulting in the targeted modified price. Therefore, let's first copy the Price Modifier from the queried lookup object record, to the Quote Line which contains the Product specified in our Price Condition:

No alt text provided for this image

As you can see, I've added my Quote Line and lookup object "Price Modifier" field API names to the requisite picklist fields, and the "Target Field" additionally requires setting the object field dependency values. Also note that the "Order" field is populated here as well; this determines the order within a specific rule in which actions fire - crucial as our next action is going to reference the value in our Target Field.

No alt text provided for this image

This action is where it all comes together. I am updating "List Price" on my Quote Line by applying the "Price Modifier" (stored on the same Quote Line as a result of the previous action) against "Original Price", which represents a static/unmodified value from which to reference the original Price Book Entry value. The "Order" is set to "2" because it must fire after the previous action in-sequence.

Here is the end result in my Quote Line Editor (QLE), for an Account in the "NAMER" region, "Enterprise" Segment, where I am applying a 5% uplift* according to bullet #2 at the beginning, and how I configured my table data in my spreadsheet/lookup object:

No alt text provided for this image
*Displayed as a "negative discount", in line with how "Additional Discount" works in CPQ; you can adjust your Price Action formula if you prefer price increases to be reflected as positive percentages, as with "Cost + Markup" pricing. Also, the packaged "Original Price" field is not available for selection in the QLE, which I work around by creating a custom currency formula field that references this field.

We've put a lot of effort to get this far, but the good news is a lot of that was "scaffolding" which will allow us to build more rules more quickly and easily. Which is helpful for tackling the next requirement:

  • Give new customers a discount automatically as a part of a promotional campaign

Not only have we built most of the elements we need to configure this rule, but you probably already use one of the new ones: Campaign Influence. If you aren't using it and never have before, first make sure that you can create new Campaigns if necessary. Next, determine your model for how Campaigns will influence Opportunities. In my case, since this is a CPQ-focused PoC, I just exposed the field on the Opportunity layout and manually selected a Campaign I created; you could even simulate this by creating a custom lookup to any object you want from your Opportunity if you don't want to use the native Campaign object for some reason. Now, create a field (Text,255) on Quote to store the name of the Campaign associated to your Opportunity. Finally, update your Flow from earlier (the "Create Records" element) to additionally populate your new Quote field with the value of your Opportunity's Campaign name (already present in the previous screenshot), save, and activate. Congrats; this is as deeply as you the CPQ Admin/Architect need to care about how marketing tracks the revenue impact of one campaign vs. another!

As before, the next step is to create a placeholder Price Rule header. Here's what mine looks like:

No alt text provided for this image

The only noteworthy part at this point is that the "Evaluation Order" is "2", because we are going to apply pricing modification AFTER that which we calculated and applied in the first rule, so it necessarily follows that we would want to ensure this rule evaluates 2nd. Retain your 18-digit record ID from the URL so you can reference it to create a new list view on your lookup object, allowing for a more targeted look at your Promotions/Campaigns-related object records and excluding the Segment/Region ones.

Since I only configured one Campaign, I only need to create one lookup object record, meaning I can take advantage of the UI configuration work I completed earlier instead of firing up my dataloader. Here's the result:

No alt text provided for this image

You'll notice a couple of new fields I created - "Campaign" (a lookup to that object), and "Campaign Discount". While I wrote earlier about the potential for reuse of "Price Modifier", I chose not to do so in this case because this is a completely different discount I will need to store in a separate Quote Line field. And while that doesn't prevent me from using the same field at the lookup object level (since this record will be pulled by a completely separate rule from the first and modifying different fields in the price waterfall*), I decided it was easier to understand the end-to-end rule logic in this PoC by creating the separate field. In an implementation where it's possible to have dozens or hundreds of such fields on millions of records, I might choose differently! Same goes for the blank fields - this is where a admin attempting to satisfy high standards of visual design might prefer to use a second object, or additional record type and layout, so as not to have to display the irrelevant fields relating to the previous rule. The additional option which is brand new (GA as of Winter '21) and exclusive to Lightning is Dynamic Forms, which would be perfect for this case in the full implementation.

*Study and memorize the price waterfall documentation, because not only will I reference it multiple times in the remainder of this article, but it is a key piece of knowledge when designing and troubleshooting CPQ, as well as passing the certification exam....

Not seen is a formula text field which pulls the Campaign Name from the related Campaign record, and this is the field I will use to compare against the Quote value in my Price Rule - there are no limitations against using formula fields here, and it saves me the step of updating my lookup object records should a Campaign be renamed for some reason.

I'm ready to complete my Price Rule. First up as before, the Price Conditions:

No alt text provided for this image

In this case, I do need two, "All" of which must be simultaneously true, because I am applying to the same product as the first rule, but this time I only want to evaluate the rule if there is in fact a Campaign attached to the Opportunity...so I test my Quote-level field, populated by my Flow.

Next, the Lookup Query:

No alt text provided for this image

Query as in singular, because all I need to do is match my Quote to the lookup object record which represents the specified Campaign. As before, I need to add my field API names to the "Tested Field" and "Lookup Field" picklist fields.

So far, this hasn't been very interesting, in the sense that it's not showing you anything the previous rule did not. That is about to change with the Price Actions, of which we need three. Here's the 1st:

No alt text provided for this image

In the end, this rule needs to achieve two things on a given Quote Line:

  1. Populate a pricing field later in the previously-referenced price waterfall than "List Price".
  2. Allow marketing (and anyone else who may be interested) to track which specific lines have promotional pricing applied.

The 2nd point in particular is one of the most common questions I get relating to Salesforce CPQ; "How does it handle promotions?" You're looking at it *waves hand to the article above*. Again, you don't necessarily need to use the Campaign object, but you need a way to track when promotions apply and when they don't, and Campaigns are as good as anything else for this. You may need to associate them to Quote Lines rather than Quotes (via the Opportunity), if different Campaigns may apply to different Products on the same Quote, which is possible, but would take additional work and possibly customization (in the form of a Quote Calculator Plugin). If it's just that multiple products apply to the same Campaign, that is another reason to use a Product attribute on the lookup object, which would additionally require a 2nd (or concatenated, as previously mentioned) Lookup Query on your Price Rule.

The first point explains the method of this action; the next pricing value in the waterfall after "List Price" is "Special Price", which also serves as the last unit price value that ignores "time" in a subscription-pricing scenario - the value in this field, which defaults to "List Price" unless the system or user modifies it, has proration applied to it to become "Regular Price". The one additional quirk to understand is that CPQ requires a value in "Special Price Type" in order to permit modifications to "Special Price". The "Type" field is an unrestricted picklist, and CPQ doesn't care what value is stored there so long as it is not blank, so as an Admin/Architect I treat this like a plaintext field in my Price Rules - and in this case, I am populating it with the plaintext value of the name of the Campaign associated to my queried lookup object record.

I apologize if you have made it this far and thus just had to endure two full paragraphs on why I'm populating a field with the value from another field, but unfortunately this is a case where you must perform this exact step in some fashion, so it's best if you know "why" and make it useful for your own purposes. Also - don't forget the "Order" field! It must be "1" here. I bring it up every time because I get a ton of support questions about this when people forget it. Moving on to the 2nd Action...

No alt text provided for this image

Not much to say here (thankfully!), I'm just populating the discount percentage on my specific Product Quote Line as I must, related to the Campaign as defined by the queried lookup object record. And last but not least:

No alt text provided for this image

Here's the "fun" part. I'm referencing "List Price", which I previously modified with the 1st Price Rule, and modifying it using the "Campaign Discount", stored on the Quote Line from the previous action, and outputting the result in "Special Price". Well, it was fun for me at least. Here's the result in the QLE:

No alt text provided for this image

So in the end, I'm using two Price Rules, referencing the same lookup object, to modify two separate pricing fields (List Price and Special Price), for two different use cases (pricing team and marketing), on the same Quote Line, in a highly scalable way with zero code - satisfying the IT operations concern/requirement.

You can stop here if you were only interested in learning how to configure Price Rules with Lookup Queries. There are still two requirements in my original business case left to address, however:

  • Sales reps want discretionary discounting capabilities, and sales management/ops wants the ability to approve/reject out-of-policy discounts
  • Finance wants to ensure fiscal responsibility at the transactional level, while still facilitating growth of the customer base and annual revenue

I am addressing these together, because they 1) are flip sides to the same coin, and 2) can be addressed using the same standard capabilities of CPQ and your approvals workflow package of choice (I prefer Salesforce CPQ Advanced Approvals if you are licensed for it, aka "SBAA", keeping the SteelBrick heritage alive) , along with collaboration between the two departments.

You may recall that I previously referenced "Regular Price". This is a key field in the price waterfall, because it is where "computer" pricing ends (typically) and "human" pricing begins. In some organizations, sales will never see Original/List Price on a Quote or even know/have reference to what it is, or why it might be more or less on a particular deal. For these organizations, the selling *begins with* Regular Price, and the rep applies further manipulations via the "Additional Discount" field, which can modify the value of Regular Price a number of different ways, resulting in "Customer Price". It's possible that you've been using CPQ for a while and have never thought about this field or didn't know that it existed; the reason being is that it is not relevant unless you are *also* applying either a "Partner Discount" (resulting in "Partner Price") and/or a "Distributor Discount", which is the final price-modifying field before arriving at "Net Price". If you are using neither of those in your implementation, then "Additional Discount" can be viewed as the final price-modifying field before "Net Price".

Just because it's there, however, doesn't mean you want to use it. Maybe your organization has put so much thought and effort into its pricing tables that "Regular Price" is your "best and final". That's perfectly fine - Products and Quote Lines both can be marked as "Non-Discountable" and/or "Partner Non-Discountable", though there is not a specific mechanism to block Distributor Discounts - just disable the field itself, I guess, if removing it from layouts is not safe enough for you.

The final concept to consider is profit or margin (or both), at the line or Quote level (or both), and then what to do with that information. CPQ does not include calculated profit/margin fields for you out-of-the-box because different organizations in different industries think about and calculate them differently. For example, a value-added reseller may look at margin as the difference between cost of goods and net price. A SaaS company may operate with extremely high list pricing that is discounted heavily in almost every case, so they may only be interested in governing the difference between an intermediate price that is closer to reality, and the final. Different organizations may look at these values in a blended way at the aggregated Quote-level, or the max discount/lowest margin or profit on any Quote Line.

Once you've determined your methodology for comparing the deal metrics and configured it, you must also define and configure the actions the organization wants to take as a result. There are three categories:

  1. The deal falls within a range that is objectively acceptable with which to allow the sales rep to proceed without additional review - from the system perspective, this is the "do nothing" category.
  2. The deal falls within a range that may be subjectively acceptable and requires additional review by sales ops/management, or potentially other teams such as finance, legal, engineering, etc. This is the "approvals workflow" category.
  3. The deal falls within a range that is objectively not acceptable to the organization; either the rep and customer must agree to different terms which reclassify the deal into one of the first two categories, or the customer is not a fit for the organization and the deliberations conclude. This is the "hard validation stop" category.

I'm going to start with the 2nd because it directly impacts the 1st, and the 3rd is both arbitrary and non-negotiable - often, organizations design complex approval hierarchies, and of course the tools can accommodate this, but they are done so without considering what exactly the desired outcome is in a quantifiable way. Many organizations don't even know where to begin to quantify this, so in those cases I typically recommend setting a desired ratio of pass/review/fail (e.g., 70%/20%/10%) and then analyzing historical sales data to determine where the thresholds need to be set to achieve those metrics. If in today's world, sales reps are only given authority to discount a quote by 5%, but 100% of quotes are approved when a 10% discount is requested, it would be more efficient to give reps 10% discount authority and instead run a weekly/quarterly/etc. report on deals which didn't need review. You may also consider incentivizing them as well via additional commission or bonus to close deals with lower discount percentages, driving good behaviors using positive reinforcement.

Another way to look at it is the time to approve deals as opportunity cost - if you are losing too many deals because the approval cycle takes too long, an approvals workflow tool paired with CPQ can shorten cycle times, but reducing the number of quotes reviewed and/or simplifying/flattening your governance structure is more effective.

A final note on this - Deploying a new CPQ tool can be scary, and you may want to put deals that pass through the system under a higher level of scrutiny at first. That's fine, but be sure to proactively plan for how and when you will exit this heightened state of awareness, and be actively seeking to define the thresholds with which you can empower your reps to sell with less organizational "friction". Beside the potential impacts to top-line growth and profitability, reps who not only are freer to sell, but also understand what the guardrails are and why they're in place, tend to experience much higher job satisfaction, which in turn frees up management to spend more time on strategic decision making - and less time on hiring and training to address turnover, micromanaging deals, etc.

As you can see, there are a plethora of people/process/technology factors to consider when deploying pricing capabilities in a CPQ tool. I hope this deep-dive exercise helps in understanding how Salesforce platform tools and techniques can work in-concert with Salesforce CPQ, and how thinking about these issues as a connected whole can raise the game for an entire company and its customers.

Samar Shahir

Salesforce QTC Solution Architect at NTT Data

3 年

Thanks for writing such a detailed article, I've enjoyed reading this and implemented few things from your article. Very Useful!

Alexander L. D. ?rstr?m

CPQ Architect & Associate Manager at Accenture | Salesforce CPQ & Salesforce Industries (Vlocity)

4 年

Hi Christopher You have written a thorough and very interesting article that I have both enjoyed and learned from. Although I have done a number of similar setups, there were several small nuances to your solution that are quite elegant and which I will implement in my own builds from now on.? Thank you for taking the time to write this down and share it with the world; I am certain that I am not the only one who will benefit from this. __ Alexander

Bryan Nicolas

Senior Manager at PwC | Leading organizations through Digital Transformation in Quote to Cash space

4 年

Thanks for sharing! So useful!

Tiffany Devlin-Drye

Salesforce Principal Architect | 10x Salesforce Certified | Local User Group Leader

4 年

So, so useful! Thank you for the level of depth and easy-to-follow instructions!

要查看或添加评论,请登录

Christopher Hickman ?的更多文章

社区洞察

其他会员也浏览了