Plantilla y ejemplos del complemento Calculadora de presupuestos JS

by nsibilsky on ‎10-04-2017 07:26 AM

DESCRIPCIÓN GENERAL

Este artículo proporciona una descripción general del complemento Javascript Calculadora de presupuestos de Salesforce CPQ. Los temas se desglosan de la manera siguiente:

  • Complemento de notas y ejemplos
  • Ejemplos de código avanzado
    • Calcular la fecha de finalización y el plazo de suscripción verdaderos - Apex
    • Calcular la fecha de finalización y el plazo de suscripción verdaderos - Javascript
    • Cálculo total de paquete personalizado - Apex
    • Cálculo total de paquete personalizado - Javascript
  • JSForce : Proporciona una descripción general de JSForce en JSQCP, seguido de varios ejemplos de código que utilizan JSForce.
    • Encontrar registros de búsqueda - Javascript
    • Encontrar registros de búsqueda (Método-encadenado) - Javascript
    • Encontrar registros de búsqueda - Apex
    • Registros de inserción - Javascript

Complemento de notas y ejemplos

/*
 * This is a sample plugin for use in the new JavaScript Quote Calculator. It exports all of the methods that
 * the calculator will look for, and documents their parameters and return types.
 *
 * These methods are all optional. One may export any, all, or none of them in order to achieve the desired behavior.
 *
 * Note that the plugin is an ES6 module. It is transpiled via Babel, and thus it is module-scoped by default. One may
 * use any elements of the ES6 language or syntax. However, the plugin must be able to run in both Browser and Node
 * environments. This means that one cannot expect browser global variables such as window to be available.
 */


/*
 * A note on Promises:
 *
 * A Promise is a built-in JavaScript object that allows for asynchronous programming in the browser. Promises allow
 * one to delay a certain action until another one has completed. Promises support a .then(success, failure) method,
 * where success is a function that is called when the promise resolves successfully, and failure is a function called
 * when the promise is rejected. If one wishes to do any asynchronous programming in the plugin (e.g. a server callout),
 * one must return a promise that resolves once that action is completed, to guarantee that calculation steps occur
 * in the proper order.
 *
 * If a method does not require asynchronous behavior, one may return a promise that resolves immediately as follows:
 * return Promise.resolve();
 *
 * Promises can resolve to a value, which is passed as a parameter to the .then() callbacks. You may use this fact in
 * your own code, but should also be aware that the promises that these methods return do not need to resolve to a value.
 * All modification should be done directly to the quote and line models provided in the parameters.
 *
 * For more information on promises, see the documentation here:
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
 */

/*
 * A note on QuoteModel and QuoteLineModel types.
 *
 * The JavaScript calculator represents Quote__c and QuoteLine__c objects as QuoteModel and QuoteLineModel objects
 * respectively. The underlying SObject is accessible through the .record property on both objects, which allows one
 * to reference fields by using their API name. For example, one can reference a custom field SBQQ__MyCustomField__c
 * on a given QuoteLineModel by accessing the attribute, record["SBQQ__MyCustomField__c"]. Additionally, there are many
 * documented utility methods on these models. One may also reference fields on related records. For example, if one
 * wishes to reference the field MyField__c on the Account object associated with a quote, one may do so by
 * accessing record["Account__r"]["MyField__c"].
 *
 */

/*
 * A note about Salesforce field types.
 *
 * The object records that one can manipulate in the plugin are stored in JavaScript Object Notation, or JSONs. They
 * are serialized from your org, and the types of certain fields may be converted. Number, Text, and Boolean fields are all stored without
 * any conversion, but other types may be converted. For example, dates are represented as strings of the format
 * "YYYY-MM-DD". If one references a field that contains a date, this must be taken into account. If one modifies
 * a date field, this format must be preserved.
 */

/**
 * This method is called by the calculator when the plugin is initialized.
 * @param {QuoteLineModel[]} quoteLineModels An array containing JS representations of all lines in a quote
 * @returns {Promise}
 */
export function onInit(quoteLineModels) {
    return Promise.resolve();
};

/**
 * 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
 * @returns {Promise}
 */
export function onBeforeCalculate(quoteModel, quoteLineModels) {
    return Promise.resolve();
};

/**
 * This method is called by the calculator before price rules are 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
 * @returns {Promise}
 */
export function onBeforePriceRules(quoteModel, quoteLineModels) {
    return Promise.resolve();
};

/**
 * This method is called by the calculator after price rules are 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
 * @returns {Promise}
 */
export function onAfterPriceRules(quoteModel, quoteLineModels) {
    return Promise.resolve();
};

/**
 * This method is called by the calculator after calculation has completed, but before formula fields are
 * re-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
 * @returns {Promise}
 */
export function onAfterCalculate(quoteModel, quoteLineModels) {
    return Promise.resolve();
};

EJEMPLOS DE CÓDIGO AVANZADO

Los siguientes segmentos de código proporcionan implementaciones de ejemplo de JSQCP de SteelBrick en Apex y Javascript.

 

Calcular la fecha de finalización y el plazo de suscripción verdaderos - Apex

global class QCPWinter16Legacy2 implements SBQQ.QuoteCalculatorPlugin, SBQQ.QuoteCalculatorPlugin2 {

/* This QCP examples calculates and stores the effective end date on each quote line, as well as the effective term.
   It also stores the max(effective end date) and max(effective term) on the Quote object
*/


    /* NOTE: the getReferencedFields method is no longer required if you use the ReferencedFields field set on
 the Quote Line object. 
             This field set must be created as it's not a managed one.
       NOTE: if you need to access Quote fields, you can create the ReferencedFields field set on the Quote object as well.
       NOTE: if you do not use the getReferencedFields method, you can remove SBQQ.QuoteCalculatorPlugin2 from the class declaration.
    */
    global Set<String> getReferencedFields() {
        return new Set<String>{
            /* Note: add fields using the following format - Only add fields referenced
                               
               by the plugin and not in the Line Editor field set on the Quote Line object
            String.valueOf(SBQQ__QuoteLine__c.My_Field_API_Name__c)
            */
            String.valueOf(SBQQ__QuoteLine__c.True_Effective_End_Date__c),
            String.valueOf(SBQQ__QuoteLine__c.True_Effective_Term__c),
            
            String.valueOf(SBQQ__Quote__c.True_Effective_End_Date__c),
            String.valueOf(SBQQ__Quote__c.True_Effective_Term__c),
            
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveStartDate__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveEndDate__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionTerm__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__DefaultSubscriptionTerm__c),
            
            String.valueOf(SBQQ__Quote__c.SBQQ__SubscriptionTerm__c)
        };
    }

    global void onBeforePriceRules(SObject quote, SObject[] lines) {
    }
    
    global void onAfterPriceRules(SObject quote, SObject[] lines) {
    }
    
    global void onBeforeCalculate(SObject quote, SObject[] lines) {
    }
    
    global void onAfterCalculate(SObject quote, SObject[] lines) {
        Date maxEffectiveEndDate = null;
        Decimal maxEffectiveTerm = 0;
        for(SObject line : lines) {
            Date trueEndDate = calculateEndDate(quote, line);
            Decimal trueTerm = getEffectiveSubscriptionTerm(quote, line);
            if(maxEffectiveEndDate == null || maxEffectiveEndDate < trueEndDate) {
                maxEffectiveEndDate = trueEndDate;
            }
            if(maxEffectiveTerm < trueTerm) {
                maxEffectiveTerm = trueTerm;
            }
            line.put(SBQQ__QuoteLine__c.True_Effective_End_Date__c, trueEndDate);
            line.put(SBQQ__QuoteLine__c.True_Effective_Term__c, trueTerm);
        }
        quote.put(SBQQ__Quote__c.True_Effective_End_Date__c, maxEffectiveEndDate);
        quote.put(SBQQ__Quote__c.True_Effective_Term__c, maxEffectiveTerm);
    }
    
    global void onInit(SObject[] lines) {
    }
    
    private Decimal getTotal(Decimal price, Decimal quantity, Decimal priorQuantity, String pricingMethod, String discountScheduleType, Boolean renewal, Boolean existing, String subscriptionPricing, Decimal ListPrice) {
        price = (price == null) ? 0 : price;
        renewal = (renewal == null) ? false : renewal;
        existing = (existing == null) ? false : existing;
        
        if(renewal == true && existing == false && priorQuantity == null) {
            return 0;
        } else {
            return price * getEffectiveQuantity(quantity, priorQuantity, pricingMethod, discountScheduleType, renewal, existing, subscriptionPricing, listPrice);
        }
        
    }
    
    private Decimal getEffectiveQuantity(Decimal quantity, Decimal priorQuantity, String pricingMethod, String discountScheduleType, Boolean renewal, Boolean existing, String subscriptionPricing, Decimal ListPrice) {
        Decimal result = 0;
        Decimal deltaQuantity = 0;
        
        quantity = (quantity == null) ? 0 : quantity;
        priorQuantity = (priorQuantity == null) ? 0 : priorQuantity;
        pricingMethod = (pricingMethod == null) ? '' : pricingMethod;
        discountScheduleType = (discountScheduleType == null) ? '' : discountScheduleType;
        subscriptionPricing = (subscriptionPricing == null) ? '' : subscriptionPricing;
        renewal = (renewal == null) ? false : renewal;
        existing = (existing == null) ? false : existing;
        listPrice = (listPrice == null) ? 0 : listPrice;
        
        deltaQuantity = quantity - priorQuantity;
        
        if(pricingMethod == 'Block' && deltaQuantity == 0) {
            result = 0;
        } else {
            if(pricingMethod == 'Block') {
                result = 1;
            } else {
                if(discountScheduleType == 'Slab' && (deltaQuantity == 0 || (quantity == 0 && renewal == true))) {
                    result = 0;
                } else {
                    if(discountScheduleType == 'Slab') {
                        result = 1;
                    } else {
                        if(existing == true && subscriptionPricing == '' && deltaQuantity < 0) {
                            result = 0;
                        } else {
                            if(existing == true && subscriptionPricing == 'Percent Of Total' && listPrice != 0 && deltaQuantity >= 0) {
                                result = quantity;
                            } else {
                                if(existing == true) {
                                    result = deltaQuantity;
                                } else {
                                    result = quantity;
                                }
                            }
                        }
                    }
                }
            }
        }
        
        return result;
    }
    
    private Date calculateEndDate(SObject quote, SObject line) {
        Date startDate = (Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveStartDate__c));
        Date endDate = (Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveEndDate__c));
        if ((startDate != null) && (endDate == null)) {
            /* Note: we are assuming that Subscription Term Unit is Month in the package settings */
            endDate = startDate.addMonths(getEffectiveSubscriptionTerm(quote, line).intValue()).addDays(-1);
            /* Note: we are assuming that Subscription Term Unit is Day in the package settings */
//          endDate = startDate.addDays(getEffectiveSubscriptionTerm(line).intValue() - 1);
        }
        return endDate;
    }
    
    private Decimal getEffectiveSubscriptionTerm(SObject quote, SObject line) {
        Decimal lineTerm = null;
        Date startDate = (Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveStartDate__c));
        Date endDate = (Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveEndDate__c));
        if ((startDate != null) && (endDate != null)) {
            /* Note: we are assuming that Subscription Term Unit is Month in the package settings */
            lineTerm = startDate.monthsBetween(endDate.addDays(1));
            /* Note: we are assuming that Subscription Term Unit is Day in the package settings */
//            lineTerm = startDate.daysBetween(endDate.addDays(1));
        } else {
            lineTerm = (Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionTerm__c));
            if (lineTerm == null) {
                lineterm = (Decimal)quote.get(String.valueOf(SBQQ__Quote__c.SBQQ__SubscriptionTerm__c));
                if (lineTerm == null) {
                    return (Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__DefaultSubscriptionTerm__c));
                }
            }
        }
        return lineTerm;
    }
    
}

 

Calcular la fecha de finalización y el plazo de suscripción verdaderos - Javascript

export function onAfterCalculate(quote, lineModels) {
    var maxEffectiveEndDate = null;
    var maxEffectiveTerm = 0;
    if (lineModels != null) {
        lineModels.forEach(function (line) {
            var trueEndDate = calculateEndDate(quote, line);
            var trueTerm = getEffectiveSubscriptionTerm(quote, line);
            if (maxEffectiveEndDate == null || (maxEffectiveEndDate < trueEndDate)) {
                maxEffectiveEndDate = trueEndDate;
            }
            if (maxEffectiveTerm < trueTerm) {
                maxEffectiveTerm = trueTerm;
            }
            line.record["True_Effective_End_Date__c"] = toApexDate(trueEndDate);
            line.record["True_Effective_Term__c"] = trueTerm;
        });
        quote.record["True_Effective_End_Date__c"] = toApexDate(maxEffectiveEndDate);
        quote.record["True_Effective_Term__c"] = maxEffectiveTerm;
    }
}

function calculateEndDate(quote, line) {
    var sd = line.record["SBQQ__EffectiveStartDate__c"];
    var ed = line.record["SBQQ__EffectiveEndDate__c"];
    if (sd != null && ed == null) {
        ed = sd;
        ed.setUTCMonth(ed.getUTCMonth() + getEffectiveSubscriptionTerm(quote, line));
        ed.setUTCDate(d.getUTCDate() - 1);
    }
    return ed;
}

function getEffectiveSubscriptionTerm(quote, line) {
    var sd = line.record["SBQQ__EffectiveStartDate__c"];
    var ed = line.record["SBQQ__EffectiveEndDate__c"];
    if (sd != null && ed != null) {
        ed.setUTCDate(ed.getUTCDate() + 1);
        return monthsBetween(sd, ed);
    } else if (line.SubscriptionTerm__c != null) {
        return line.SubscriptionTerm__c;
    } else if (quote.SubscriptionTerm__c != null) {
        return quote.SubscriptionTerm__c;
    } else {
        return line.DefaultSubscriptionTerm__c;
    }
}

/**
 * Takes a JS Date object and turns it into a string of the type 'YYYY-MM-DD', which is what Apex is expecting.
 * @param {Date} date The date to be stringified
 * @returns {string}
 */
function toApexDate(/*Date*/ date) {
    if (date == null) {
        return null;
    }
    // Get the ISO formatted date string.
    // This will be formatted: YYYY-MM-DDTHH:mm:ss.sssZ
    var dateIso = date.toISOString();

    // Replace everything after the T with an empty string
    return dateIso.replace(new RegExp('[Tt].*'), "");
}

function monthsBetween(/*Date*/ startDate, /*Date*/ endDate) {
    // If the start date is actually after the end date, reverse the arguments and multiply the result by -1
    if (startDate  > endDate) {
        return -1 * this.monthsBetween(endDate, startDate);
    }
    var result = 0;
    // Add the difference in years * 12
    result += ((endDate.getUTCFullYear() - startDate.getUTCFullYear()) * 12);
    // Add the difference in months. Note: If startDate was later in the year than endDate, this value will be
    // subtracted.
    result += (endDate.getUTCMonth() - startDate.getUTCMonth());
    return result;
}

 

Cálculo total de paquete personalizado - Apex

global class QCPWinter16Legacy implements SBQQ.QuoteCalculatorPlugin, SBQQ.QuoteCalculatorPlugin2 {

    /* NOTE: the getReferencedFields method is no longer required if you use the ReferencedFields field set on
 the Quote Line object. 
             This field set must be created as it's not a managed one.
       NOTE: if you need to access Quote fields, you can create the ReferencedFields field set on the Quote object as well.
       NOTE: if you do not use the getReferencedFields method, you can remove SBQQ.QuoteCalculatorPlugin2 from the class declaration.
    */
    global Set<String> getReferencedFields() {
        return new Set<String>{
            /* Note: add fields using the following format - Only add fields referenced
                               
               by the plugin and not in the Line Editor field set on the Quote Line object
            String.valueOf(SBQQ__QuoteLine__c.My_Field_API_Name__c)
            */
            String.valueOf(SBQQ__QuoteLine__c.Component_Custom_Total__c),
            
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProratedListPrice__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__PriorQuantity__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__PricingMethod__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__DiscountScheduleType__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__Renewal__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__Existing__c),
            String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionPricing__c)
        };
    }

    global void onBeforePriceRules(SObject quote, SObject[] lines) {
    }
    
    global void onAfterPriceRules(SObject quote, SObject[] lines) {
    }
    
    global void onBeforeCalculate(SObject quote, SObject[] lines) {
    }
    
    global void onAfterCalculate(SObject quote, SObject[] lines) {
        for(SObject line : lines) {
            SObject parent = line.getSObject(SBQQ__QuoteLine__c.SBQQ__RequiredBy__c.getDescribe().getRelationshipName());
            if(parent != null) {
              Decimal pComponentCustomTotal = (Decimal)parent.get(String.valueOf(SBQQ__QuoteLine__c.Component_Custom_Total__c));
              
              Decimal cListPrice = (Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProratedListPrice__c));
              Decimal cQuantity = (Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Quantity__c));
              Decimal cPriorQuantity = (Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__PriorQuantity__c));
              String cPricingMethod = (String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__PricingMethod__c));
              String cDiscountScheduleType = (String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__DiscountScheduleType__c));
              Boolean cRenewal = (Boolean)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Renewal__c));
              Boolean cExisting = (Boolean)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Existing__c));
              String cSubscriptionPricing = (String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionPricing__c));
              
              pComponentCustomTotal = (pComponentCustomTotal == null) ? 0 : pComponentCustomTotal;
              
              cListPrice = (cListPrice == null) ? 0 : cListPrice;
              cQuantity = (cQuantity == null) ? 1 : cQuantity;
              cPriorQuantity = (cPriorQuantity == null) ? 0 : cPriorQuantity;
              cPricingMethod = (cPricingMethod == null) ? 'List' : cPricingMethod;
              cDiscountSCheduleType = (cDiscountSCheduleType == null) ? '' : cDiscountSCheduleType;
              cRenewal = (cRenewal == null) ? false : cRenewal;
              cExisting = (cExisting == null) ? false : cExisting;
              cSubscriptionPricing = (cSubscriptionPricing == null) ? '' : cSubscriptionPricing;
              
              Decimal cTotalPrice = getTotal(cListPrice, cQuantity, cPriorQuantity, cPricingMethod, cDiscountScheduleType, cRenewal, cExisting, cSubscriptionPricing, cListPrice);
              pComponentCustomTotal += cTotalPrice;
              
              parent.put(SBQQ__QuoteLine__c.Component_Custom_Total__c, pComponentCustomTotal);
              
            }
        }
    }
    
    global void onInit(SObject[] lines) {
        for(SObject line : lines) {
            line.put(SBQQ__QuoteLine__c.Component_Custom_Total__c, 0);
        }
    }
    
    private Decimal getTotal(Decimal price, Decimal quantity, Decimal priorQuantity, String pricingMethod, String discountScheduleType, Boolean renewal, Boolean existing, String subscriptionPricing, Decimal ListPrice) {
        price = (price == null) ? 0 : price;
        renewal = (renewal == null) ? false : renewal;
        existing = (existing == null) ? false : existing;
        
        if(renewal == true && existing == false && priorQuantity == null) {
            return 0;
        } else {
            return price * getEffectiveQuantity(quantity, priorQuantity, pricingMethod, discountScheduleType, renewal, existing, subscriptionPricing, listPrice);
        }
        
    }
    
    private Decimal getEffectiveQuantity(Decimal quantity, Decimal priorQuantity, String pricingMethod, String discountScheduleType, Boolean renewal, Boolean existing, String subscriptionPricing, Decimal ListPrice) {
        Decimal result = 0;
        Decimal deltaQuantity = 0;
        
        quantity = (quantity == null) ? 0 : quantity;
        priorQuantity = (priorQuantity == null) ? 0 : priorQuantity;
        pricingMethod = (pricingMethod == null) ? '' : pricingMethod;
        discountScheduleType = (discountScheduleType == null) ? '' : discountScheduleType;
        subscriptionPricing = (subscriptionPricing == null) ? '' : subscriptionPricing;
        renewal = (renewal == null) ? false : renewal;
        existing = (existing == null) ? false : existing;
        listPrice = (listPrice == null) ? 0 : listPrice;
        
        deltaQuantity = quantity - priorQuantity;
        
        if(pricingMethod == 'Block' && deltaQuantity == 0) {
            result = 0;
        } else {
            if(pricingMethod == 'Block') {
                result = 1;
            } else {
                if(discountScheduleType == 'Slab' && (deltaQuantity == 0 || (quantity == 0 && renewal == true))) {
                    result = 0;
                } else {
                    if(discountScheduleType == 'Slab') {
                        result = 1;
                    } else {
                        if(existing == true && subscriptionPricing == '' && deltaQuantity < 0) {
                            result = 0;
                        } else {
                            if(existing == true && subscriptionPricing == 'Percent Of Total' && listPrice != 0 && deltaQuantity >= 0) {
                                result = quantity;
                            } else {
                                if(existing == true) {
                                    result = deltaQuantity;
                                } else {
                                    result = quantity;
                                }
                            }
                        }
                    }
                }
            }
        }
        
        return result;
    }
    
}

 

Cálculo total de paquete personalizado - Javascript

export function onInit(lines) {
    if (lines != null) {
        lines.forEach(function (line) {
            line.record["Component_Custom_Total__c"] = 0;
        });
    }
};

export function onAfterCalculate(quoteModel, quoteLines) {
    if (quoteLines != null) {
        quoteLines.forEach(function (line) {
            var parent = line.parentItem;
            if (parent != null) {
                var pComponentCustomTotal = parent.record["Component_Custom_Total__c"] || 0;
                var cListPrice = line.ProratedListPrice__c || 0;
                var cQuantity = line.Quantity__c == null ? 1 : line.Quantity__c;
                var cPriorQuantity = line.PriorQuantity__c || 0;
                var cPricingMethod = line.PricingMethod__c == null ? "List" : line.PricingMethod__c;
                var cDiscountScheduleType = line.DiscountScheduleType__c || '';
                var cRenewal = line.Renewal__c || false;
                var cExisting = line.Existing__c || false;
                var cSubscriptionPricing = line.SubscriptionPricing__c || '';

                var cTotalPrice = getTotal(cListPrice, cQuantity, cPriorQuantity, cPricingMethod, cDiscountScheduleType, cRenewal, cExisting, cSubscriptionPricing, cListPrice);
                pComponentCustomTotal += cTotalPrice;

                parent.record["Component_Custom_Total__c"] = pComponentCustomTotal;
            }
        });
    }
};

function getTotal(price, qty, priorQty, pMethod, dsType, isRen, isExist, subPricing, listPrice) {
    if ((isRen === true) && (isExist === false) && (priorQty == null)) {
        // Personal note: In onAfterCalculate, we specifically make sure that priorQuantity can't be null.
        // So isn't this loop pointless?
        return 0;
    } else {
        return price * getEffectiveQuantity(qty, priorQty, pMethod, dsType, isRen, isExist, subPricing, listPrice);
    }
}

function getEffectiveQuantity(qty, priorQty, pMethod, dsType, isRen, exists, subPricing, listPrice) {
    var delta = qty - priorQty;

    if (pMethod == 'Block' && delta == 0) {
        return 0;
    } else if (pMethod == 'Block') {
        return 1;
    } else if (dsType == 'Slab' && (delta == 0 || (qty == 0 && isRen == true))) {
        return 0;
    } else if (dsType == 'Slab') {
        return 1;
    } else if (exists == true && subPricing == '' && delta < 0) {
        return 0;
    } else if (exists == true && subPricing == 'Percent Of Total' && listPrice != 0 && delta >= 0) {
        return qty;
    } else if (exists == true) {
        return delta;
    } else {
        return qty;
    }
}

 

JSForce

JSForce es una biblioteca externa que proporciona una manera unificada de realizar consultas, ejecutar llamadas REST de Apex, utilizar la
API de metadatos o realizar solicitudes HTTP de forma remota. Los métodos acceden a jsforce a través del parámetro conn opcional.
 
Para obtener más información sobre JSForce, consulte los siguientes recursos:

 

Encontrar registros de búsqueda - Javascript

/**
 * Created by jfeingold on 9/27/16.
 */
export function onAfterCalculate(quote, lines, conn) {
 if (lines.length > 0) {
  var productCodes = [];
  lines.forEach(function(line) {
   if (line.record['SBQQ__ProductCode__c']) {
    productCodes.push(line.record['SBQQ__ProductCode__c']);
   }
  });
  if (productCodes.length) {
   var codeList = "('" + productCodes.join("', '") + "')";
   /*
    * conn.query() returns a Promise that resolves when the query completes.
    */
   return conn.query('SELECT Id, SBQQ__Category__c, SBQQ__Value__c FROM SBQQ__LookupData__c WHERE SBQQ__Category__C IN ' + codeList)
    .then(function(results) {
     /*
      * conn.query()'s Promise resolves to an object with three attributes:
      * - totalSize: an integer indicating how many records were returned
      * - done: a boolean indicating whether the query has completed
      * - records: a list of all records returned
      */
     if (results.totalSize) {
      var valuesByCategory = {};
      results.records.forEach(function(record) {
       valuesByCategory[record.SBQQ__Category__c] = record.SBQQ__Value__c;
      });
      lines.forEach(function(line) {
       if (line.record['SBQQ__ProductCode__c']) {
        line.record['SBQQ__Description__c'] = valuesByCategory[line.record['SBQQ__ProductCode__c']] || '';
       }
      });
     }
    });
  }
 }
 return Promise.resolve();
}

 

Encontrar registros de búsqueda - Javascript (utiliza el estilo método-encadenado para construir su consulta; resultará útil para construir consultas de forma dinámica)

/**
 * Created by jfeingold on 9/27/16.
 */
export function onAfterCalculate(quote, lines, conn) {
 if (lines.length) {
  var codes = [];
  lines.forEach(function(line) {
   var code = line.record['SBQQ__ProductCode__c'];
   if (code) {
    codes.push(code);
   }
  });
  if (codes.length) {
   var conditions = {
    SBQQ__Category__c: {$in: codes}
   };
   var fields = ['Id', 'Name', 'SBQQ__Category__c', 'SBQQ__Value__c'];
   /*
    * Queries can also be constructed in a method-chaining style.
    */
   return conn.sobject('SBQQ__LookupData__c')
    .find(conditions, fields)
    .execute(function(err, records) {
     if (err) {
      return Promise.reject(err);
     } else {
      var valuesByCategory = {};
      records.forEach(function(record) {
       valuesByCategory[record.SBQQ__Category__c] = record.SBQQ__Value__c;
      });
      lines.forEach(function(line) {
       if (line.record['SBQQ__ProductCode__c']) {
        line.record['SBQQ__Description__c'] = valuesByCategory[line.record['SBQQ__ProductCode__c']] || '';
       }
      });
     }
    });
  }
 }
 return Promise.resolve();
}

 

Encontrar registros de búsqueda - Apex

/**
* Created by jfeingold on 9/27/16.
*
* This is a silly use case, since it by definition could be accomplished with a price rule and a lookup table.
* It is meant to be demonstrative of any plugin that requires a query for external data.
*/

global class QCPForFindingLookupRecords implements SBQQ.QuoteCalculatorPlugin, SBQQ.QuoteCalculatorPlugin2 {
global set<String> getReferencedFields() {
return new Set<String> {
String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProductCode__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__Description__c)
};
}

global void onInit(SObject[] lines) {}

global void onBeforeCalculate(SObject quote, SObject[] lines) {}

global void onBeforePriceRules(SObject quote, SObject[] lines) {}

global void onAfterPriceRules(SObject quote, SObject[] lines) {}

global void onAfterCalculate(SObject quote, SObject[] lines) {
if (!lines.isEmpty()) {
String[] productCodes = new String[0];
for (SObject line : lines) {
String productCode = (String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProductCode__c));
if (productCode != null && !productCode.isWhitespace()) {
productCodes.add(productCode);
}
}
SBQQ__LookupData__c[] ds = [SELECT Id, SBQQ__Category__c, SBQQ__Value__c FROM SBQQ__LookupData__c WHERE SBQQ__Category__c IN :productCodes];
if (!ds.isEmpty()) {
Map<String,String> valuesByCategory = new Map<String,String>();
for (SBQQ__LookupData__c d : ds) {
valuesByCategory.put(d.SBQQ__Category__c, d.SBQQ__Value__c);
}
for (SObject line : lines) {
String productCode = (String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProductCode__c));
if (productCode != null && !productCode.isWhitespace()) {
line.put(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Description__c), valuesByCategory.get(productCode));
}
}
}
}
}
}

 

Registros de inserción - Javascript

/**
 * Created by jfeingold on 9/27/16.
 */
export function onAfterCalculate(quote, lines, conn) {
 if (lines.length) {
  var codes = [];
  lines.forEach(function(line) {
   var code = line.record['SBQQ__ProductCode__c'];
   if (code) {
    codes.push(code);
   }
  });
  if (codes.length) {
   var conditions = {
    SBQQ__Category__c: {$in: codes}
   };
   var fields = ['Id', 'Name', 'SBQQ__Category__c', 'SBQQ__Value__c'];
   return conn.sobject('SBQQ__LookupData__c')
    .find(conditions, fields)
    .execute(function(err, records) {
     console.log(records);
     if (err) {
      return Promise.reject(err);
     } else {
      var valuesByCategory = {};
      records.forEach(function(record) {
       valuesByCategory[record.SBQQ__Category__c] = record.SBQQ__Value__c;
      });
      var newRecords = [];
      lines.forEach(function(line) {
       var code = line.record['SBQQ__ProductCode__c'];
       var desc = line.record['SBQQ__Description__c'];
       if (code && desc && !valuesByCategory[code]) {
        newRecords.push({
         SBQQ__Category__c: code,
         SBQQ__Value__c: line.record['SBQQ__Description__c']
        });
       }
      });
      if (newRecords.length) {
       return conn.sobject('SBQQ__LookupData__c')
        .create(newRecords, function(err, ret) {
         console.log(ret);
        });
      }
     }
    });
  }
 }
 return Promise.resolve();
}