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.