0

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!

2
  • Did you try with "onInit" event? Jun 8, 2018 at 11:51
  • Yes, I tried with onInit. In every event, the object in the dev console has all of the proper values filled in it just doesn't display until I click "Calculate". Jun 8, 2018 at 15:56

2 Answers 2

1
+100

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);
        });
})
6
  • Unfortunately you must return a promise from each of the events. Jun 8, 2018 at 15:55
  • That's correct. But the conn.query() function does return a promise (or at least something that behaves like one - with then method etc.) so returning it from the event handler should work. If for some reason CPQ needs an actual Promise object, you can also wrap the conn.query with a standard Promise. Jun 9, 2018 at 16:47
  • I tried making the change and received an error that returning a Promise is required. What would wrapping a conn.query in a Promise look like? Jun 10, 2018 at 20:13
  • Something along the lines of: return new Promise(function(resolve, reject) { conn.query(queryString) .then(function (results) { // ... processing results ... resolve(results); }); }) Jun 11, 2018 at 11:57
  • 1
    Great! I've updated the answer with the code for creating a Promise out of the query. Jun 12, 2018 at 8:59
0

I think this can be done in the following event.

export function onInit(quoteLineModels, conn) {
    debug('onInit()');
    debug('quoteLineModel', quoteLineModels);
    debug('conn (jsforce connection object)', conn);
    debug('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
    return Promise.resolve();
}

onBeforeCalculate will get called when you click on the Calculate.

2
  • Thank you for the answer, unfortunately I've tried putting the code in all the various events but the same issues occurs. Jun 8, 2018 at 15:01
  • @techbusinessman please refer to my comment below Apr 5, 2021 at 15:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .