Maximizing Your Salesforce QCP Potential: Overcoming the Character Limit Challenge
Roger Rosset
Posted on October 2, 2024
Goals
- End the character limitation in Salesforce Quote Calculator Plugin (QCP) while retaining the current implementation.
- Leverage the proper use of QCP for future customizations and enhancements by removing the existing code limitation.
Specifications
Salesforce QCP code is stored within a custom script field (SBQQ_CustomScript_c). Since this is a standard Salesforce object, the usual limitations, rules, and execution guidelines for Salesforce objects apply. In particular, the code is stored in the SBQQ_Code_c field, which has a maximum character limit of 131,072 characters due to its field type (Long Text Area).
In typical JavaScript development, code can be lengthy and, practically speaking, unlimited. Modern JavaScript allows the use of modules, enabling developers to separate and organize code in ways that improve readability, reusability, and maintenance across an entire application. However, when using QCP in Salesforce CPQ, the custom script is transpiled in real time by the CPQ application (usually in the Quote Line Editor), which means we are limited to having only one record read as a plugin at a time.
The setting for this can be found here:
Setup > Installed Packages > Salesforce CPQ > Configure > Plugins Tab > Quote Calculator Plugin
This configuration tells the Quote Line Editor which specific custom script record to read as the active plugin.
Unfortunately, due to the Salesforce CPQ architecture and the out-of-the-box (OOB) behavior, JavaScript modules are not natively supported for QCP. To address this limitation, a creative workaround involves adapting the code architecture to work more effectively within Salesforce, using Static Resources.
Static Resource Approach
QCP and custom scripts in Salesforce allow the invocation of Apex methods from JavaScript code by leveraging the conn
parameter, which is powered by JSForce (a JavaScript library for interacting with Salesforce) to make Apex REST calls. By using this mechanism, we can design an architecture that overcomes the character limitation. Here’s how it works:
Static Resources: Store the main JavaScript logic in a Static Resource, which can hold the full, modular JavaScript code without the character limitations of a text area field.
Custom Script Record: Use the SBQQ_CustomScript_c record to include only the logic needed to call the appropriate methods or initialize processes from the Static Resource.
Apex Handler: Write an Apex handler class to facilitate communication between the QCP script and Salesforce, making it possible to evaluate the JavaScript code in the Static Resource at runtime.
This approach leverages JavaScript's eval()
function in a controlled manner to load the code from the Static Resource dynamically. It’s important to note that while eval()
can introduce security risks if misused, using it in this context—carefully and within the Salesforce controlled environment—can be a pragmatic solution.
Implementation Details
Custom Script Record
let QCP;
export async function onInit(quoteLineModels, conn) {
try {
const responseString = await conn.apex.get('/qcphandler/');
const response = JSON.parse(responseString);
const qcpCode = response.result;
if (qcpCode) {
await readQCP(qcpCode);
} else {
throw new Error("QCP code is not available. Check for the static resource name that should be exactly 'qcpsourcecode'");
}
} catch (e) {
console.error(e);
}
return Promise.resolve();
}
async function readQCP(qcpCode) {
await eval(qcpCode);
if (window.QCPCode !== undefined) {
QCP = window.QCPCode;
} else {
QCP = globalThis.QCPCode;
}
try {
QCP.test();
} catch (error) {
console.error("Error executing QCP.test():", error);
}
}
The SBQQ__CustomScript__c should contain a minimal script that connects to the Static Resource. This script will handle any initial setup and call the Apex methods needed for the rest of the processing.
Static Resource (JavaScript TXT file)
class QCPCode{
static test(){
console.log('Hello from QCP Static Resource');
}
}
if(typeof window !== 'undefined'){
window.QCPCode = QCPCode;
}else{
globalThis.QCPCode = QCPCode;
}
The main logic of the QCP is stored here, including modular code that otherwise would exceed character limits. This allows you to use modern JavaScript coding practices like functions and code separation for better readability.
In this example we are just implementing one simple method that does a simple console.log, but you can basically replicate your existing logic for all the QCP functions such as onBeforeCalculate(), onAfterCalculate(), etc.
Basically, the code needs to be a JS class, that after it's definition will be saved to the window or globalThis context of the browser. This is because it's not possible to use things like localStorage or sessionStorage here.
Apex Handler
@restresource(urlmapping='/qcphandler/*')
global with sharing class CPQ_QCPHandler {
@HttpGet
global static String startQcp(){
String responseString = '';
try {
String qcpSourceCode = [SELECT Body FROM StaticResource WHERE Name = 'qcpsourcecode'].Body.toString();
QCPHandlerResponse response = new QCPHandlerResponse(qcpSourceCode, true, null);
responseString = JSON.serialize(response);
} catch (Exception e) {
System.debug('Exception: ' + e.getMessage());
QCPHandlerResponse response = new QCPHandlerResponse(null, false, e.getMessage());
responseString = JSON.serialize(response);
}
return responseString;
}
private class QCPHandlerResponse{
public QCPHandlerResponse(String result, Boolean success, String errorMessage){
this.result = result;
this.success = success;
this.errorMessage = errorMessage;
}
public String result;
public Boolean success;
public String errorMessage;
}
}
The Apex handler acts as a REST endpoint that the JavaScript can call to fetch or execute resources as needed. This makes integration straightforward and keeps test coverage easy to maintain, as only a single, stable Apex method is used.
In this sample case where we basically only added one single method that does a console.log, this should be the outcome when entering the Quote Line Editor (QLE) and calculating the quote: (Screenshot taken from browser's console)
Here it's a simple boilerplate about what would the code really look like for using this approach to properly use it in real QCP scenarios:
export function onBeforeCalculate(quote, lines, conn) {
return CPQ.onBeforeCalculate(quote,lines,conn);
}
Benefits of This Approach
Readability and Maintenance: By storing the bulk of the JavaScript in Static Resources, the QCP code becomes significantly more readable and modular. It also facilitates easier long-term maintenance.
Character Limit Removed: The QCP code size is effectively unrestricted since the Static Resource can hold much larger JavaScript files than the text field in Salesforce.
Easy Rollback: During testing and implementation, it is easy to revert to the current setup using Salesforce’s OOB configuration. This ensures minimal disruption during experimentation.
Simplified Test Coverage: Since this solution involves only one Apex class that operates as a REST resource with a single method, the Apex test coverage requirements remain stable. Deploying the QCP code is simplified to deploying a new version of the Static Resource.
Simplified Testing: This approach is more about restructuring the way code is accessed rather than adding new features. Thus, testing focuses on verifying stability rather than introducing new potential bugs, making the process less risky.
Drawbacks
- Existing Code Complexity: While this approach addresses the character limit issue, it doesn’t inherently improve the quality of the existing codebase. The same logic and practices (whether well-structured or not) are preserved, meaning any pre-existing messiness or inefficiencies in the code will remain. Further refactoring could be pursued in the future to optimize code quality and structure.
Conclusion
This approach provides an effective workaround to Salesforce CPQ's limitations by leveraging Static Resources and a streamlined Apex handler. It brings significant improvements in readability, scalability, and maintenance without requiring a complete overhaul of existing business logic. Although this solution does not inherently improve the underlying code practices, it sets the stage for easier future optimizations and more robust customizations.
Important🥰
Please like this post and don't forget to make a proper reference to this article if it's useful for you.
You can find more information about me HERE
😃
Posted on October 2, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.