Annual Pricing Variable

A custom field with a defined pricing variable alone isn't sufficient for annual prices. Rollups are needed for both the line items and the order header. The order total must incorporate the annual prices, and these prices must be capable of handling adjustments, discounts, and other modifications.

Annual pricing variable enables users to specify an annual price for products within the Enterprise Product Catalog (EPC). This pricing detail can then be utilized in both the Industries Configure, Price, Quote (CPQ) Cart and the Standard Digital Commerce API.

Initial Setup

Create Custom Fields on Order Item

Field Label Field Name Data Type Purpose
Annual Charge AnnualCharge__c Currency(16, 2) Store base annual price from PLE or ABP
Annual Calculated Price AnnualCalculatedPrice__c Currency(16, 2) Net price, if discount is present
Annual Effective Total AnnualEffectiveTotal__c Currency(16, 2) Annual total for root items (Bundle Total)
Annual Manual Discount AnnualManualDiscount__c Percent(2, 2) Discount percentage field
Annual Total AnnualTotal__c Currency(16, 2) Total for calculated price and rollup with quantity

Create Custom Fields on Order

Field Label Field Name Data Type Formula
Total: Annual Charges AnnualTotal__c Roll-Up Summary (Annual Total) -
Effective Total: Annual AnnualEffectiveTotal__c Roll-Up Summary (Annual Effective Total) -
Order Total OrderTotal2__c Formula vlocity_cmt__OneTimeTotal__c + vlocity_cmt__Tax__c +( vlocity_cmt__RecurringTotal__c * vlocity_cmt__NumberOfContractedMonths__c )+(AnnualTotal__C*(vlocity_cmt__NumberOfContractedMonths__c/12))
Effective Order Total EffectiveOrderTotal2__c Formula vlocity_cmt__EffectiveOneTimeTotal__c + vlocity_cmt__Tax__c +( vlocity_cmt__EffectiveRecurringTotal__c * vlocity_cmt__NumberOfContractedMonths__c ) + (AnnualEffectiveTotal__c * (vlocity_cmt__NumberOfContractedMonths__c) / 12)

note: The total formulas use the vlocity_cmt__NumberOfContractedMonths__c field to prorate Annual totals. This can be adjusted or changed independently without affecting the rest of the configuration.

Create Annual Pricing Variable

Pricing Variable Name Code Aggregation Charge Type Frequency Scope Applies To Variable Pricing Variable Binding Name Object Field Type Sub-Type ValueType
Annual Charge Adjustment Absolute ANNUAL_CHARGE_ADJ_ABS Unit Adjustment Yearly Line Annual Std Price Price Standard Pricing Element
Annual Charge Adjustment Percent ANNUAL_CHARGE_ADJ_PERC Unit Adjustment Yearly Line Annual Std Price Price Standard Pricing Element
Annual Std Price ANNUAL_CHARGE Unit Recurring Yearly Line Annual Std Price - OrderItem OrderItem AnnualCharge__c Price Standard Pricing Element
Annual Std Price Calculated ANNUAL_STD_PRC_CALC Unit Recurring Yearly Line Annual Std Price Calculated - OrderItem OrderItem AnnualCalculatedPrice__c Price Standard Calculated
Annual Std Price Discount Pct Manual ANNUAL_STD_PRC_DISC_PCT_MAN Unit Adjustment Yearly Line Annual Std Price Price Standard Calculated
Annual Std Price Total ANNUAL_STD_PRC_TOTAL Quantity Recurring Yearly Line Annual Std Price Total - OrderItem OrderItem AnnualTotal__c Price Standard Calculated
Effective Annual Std Price Total EFF_ANNUAL_STD_PRC_TOTAL Quantity Recurring Yearly Line Effective Annual Std Price Total - OrderItem OrderItem AnnualEffectiveTotal__c Price Standard Calculated
Group Rollup Annual Std Price Total GROUP_ROLLUP_ANNUAL_STD_PRC_TOTAL Quantity Recurring Yearly Group Rollup Parent Rollup Annual Std Price Total Price Standard Calculated
Parent Rollup Annual Std Price Total PARENT_ROLLUP_ANNUAL_STD_PRC_TOTAL Quantity Recurring Parent Rollup Annual Std Price Total - Order OrderItem AnnualEffectiveTotal__c Price Standard Calculated
Rollup Annual Std Price Total ROLLUP_ANNUAL_STD_PRC_TOTAL Quantity Recurring Rollup Annual Std Price Total Price Standard Calculated

Create Custom Pricing Hook

Here's the sample code for creating a custom pricing hook.

global with sharing class PricingVariableMapHookImplementation implements vlocity_cmt.VlocityOpenInterface
{
    global Boolean invokeMethod (String methodName, Map<String, Object> input,Map<String, Object> output, Map<String, Object> option) {
        System.debug ('debug PricingVariableCalcHook: class called');
        if(methodName == 'calculate.PostInvoke') {
            calculatePostInvoke (input, output);
        }
        return true;
    }

    private void calculatePostInvoke(Map<String, Object> input,Map<String, Object> output) {

    // New code from here
        System.debug('?? initial output map: '+JSON.serialize(output));
        Boolean isRoot = (Boolean)input.get('isRoot');
        System.debug('?? isRoot: '+(Boolean)input.get('isRoot'));
        Map<string, Object> calculatedPricingLogDataMap = (Map<string, Object>)output.get('calculatedPricingLogData');
        Map<String, Object> pricingVariableCodeToValueMap = (Map<String,Object>)output.get('pricingVariableCodeValueMap');

        // Below 2 checks for apply promo scenario for cart API as apply promo is still uses classic flow
        // Should not be required for standard DC API
        if(calculatedPricingLogDataMap == null ) {
            calculatedPricingLogDataMap = (Map<String, Object>)output.get('pricingLogDataMap');
        }

        if(pricingVariableCodeToValueMap == null ) {
            pricingVariableCodeToValueMap = (Map<String, Object>)output.get('pricingVariableMap');
        }

        Decimal lineQuantity = getValueFromPVC(pricingVariableCodeToValueMap, 'LINE_QUANTITY');


        Decimal annualStdPrice = getValueFromPVC(pricingVariableCodeToValueMap,'ANNUAL_CHARGE');
        System.debug('?? annualStdPrice: ' + String.valueOf(annualStdPrice));

        Decimal annualStdPriceDiscPctManual = getValueFromPVC(pricingVariableCodeToValueMap,'ANNUAL_STD_PRC_DISC_PCT_MAN');

        Decimal rollupAnnualStdPriceTotal = getValueFromPVC(pricingVariableCodeToValueMap,'ROLLUP_ANNUAL_STD_PRC_TOTAL');
        System.debug('?? rollupAnnualStdPriceTotal: ' + String.valueOf(rollupAnnualStdPriceTotal));

        //getValueFromPVC(pricingVarCodeValueMap, PricingVariableCode.EFF_REC_MONTH_STD_PRICE_TOTAL);

        Decimal annualStdPriceCalc = annualStdPrice - (annualStdPrice * (annualStdPriceDiscPctManual / 100.0));

        Double annualStdPriceTotal = lineQuantity * (annualStdPriceCalc.doubleValue() + rollupAnnualStdPriceTotal.doubleValue());

        // Should consider rounding of value
        pricingVariableCodeToValueMap.put('ANNUAL_STD_PRC_TOTAL',annualStdPriceTotal);

        pricingVariableCodeToValueMap.put('ANNUAL_STD_PRC_CALC',annualStdPriceCalc);

        calculatedPricingLogDataMap.put('ANNUAL_STD_PRC_TOTAL',
                    new Map<String, Object>{
                        'format'=> '[%s (%s) + Rollup %s (%s)] x %s (%s)',
                        'data'=>new List<String>{'Annual Calculated Price',
                                                  String.valueOf(annualStdPriceCalc),
                                                  'Annual Total',
                                                  String.valueOf(rollupAnnualStdPriceTotal),
                                                 'LINE_QUANTITY',
                                                 String.valueOf(lineQuantity)
                                             }});

        if (isRoot) {
            pricingVariableCodeToValueMap.put('EFF_ANNUAL_STD_PRC_TOTAL',annualStdPriceTotal);
            System.debug('?? annualStdPriceTotal: ' + String.valueOf(annualStdPriceTotal));
        }24

        System.debug('?? final output map'+JSON.serialize(output));
        //logic for line item totals and rollups

        //Set line item level custom fields
        //Getting the cart document instance
        vlocity_cmt.CpqCartDocument cart = (vlocity_cmt.CpqCartDocument)input.get('cartDocument');
        String currentItemId = (String)input.get('itemId');
        System.debug('debug currentItemId:'+currentItemId);
        //method to get all the line items
        Map<String, Object> outputMap = cart.call('getAllItems', new Map<String, Object>{'hierarchyLevel'=>-1});
        Map<String, vlocity_cmt.CpqCartDocumentItem> allItemsMap = (Map<String, vlocity_cmt.CpqCartDocumentItem>) outputMap.get('result');

        //Getting the cartDocumentItem having id as currentItemId
        vlocity_cmt.CpqCartDocumentItem currentItem = allItemsMap.get(currentItemId);

        Map<String,Object> itemArgs = new Map<String, Object>();
        itemArgs.put('annualrecurringcharge', annualStdPrice);
        Double total = 0.0;
        itemArgs.put('annualtotal', annualStdPriceTotal);

        currentItem.call('setItemFields', new Map<String,Object>{'fieldValueMap' => itemArgs});
    }

    private Decimal getValueFromPVC(Map<String, Object> pricingVarCodeValueMap, String pricingVariableCode) {

        Object val = pricingVarCodeValueMap.get(pricingVariableCode);
        if (val == null) {
            pricingVarCodeValueMap.put(pricingVariableCode, 0.0);
        } else {
            pricingVarCodeValueMap.put(pricingVariableCode, val);
        }
        return Decimal.valueOf((Double) pricingVarCodeValueMap.get(pricingVariableCode));
    }
}

Create Interface Implementation Record

First, create the interface. Then, associate the Active Implementation Class from the Interface Implementation Detail related list. The Active Implementation Class field will be automatically populated once the implementation record is created.

  • Interface Name: CpqPricingVariableCalcHook
  • Active Implementation Class: PricingVariableMapHookImplementation

Enable Standard Digital Commerce

You need to enable standard digital commerce. See Enable Standard Digital Commerce.

Create Cart Mapping Entries for Annual Custom Fields

Name Destination Field name Destination SObject Type Source Field Name Source SObject Type Source Field Type
OrderItem>CPQCartDocumentItem101 AnnualCharge CpqCartDocumentItem AnnualCharge OrderItem string
OrderItem>CPQCartDocumentItem102 AnnualCalculatedPrice CpqCartDocumentItem AnnualCalculatedPrice OrderItem string
OrderItem>CPQCartDocumentItem103 AnnualEffectiveTotal CpqCartDocumentItem AnnualEffectiveTotal OrderItem string
OrderItem>CPQCartDocumentItem104 AnnualManualDiscount CpqCartDocumentItem AnnualManualDiscount OrderItem string
OrderItem>CPQCartDocumentItem105 AnnualTotal CpqCartDocumentItem AnnualTotal OrderItem string

Add Custom Fields to Basket

You need to add fields to the basket. See Add Fields to the Basket.