[SalesForce] Salesforce CPQ – JS Calc Works But Does Not Show In UI

I've been working through configuring CPQ and have reached a scenario I was trying to solve using customization. When generating a quote and adding a line, I want to look at previous active quotes (based on contract start/end dates) and determine if a discounted price is already in effect, if so, apply the same discount.

I'm using all OOB fields to complete this calculation. I have added the custom script and have verified all of the values are being updated correctly. The problem is that when the screen loads, the discount and prices are not updated. If I click "Calculate" right after the product is added, everything fills out correctly (even though the calculation lifecycle has already completed once).

I am using the following code:

/**
* This method is called by the calculator before calculation begins, but after formula fields have been evaluated.
* @param {QuoteModel} quoteModel JS representation of the quote being evaluated
* @param {QuoteLineModel[]} quoteLineModels An array containing JS representations of all lines in the quote
* @param {Conn[]} conn Is a representation of the JSForce library for connecting to SFDC server side
* @returns {Promise}
*/
export function onBeforeCalculate(quoteModel, quoteLineModels, conn) {
    console.log('RUNNING -----> onBeforeCalculate', 'arguments available --> ', arguments);
    determineValidHistoricalPricing(quoteModel, quoteLineModels, conn);
    return Promise.resolve();
}

function determineValidHistoricalPricing(quote, lines, conn) {
    if(lines.length > 0) {
        var productIds = [];
        lines.forEach(function(line){
        if(line.record['SBQQ__Product__c']) {
            productIds.push(line.record['SBQQ__Product__c']);
        }
    });
    if(productIds.length) {
        var productIdList = "('" + productIds.join("','") + "')";
    }

    var queryString = "SELECT SBQQ__Quote__r.SBQQ__Account__c, SBQQ__Product__c, AVG(Average_Price_Per_Unit__c) avgPricePerUnit ";
    queryString += "FROM SBQQ__QuoteLine__c ";
    queryString +=     "WHERE SBQQ__Quote__r.SBQQ__Status__c = 'Accepted' AND ";
    queryString += "SBQQ__Quote__r.SBQQ__Account__c = '" + quote.Account__c + "' AND ";
    queryString += "SBQQ__Product__c IN " + productIdList + " AND ";
    queryString += "SBQQ__Quote__r.SBQQ__EndDate__c > TODAY AND ";
    queryString += "SBQQ__Quote__r.SBQQ__StartDate__c < TODAY ";
    queryString += "GROUP BY SBQQ__Quote__r.SBQQ__Account__c, SBQQ__Product__c ";
    queryString += "ORDER BY SBQQ__Quote__r.SBQQ__Account__c";
    return conn.query(queryString)
        .then(function (results) {
            if(results.totalSize) {
                var avgPriceByProduct = {};
                results.records.forEach(function (record) {
                    avgPriceByProduct[record.SBQQ__Product__c] = record.avgPricePerUnit;
                });

                lines.forEach(function(line){
                    if(
                        line.isSubscription &&
                        line.Id === null &&
                        line.record['SBQQ__Product__c'] &&
                        avgPriceByProduct[line.record['SBQQ__Product__c']]
                    ) {
                        var contractedPrice =
                            avgPriceByProduct[line.record['SBQQ__Product__c']] *
                            line.record['SBQQ__Quantity__c'];
                        console.log('contractedPrice = ' + contractedPrice);
                        var lineDiscount = (1 - (contractedPrice / line.record['SBQQ__ListPrice__c'])) * 100;
                        console.log('lineDiscount = ' + lineDiscount);
                        if(lineDiscount > 0) {
                            line.record['SBQQ__PartnerDiscount__c'] = lineDiscount;
                        }
                    }
                });
            }
        });
    }
}

I have tried adding the "determineValidHistoricalPricing" function to all event triggers (onInit, onBeforeCalculate, onBeforePriceRules, onAfterPriceRules, and onAfterCalculate) and have the same results, the values are updated in the record correctly but the UI does not update until after the product is added, the calculation rules run, and then I click Calculate again (the record in Google Chrome Dev Tools shows the updated values after the calculation rules run the first time, before clicking Calculate).

Any help would be much appreciated!

Best Answer

Most probably the problem is in the order of execution of your code.

The onBeforeCalculate (and other event handler functions) return a Promise so that the calculating engine can wait for one, to finish processing, before starting another.

But in your code, you are starting a Promise (conn.query()) but exit from the event handler immediately with return Promise.resolve();

What you probably want is to return the promise in the event handler: return determineValidHistoricalPricing(...) so that the flow actually waits for your logic to finish.

Update: CPQ requires its event handlers to return an actual Promise object (and not a promise-like JSForce connection), so for the above to work one must also wrap the JSForce query in a standard Promise, like so:

return new Promise(function(resolve, reject) {
    conn.query(queryString)
        .then(function (results) {
            // ... processing results ...
            resolve(results);
        });
})
Related Topic