Error Message in QLE: Apex CPQ Time Limit Exceeded

Whaaaaaat is going on with this error message??
Sometimes, when you click save on the Quote Line Editor, you get a message saying it timed out! Why would this happen, you ask? There are plenty of reasons and some of them are listed below.

  • Too many Price Rules/Product Rules
    • Price Rules
    • Product Rules
    • Summary Variables
  • Too many pieces of automation
    • Workflow Rules
    • Process Builder
    • Flows
    • APEX Trigger
  • Too many records
    • Quote Lines
    • Opportunity Products
    • Order Lines
    • Subscriptions
  • Too much data on the above objects
    • Custom fields
    • Large/Complex Bundle Products
  • Too much processing is happening in the background.
    • Timed Batch processes
  • You’re in a slow sandbox

How to figure out what’s causing the problem
Here is a sequence of testing events you can employ to figure out which of the above is causing the issue. Make a dummy quote with a thousand lines. Try each of the below and try saving the dummy quote after each step.

  • Turn off Price/Product Rules
  • Turn off Workflow/Process Builder/Flow/APEX Trigger
  • Turn on Large Quoting Experience
  • Simplify a complex bundle product
  • Remove unused custom fields
  • Reduce number of quote lines

How to FIX IT
There are a few best practice things you can do to mitigate this issue. Here’s another list!

  • Price/Product Rules
    If you are noticing you have a lot of Price Rules or Product Rules, look for the below things you can optimize.Combine Rules. If you have 10 rules that all fire on Before Calculate, and there are no Price Conditions of any of the rules, you can combine all of these rules into one rule that has many actions.

    Remove Calculator Events. Sometimes, there are rules that are fired on all Calculator Events. This is generally bad practice and means you should look into reengineering your Price Rules so that they do not need to be fired so often.

    Write a QCP. Often times, rules that fire on all calculation events or that require a double calculate are better functioning if they exist inside a QCP. Simple JavaScript can sometimes make complex rules very very simple. See below for an example rollup.
//QuoteCalculatorJS
export const CONSTANTS = {
	DEBUG: true,
	DS01: 'DS01'
};

export function onAfterCalculate(quote, lines, conn) {
	var sumMatrix = new Array();
	
	sumMatrix = sumUpHours(quote, lines, conn, sumMatrix);
	
	return Promise.resolve();
}

export function sumUpHours(quote, lines, conn, sumMatrix) {
	if (lines.length > 0) {
		console.log('Zeroing Bundle Hours... Setting Matrix...');
		lines.forEach(function(line) {
			if (line.record != null) {
				line.record['SBQQ__Source__c'] = null;
				if (line.record['SBQQ__Bundle__c'] == true) {
					line.record['Hours__c'] = 0;
					line.record['Price__c'] = 0;
					//sumMatrix[line.record['SBQQ__Product__c']] = line;
					sumMatrix[line.record['Id']] = line;
				} else if (line.record['Hours__c'] != '' && line.record['Hours__c'] != null && line.record['Hours__c'] > 0) {
					line.record['Price__c'] = line.record['SBQQ__ListPrice__c'] * line.record['Hours__c'];
				} else if (line.record['Hours__c'] == '' || line.record['Hours__c'] == null || line.record['Hours__c'] == 0) {
					line.record['Price__c'] = null;
				}
			}
		});
		console.log('Zeroing Hours... Setting Matrix... Done!');
	
		console.log('Filling Sum Matrix...');
		// loop through option levels starting from largest.
		var optionLevel = 0;
		for (optionLevel = 4; optionLevel > 0; optionLevel--) {
			// loop through quote lines with this option level.
			lines.forEach(function(line) {
				if (line.record != null && line.record['SBQQ__OptionLevel__c'] == optionLevel) {
					// get this line's hours.
					var lineHours = line.record['Hours__c'];
					var linePrice = line.record['Price__c'];
					
					// if this quote line's hours is not null and is > 0,
					if (lineHours != null && lineHours != 0) {
						// get this quote line's parent.
						var lineParProdID = line.record['SBQQ__RequiredBy__c'];
						//var lineParProdID = line.record['Required_By_Product_ID__c'];
						//if (lineParProdID == null || lineParProdID == '') {
						//	lineParProdID = line.record['Parent_Product_ID__c'];
						//}
						
						if (lineParProdID != null && lineParProdID != '') {
							// add this quote line's hours to the parent hours count in the matrix for this parent.
							sumMatrix[lineParProdID].record['Hours__c'] += lineHours;
							sumMatrix[lineParProdID].record['Price__c'] += linePrice;
						}
					}
				}
			});
			
			// resolve promise.
			//Promise.resolve();
		}
	}
	console.log('Filling Sum Matrix... Done.');
	return sumMatrix;
}
  • Automation
    Move any automation that exists on the Quote, Quote Line, and Quote Line Group to being on somewhere else like Opportunity Stage = Closed Won. Another thing you can do is use Price Rules or a QCP (Quote Calculator Plug-in). This will speed things up and also help consolidate. Keep in mind that when you have a primary quote, and have many quote lines, and click save, CPQ will insert/update/delete Opportunity Products at the same time. It will also update Order Lines or Subscriptions which will also fire the automation you have on those objects.
    • Quote and Quote Lines
      • Move as much of this automation to Price Rules as possible.
    • Opportunity Products
      • Move as much of this automation to Opportunity Stage = Closed Won as possible.
    • Subscriptions
      • Move as much of this automation to Contract Status = Activated as possible.
    • Order Lines
      • Move as much of this automation to Order Status = Active as possible.
  • Large Quoting Experience
    Enabling this feature in the CPQ package settings is highly recommended if you are expecting to have lots of Quote Lines. Make sure to set your Quote Batch Size to a reasonable number to chunk things up in manageable groups of Quote Lines.
  • Custom Fields
    Remove as many custom fields as possible from the CPQ Objects. (Quote, Quote Line, Opportunity, Opportunity Product, Order, Order Line, Contract, Subscription, Asset, Contracted Price)
  • Large Bundles
    Look into redesigning your bundles such that there are less Features, Options, Constraints, and other complexities.
  • Timed Batch Processing
    If you have processes that kick off in a timed manner, especially processes that touch the objects that CPQ uses, move this processing to off hours. That way it will not be running at the time the users are in there making quotes.
  • Sandboxes
    If none of the things above are happening, sometimes the sandbox you’re developing in is VERY slow. In this case, back up your data and refresh the sandbox. In most cases, this resolves the issue.
  • Asynchronous Calculation
    Another way to help resolve the timeout issue is to enable asynchronous calculation. This is a way to elongate the timeout limit for calculations that happen outside the line editor.

Debugging Article: https://help.salesforce.com/articleView?id=000317375&language=en_US&type=1&mode=1
QCP Documentation: https://developer.salesforce.com/docs/atlas.en-us.cpq_dev_plugins.meta/cpq_dev_plugins/cpq_dev_jsqcp_parent.htm
This Error SFDC Article: https://help.salesforce.com/articleView?id=000317385&language=en_US&type=1&mode=1
Asynchronous Calculation Article: https://help.salesforce.com/articleView?id=cpq_enable_async_calculation.htm&type=5

2 thoughts on “Error Message in QLE: Apex CPQ Time Limit Exceeded

  1. How to incorporate Alert Message within QCP ? I have a requirement to Alert Message if a product is not allowed to sale in the particular country during Quote Creation. I am using QCP to get the customer pricing , discounts from ERP systems.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.