Last active
September 23, 2024 10:40
-
-
Save paustint/40b602503b6cd6ae879af7b85d910da8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* @description Data models for interacting with Steelbrick CPQ | |
* // https://community.steelbrick.com/t5/Developer-Guidebook/Public-API-Technical-Documentation-amp-Code-1/ta-p/5690 | |
* | |
*/ | |
public without sharing class CPQ_ApiDataModels { | |
/** INPUT PAYLOADS */ | |
public class RenewalContext { | |
public Id masterContractId; | |
public Contract[] renewedContracts; | |
} | |
public class ProductLoadContext { | |
public Id pricebookId; | |
public String currencyCode; | |
public ProductLoadContext(){} | |
public ProductLoadContext(Id pricebookId, String currencyCode) { | |
this.pricebookId = pricebookId; | |
this.currencyCode = currencyCode; | |
} | |
} | |
public class SearchContext { | |
public String format; | |
public QuoteModel quote; | |
public SBQQ__SearchFilter__c[] filters; | |
} | |
public class SuggestContext { | |
public String format; | |
public QuoteModel quote; | |
public SBQQ__QuoteProcess__c process; | |
} | |
public class ProductAddContext { | |
public Boolean ignoreCalculate; | |
public QuoteModel quote; | |
public ProductModel[] products; | |
public Integer groupKey; | |
public ProductAddContext(){ | |
products = new List<ProductModel>(); | |
} | |
public ProductAddContext(QuoteModel quote, ProductModel[] products){ | |
this(false, quote, products, null); | |
} | |
public ProductAddContext(Boolean ignoreCalculate, QuoteModel quote, ProductModel[] products) { | |
this(ignoreCalculate, quote, products, null); | |
} | |
public ProductAddContext(Boolean ignoreCalculate, QuoteModel quote, ProductModel[] products, Integer groupKey){ | |
this.ignoreCalculate = ignoreCalculate; | |
this.quote = quote; | |
this.products = products; | |
this.groupKey = groupKey; | |
} | |
} | |
public class CalculatorContext { | |
public QuoteModel quote; | |
public CalculatorContext(){} | |
public CalculatorContext(QuoteModel quote) { | |
this.quote = quote; | |
} | |
} | |
public class ConfigLoadContext { | |
public TinyQuoteModel quote; | |
public TinyProductModel parentProduct; // Only required if the configuration must inherit Configuration Attribute values from its parent. | |
} | |
public class LoadRuleRunnerContext { | |
public TinyQuoteModel quote; | |
public String[] dynamicOptionSkus; | |
public TinyConfigurationModel configuration; | |
public TinyProductModel parentProduct; // Only required if the configuration must inherit Configuration Attributes from the parent. | |
} | |
public class ValidationContext { | |
public TinyQuoteModel quote; | |
public TinyConfigurationModel configuration; | |
public Id upgradedAssetId; | |
public String event; | |
} | |
/** DATA MODELS */ | |
public without sharing class ProductModel { | |
/** | |
* The record that this product model represents. | |
*/ | |
public Product2 record {get; private set;} | |
/** | |
* Provides a source for SBQQ__QuoteLine__c.SBQQ__UpgradedAsset__c | |
*/ | |
public Id upgradedAssetId {get; set;} | |
/** | |
* The symbol for the currency in use | |
*/ | |
public String currencySymbol {get; private set;} | |
/** | |
* The ISO code for the currency in use | |
*/ | |
public String currencyCode {get; private set;} | |
/** | |
* Allows for Product Features to be sorted by category | |
*/ | |
public String[] featureCategories {get; private set;} | |
/** | |
* A list of all available options on this product | |
*/ | |
public OptionModel[] options {get; private set;} | |
/** | |
* All features present on this product | |
*/ | |
public FeatureModel[] features {get; private set;} | |
/** | |
* An object representing this product's current configuration | |
*/ | |
public ConfigurationModel configuration {get; private set;} | |
/** | |
* A list of all configuration attributes available on this product | |
*/ | |
public ConfigAttributeModel[] configurationAttributes {get; private set;} | |
/** | |
* A list of all configuration attributes this product inherits from ancestor products | |
*/ | |
public ConfigAttributeModel[] inheritedConfigurationAttributes {get; private set;} | |
/** | |
* Constraints on this product | |
*/ | |
public ConstraintModel[] constraints; | |
} | |
public class ConstraintModel { | |
public SBQQ__OptionConstraint__c record; | |
public Boolean priorOptionExists; | |
} | |
public class OptionModel { | |
public SBQQ__ProductOption__c record; | |
public Map<String,String> externalConfigurationData; | |
public Boolean configurable; | |
public Boolean configurationRequired; | |
public Boolean quantityEditable; | |
public Boolean priceEditable; | |
public Decimal productQuantityScale; | |
public Boolean priorOptionExists; | |
public Set<Id> dependentIds; | |
public Map<String,Set<Id>> controllingGroups; | |
public Map<String,Set<Id>> exclusionGroups; | |
public String reconfigureDimensionWarning; | |
public Boolean hasDimension; | |
public Boolean isUpgrade; | |
public String dynamicOptionKey; | |
} | |
public class ConfigAttributeModel { | |
public String name; | |
public String targetFieldName; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__TargetField__c | |
public Decimal displayOrder; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__DisplayOrder__c | |
public String columnOrder; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ColumnOrder__c | |
public Boolean required; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Required__c | |
public Id featureId; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Feature__c | |
public String position; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Position__c | |
public Boolean appliedImmediately; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__AppliedImmediately__c | |
public Boolean applyToProductOptions; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ApplyToProductOptions__c | |
public Boolean autoSelect; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__AutoSelect__c | |
public String[] shownValues; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ShownValues__c | |
public String[] hiddenValues; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__HiddenValues__c | |
public Boolean hidden; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Hidden__c | |
public String noSuchFieldName; // If no field with the target name exists, the target name is stored here. | |
public Id myId; // Corresponds directly to SBQQ__ConfigurationAttribute__c.Id | |
} | |
public class FeatureModel { | |
public SBQQ__ProductFeature__c record; | |
public String instructionsText; | |
public Boolean containsUpgrades; | |
} | |
public class ConfigurationModel { | |
public Id configuredProductId; | |
public Id optionId; | |
public SBQQ__ProductOption__c optionData; // Editable data about the option in question, such as quantity or discount | |
public SBQQ__ProductOption__c configurationData; | |
public SBQQ__ProductOption__c inheritedConfigurationData; | |
public ConfigurationModel[] optionConfigurations; | |
public Boolean configured; | |
public Boolean configurationEntered; | |
public Boolean changedByProductActions; | |
public Boolean isDynamicOption; | |
public Boolean isUpgrade; | |
public Set<Id> disabledOptionIds; | |
public Set<Id> hiddenOptionIds; | |
public Decimal listPrice; | |
public Boolean priceEditable; | |
public String[] validationMessages; | |
public String dynamicOptionKey; | |
} | |
public without sharing class QuoteModel { | |
/** | |
* The record represented by this model | |
*/ | |
public SBQQ__Quote__c record; | |
/** | |
* The lines contained in this quote | |
*/ | |
public QuoteLineModel[] lineItems; | |
/** | |
* The groups contained in this quote | |
*/ | |
public QuoteLineGroupModel[] lineItemGroups; | |
/** | |
* The next key that will be used for new groups or lines. | |
* To ensure uniqueness of keys, this value should never be changed to a lower value. | |
*/ | |
public Integer nextKey; | |
/** | |
* Corresponds to the 'magic field', SBQQ__Quote__c.ApplyAdditionalDiscountLast__c | |
*/ | |
public Boolean applyAdditionalDiscountLast; | |
/** | |
* Corresponds to the 'magic field', SBQQ__Quote__c.ApplyPartnerDiscountFirst__c | |
*/ | |
public Boolean applyPartnerDiscountFirst; | |
/** | |
* Corresponds to the 'magic field', SBQQ__Quote__c.ChannelDiscountsOffList__c | |
*/ | |
public Boolean channelDiscountsOffList; | |
/** | |
* SBQQ__Quote__c.SBQQ__CustomerAmount__c is a Roll-up Summary Field, so its accuracy can only be guaranteed | |
* after a quote is persisted. As such, its current value is stored here until then. | |
*/ | |
public Decimal customerTotal; | |
/** | |
* SBQQ__Quote__c.SBQQ__NetAmount__c is a Roll-up Summary Field, so its accuracy can only be guaranteed | |
* after a quote is persisted. As such, its current value is stored here until then. | |
*/ | |
public Decimal netTotal; | |
/** | |
* The Net Total for all non-multidimensional quote lines. | |
*/ | |
public Decimal netNonSegmentTotal; | |
public Boolean calculationRequired; | |
} | |
public without sharing class QuoteLineModel { | |
/** | |
* The record represented by this model. | |
*/ | |
public SBQQ__QuoteLine__c record; | |
/** | |
* Corresponds to the 'magic field', SBQQ__QuoteLine__c.ProrateAmountDiscount__c. | |
*/ | |
public Boolean amountDiscountProrated; | |
/** | |
* The unique key of this line's group, if this line is part of a grouped quote. | |
*/ | |
public Integer parentGroupKey; | |
/** | |
* The unique key of this line's parent, if this line is part of a bundle. | |
*/ | |
public Integer parentItemKey; | |
/** | |
* Each quote line and group has a key that is unique against all other keys on the same quote. | |
*/ | |
public Integer key; | |
/** | |
* True if this line is an MDQ segment that can be uplifted from a previous segment. | |
*/ | |
public Boolean upliftable; | |
/** | |
* Indicates the configuration type of the product this line represents. | |
*/ | |
public String configurationType; | |
/** | |
* Indicates the configuration event of the product this line represents. | |
*/ | |
public String configurationEvent; | |
/** | |
* If true, this line cannot be reconfigured. | |
*/ | |
public Boolean reconfigurationDisabled; | |
/** | |
* If true, this line's description cannot be changed. | |
*/ | |
public Boolean descriptionLocked; | |
/** | |
* If true, this line's quantity cannot be changed. | |
*/ | |
public Boolean productQuantityEditable; | |
/** | |
* The number of decimal places to which this line's quantity shall be rounded. | |
*/ | |
public Decimal productQuantityScale; | |
/** | |
* The type of MDQ dimension this line represents. | |
*/ | |
public String dimensionType; | |
/** | |
* If true, the underlying product can be represented as a Multi-dimensional line. | |
*/ | |
public Boolean productHasDimensions; | |
/** | |
* The unit price towards which this quote line will be discounted. | |
*/ | |
public Decimal targetCustomerAmount; | |
/** | |
* The customer amount towards which this quote line will be discounted. | |
*/ | |
public Decimal targetCustomerTotal; | |
/** | |
* The net total towards which this quote line will be discounted. | |
*/ | |
} | |
public without sharing class QuoteLineGroupModel { | |
/** | |
* The record represented by this model. | |
*/ | |
public SBQQ__QuoteLineGroup__c record; | |
/** | |
* The Net Total for all non-multidimensional quote lines. | |
*/ | |
public Decimal netNonSegmentTotal; | |
/** | |
* Each quote line and group has a key that is unique against all other keys on the same quote. | |
*/ | |
public Integer key; | |
} | |
// ============ TINY MODEL CLASSES ========= | |
// Use these with config API's | |
// These are referenced in the docs here: https://community.steelbrick.com/t5/Developer-Guidebook/Public-API-Technical-Documentation-amp-Code-2/ta-p/5691 | |
// They should probably be refactored to use full models (even if some values are null) instead of keeping multiple versions | |
public class TinyProductModel { | |
public Product2 record; | |
public String currencyCode; | |
public TinyOptionModel[] options; | |
public TinyFeatureModel[] features; | |
public TinyConfigurationModel configuration; | |
public TinyConfigAttributeModel[] configurationAttributes; | |
public TinyConfigAttributeModel[] inheritedConfigurationAttributes; | |
public TinyConstraintModel[] constraints; | |
} | |
public class TinyConstraintModel { | |
public SBQQ__OptionConstraint__c record; | |
public Boolean priorOptionExists; | |
} | |
public class TinyOptionModel { | |
public SBQQ__ProductOption__c record; | |
public Map<String,String> externalConfigurationData; | |
public Boolean configurable; | |
public Boolean configurationRequired; | |
public Boolean quantityEditable; | |
public Boolean priceEditable; | |
public Decimal productQuantityScale; | |
public Boolean priorOptionExists; | |
public Set<Id> dependentIds; | |
public Map<String,Set<Id>> controllingGroups; | |
public Map<String,Set<Id>> exclusionGroups; | |
public String reconfigureDimensionWarning; | |
public Boolean hasDimension; | |
public Boolean isUpgrade; | |
public String dynamicOptionKey; | |
} | |
public class TinyConfigAttributeModel { | |
public String name; | |
public String targetFieldName; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__TargetField__c | |
public Decimal displayOrder; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__DisplayOrder__c | |
public String columnOrder; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ColumnOrder__c | |
public Boolean required; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Required__c | |
public Id featureId; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Feature__c | |
public String position; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Position__c | |
public Boolean appliedImmediately; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__AppliedImmediately__c | |
public Boolean applyToProductOptions; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ApplyToProductOptions__c | |
public Boolean autoSelect; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__AutoSelect__c | |
public String[] shownValues; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ShownValues__c | |
public String[] hiddenValues; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__HiddenValues__c | |
public Boolean hidden; // Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Hidden__c | |
public String noSuchFieldName; // If no field with the target name exists, the target name is stored here. | |
public Id myId; // Corresponds directly to SBQQ__ConfigurationAttribute__c.Id | |
} | |
public class TinyFeatureModel { | |
public SBQQ__ProductFeature__c record; | |
public String instructionsText; | |
public Boolean containsUpgrades; | |
} | |
public class TinyConfigurationModel { | |
public Id configuredProductId; | |
public Id optionId; | |
public SBQQ__ProductOption__c optionData; // Editable data about the option in question, such as quantity or discount | |
public SBQQ__ProductOption__c configurationData; | |
public SBQQ__ProductOption__c inheritedConfigurationData; | |
public TinyConfigurationModel[] optionConfigurations; | |
public Boolean configured; | |
public Boolean changedByProductActions; | |
public Boolean isDynamicOption; | |
public Boolean isUpgrade; | |
public Set<Id> disabledOptionIds; | |
public Set<Id> hiddenOptionIds; | |
public Decimal listPrice; | |
public Boolean priceEditable; | |
public String[] validationMessages; | |
public String dynamicOptionKey; | |
} | |
public class TinyQuoteModel { | |
public SBQQ__Quote__c record; | |
public TinyQuoteLineModel[] lineItems; | |
public TinyQuoteLineGroupModel[] lineItemGroups; | |
public Integer nextKey; | |
public Boolean applyAdditionalDiscountLast; | |
public Boolean applyPartnerDiscountFirst; | |
public Boolean channelDiscountsOffList; | |
public Decimal customerTotal; | |
public Decimal netTotal; | |
public Decimal netNonSegmentTotal; | |
} | |
public class TinyQuoteLineModel { | |
public SBQQ__QuoteLine__c record; | |
public Decimal renewalPrice; | |
public Boolean amountDiscountProrated; | |
public Integer parentGroupKey; | |
public Integer parentItemKey; | |
public Integer key; | |
public Boolean upliftable; | |
public String configurationType; | |
public String configurationEvent; | |
public Boolean reconfigurationDisabled; | |
public Boolean descriptionLocked; | |
public Boolean productQuantityEditable; | |
public Decimal productQuantityScale; | |
public String dimensionType; | |
public Boolean productHasDimensions; | |
public Decimal targetCustomerAmount; | |
public Decimal targetCustomerTotal; | |
} | |
public class TinyQuoteLineGroupModel { | |
public SBQQ__QuoteLineGroup__c record; | |
public Decimal netNonSegmentTotal; | |
public Integer key; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This test class is only testing for code coverage. Functionality does not require testing for this wrapper class. | |
* | |
*/ | |
@isTest | |
private class CPQ_ApiDataModelsTest { | |
@isTest static void testProductLoadContext() { | |
CPQ_ApiDataModels.ProductLoadContext loadContext = new CPQ_ApiDataModels.ProductLoadContext(); | |
System.assertEquals(loadContext.pricebookId, null); | |
System.assertEquals(loadContext.currencyCode, null); | |
Id pricebookId = Test.getStandardPricebookId(); | |
String currencyCode = 'USD'; | |
CPQ_ApiDataModels.ProductLoadContext loadContextWithPricebookAndCurrency = new CPQ_ApiDataModels.ProductLoadContext(pricebookId, currencyCode); | |
System.assertEquals(loadContextWithPricebookAndCurrency.pricebookId, pricebookId); | |
System.assertEquals(loadContextWithPricebookAndCurrency.currencyCode, currencyCode); | |
} | |
@isTest static void testProductAddContext() { | |
CPQ_ApiDataModels.ProductAddContext addContextDefault = new CPQ_ApiDataModels.ProductAddContext(); | |
System.assertEquals(addContextDefault.quote, null); | |
System.assertEquals(addContextDefault.products, new List<CPQ_ApiDataModels.ProductModel>()); | |
System.assertEquals(addContextDefault.groupKey, null); | |
CPQ_ApiDataModels.QuoteModel quote = new CPQ_ApiDataModels.QuoteModel(); | |
List<CPQ_ApiDataModels.ProductModel> products = new List<CPQ_ApiDataModels.ProductModel>(); | |
CPQ_ApiDataModels.ProductAddContext addContextquoteProducts = new CPQ_ApiDataModels.ProductAddContext(quote, products); | |
System.assertEquals(addContextquoteProducts.quote, quote); | |
System.assertEquals(addContextquoteProducts.products, products); | |
System.assertEquals(addContextquoteProducts.groupKey, null); | |
CPQ_ApiDataModels.ProductAddContext addContextquoteProductsIgnoreCalculate = new CPQ_ApiDataModels.ProductAddContext(true, quote, products); | |
System.assertEquals(addContextquoteProductsIgnoreCalculate.quote, quote); | |
System.assertEquals(addContextquoteProductsIgnoreCalculate.products, products); | |
System.assertEquals(addContextquoteProductsIgnoreCalculate.groupKey, null); | |
System.assertEquals(addContextquoteProductsIgnoreCalculate.ignoreCalculate, true); | |
CPQ_ApiDataModels.ProductAddContext addContextquoteProductsIgnoreCalculateWithGroup = | |
new CPQ_ApiDataModels.ProductAddContext(true, quote, products, 1); | |
System.assertEquals(addContextquoteProductsIgnoreCalculateWithGroup.quote, quote); | |
System.assertEquals(addContextquoteProductsIgnoreCalculateWithGroup.products, products); | |
System.assertEquals(addContextquoteProductsIgnoreCalculateWithGroup.groupKey, 1); | |
System.assertEquals(addContextquoteProductsIgnoreCalculateWithGroup.ignoreCalculate, true); | |
} | |
@isTest static void testCalculatorContext() { | |
CPQ_ApiDataModels.CalculatorContext calcContext = new CPQ_ApiDataModels.CalculatorContext(); | |
System.assertEquals(calcContext.quote, null); | |
CPQ_ApiDataModels.QuoteModel quote = new CPQ_ApiDataModels.QuoteModel(); | |
CPQ_ApiDataModels.CalculatorContext calcContextWithQuote = new CPQ_ApiDataModels.CalculatorContext(quote); | |
System.assertEquals(calcContextWithQuote.quote, quote); | |
} | |
@isTest static void testProductModel() { | |
String productModelJson = '{' + | |
'"record": {' + | |
'"attributes": {' + | |
'"type": "Product2",' + | |
'"url": "/services/data/v42.0/sobjects/Product2/01t0q000000gaO9AAI"' + | |
'},' + | |
'"Id": "01t0q000000gaO9AAI",' + | |
'"CurrencyIsoCode": "USD",' + | |
'"Name": "API - Overage",' + | |
'"ProductCode": "API - Overage",' + | |
'"Description": "atg",' + | |
'"SBQQ__SubscriptionPricing__c": "Fixed Price",' + | |
'"SBQQ__PriceEditable__c": false,' + | |
'"SBQQ__DefaultQuantity__c": 1.00000,' + | |
'"SBQQ__QuantityEditable__c": true,' + | |
'"SBQQ__CostEditable__c": false,' + | |
'"SBQQ__NonDiscountable__c": false,' + | |
'"SBQQ__NonPartnerDiscountable__c": false,' + | |
'"SBQQ__SubscriptionTerm__c": 1,' + | |
'"SBQQ__PricingMethod__c": "List",' + | |
'"SBQQ__PricingMethodEditable__c": true,' + | |
'"SBQQ__OptionSelectionMethod__c": "Click",' + | |
'"SBQQ__Optional__c": false,' + | |
'"SBQQ__Taxable__c": false,' + | |
'"SBQQ__CustomConfigurationRequired__c": false,' + | |
'"SBQQ__Hidden__c": false,' + | |
'"SBQQ__ReconfigurationDisabled__c": false,' + | |
'"SBQQ__ExcludeFromOpportunity__c": true,' + | |
'"SBQQ__DescriptionLocked__c": false,' + | |
'"SBQQ__ExcludeFromMaintenance__c": false,' + | |
'"SBQQ__IncludeInMaintenance__c": false,' + | |
'"SBQQ__AllocatePotOnOrders__c": false,' + | |
'"SBQQ__NewQuoteGroup__c": false,' + | |
'"SBQQ__SubscriptionType__c": "Renewable",' + | |
'"SBQQ__HasConfigurationAttributes__c": false,' + | |
'"SBQQ__ExternallyConfigurable__c": false,' + | |
'"SBQQ__BillingFrequency__c": "Monthly",' + | |
'"SBQQ__ChargeType__c": "Usage",' + | |
'"PricebookEntries": {' + | |
'"totalSize": 1,' + | |
'"done": true,' + | |
'"records": [' + | |
'{' + | |
'"attributes": {' + | |
'"type": "PricebookEntry",' + | |
'"url": "/services/data/v42.0/sobjects/PricebookEntry/01u0q000001jwBjAAI"' + | |
'},' + | |
'"Product2Id": "01t0q000000gaO9AAI",' + | |
'"Id": "01u0q000001jwBjAAI",' + | |
'"Pricebook2Id": "01s0q000000CbjqAAC",' + | |
'"UnitPrice": 0.08,' + | |
'"IsActive": true,' + | |
'"CurrencyIsoCode": "USD"' + | |
'}' + | |
']' + | |
'}' + | |
'},' + | |
'"options": [],' + | |
'"features": [],' + | |
'"featureCategoryLabels": {' + | |
'"Reporting": "Reporting",' + | |
'"Implementation": "Implementation",' + | |
'"Software": "Software",' + | |
'"Hardware": "Hardware"' + | |
'},' + | |
'"featureCategories": [],' + | |
'"currencySymbol": "USD",' + | |
'"currencyCode": "USD",' + | |
'"constraints": [],' + | |
'"configurationAttributes": []' + | |
'}'; | |
CPQ_ApiDataModels.ProductModel productModel = (CPQ_ApiDataModels.ProductModel) JSON.deserialize(productModelJson, CPQ_ApiDataModels.ProductModel.class); | |
System.assertEquals(productModel.record.Name, 'API - Overage'); | |
System.assertEquals(productModel.upgradedAssetId, null); | |
System.assertEquals(productModel.currencySymbol, 'USD'); | |
System.assertEquals(productModel.currencyCode, 'USD'); | |
System.assertEquals(productModel.featureCategories, new String[]{}); | |
System.assertEquals(productModel.options, new CPQ_ApiDataModels.OptionModel[]{}); | |
System.assertEquals(productModel.features, new CPQ_ApiDataModels.FeatureModel[]{}); | |
System.assertEquals(productModel.configuration, null); | |
System.assertEquals(productModel.configurationAttributes, new CPQ_ApiDataModels.ConfigAttributeModel[]{}); | |
System.assertEquals(productModel.inheritedConfigurationAttributes, null); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* | |
* @description This class wraps the Salesforce CPQ API to allow | |
* easier interaction and to demonstrate how to call various methods | |
* EXAMPLE INVOKING: | |
* Contract contract = [SELECT Id FROM Contract WHERE Id = '800f4000000DL11' LIMIT 1]; | |
* CPQ_ApiWrapper.renewContract(contract); | |
* To Manually call the CPQ API via REST, the convention is as follows: | |
* GET /services/apexrest/SBQQ/ServiceRouter/read?reader=SBQQ.QuoteAPI.QuoteReader&uid=a0nf4000000W4vs | |
* | |
*/ | |
public without sharing class CPQ_ApiWrapper { | |
public static Boolean debug = true; | |
/** CPQ API METHODS */ | |
public static final String CONTRACT_RENEWER = 'SBQQ.ContractManipulationAPI.ContractRenewer'; | |
public static final String CONTRACT_AMENDER = 'SBQQ.ContractManipulationAPI.ContractAmender'; | |
public static final String CONFIG_LOADER = 'SBQQ.ConfigAPI.ConfigLoader'; | |
public static final String LOAD_RULE_EXECUTOR = 'SBQQ.ConfigAPI.LoadRuleExecutor'; | |
public static final String CONFIGURATION_VALIDATOR = 'SBQQ.ConfigAPI.ConfigurationValidator'; | |
public static final String PRODUCT_LOADER = 'SBQQ.ProductAPI.ProductLoader'; | |
public static final String PRODUCT_SUGGESTER = 'SBQQ.ProductAPI.ProductSuggester'; | |
public static final String PRODUCT_SEARCHER = 'SBQQ.ProductAPI.ProductSearcher'; | |
public static final String QUOTE_READER = 'SBQQ.QuoteAPI.QuoteReader'; | |
public static final String QUOTE_PRODUCT_ADDER = 'SBQQ.QuoteAPI.QuoteProductAdder'; | |
public static final String QUOTE_CALCULATOR = 'SBQQ.QuoteAPI.QuoteCalculator'; | |
public static final String QUOTE_SAVER = 'SBQQ.QuoteAPI.QuoteSaver'; | |
/** Mini Wrapper around SBQQ API METHODS */ | |
private static String read(String name, String uid) { | |
return SBQQ.ServiceRouter.read(name, uid); | |
} | |
private static String load(String name, String uid, Object payload) { | |
return loadStr(name, uid, JSON.serialize(payload)); | |
} | |
private static String loadStr(String name, String uid, String payloadJson) { | |
return SBQQ.ServiceRouter.load(name, uid, payloadJson); | |
} | |
private static String save(String name, Object model) { | |
return saveStr(name, JSON.serialize(model)); | |
} | |
private static String saveStr(String name, String modelJson) { | |
return SBQQ.ServiceRouter.save(name, modelJson); | |
} | |
// Will need to add unit tests for these if uncommented | |
//public static List<CPQ_ApiDataModels.QuoteModel> renewContract(Contract contract) { | |
// return renewContract(contract.Id, new List<Contract>{contract}); | |
//} | |
//public static List<CPQ_ApiDataModels.QuoteModel> renewContract(Id contractId, List<Contract> contracts) { | |
// CPQ_ApiDataModels.RenewalContext payload = new CPQ_ApiDataModels.RenewalContext(); | |
// payload.renewedContracts = contracts; | |
// String jsonResult = load(CONTRACT_RENEWER, (String) contractId, payload); | |
// if(debug) { | |
// System.debug(LoggingLevel.WARN, 'jsonResult: ' + jsonResult); | |
// } | |
// List<CPQ_ApiDataModels.QuoteModel> quoteModel = (List<CPQ_ApiDataModels.QuoteModel>) JSON.deserialize(jsonResult, LIST<CPQ_ApiDataModels.QuoteModel>.class); | |
// if(debug) { | |
// System.debug(LoggingLevel.WARN, 'jsonResult: ' + jsonResult); | |
// System.debug(LoggingLevel.WARN, 'quoteModel: ' + quoteModel); | |
// } | |
// return quoteModel; | |
//} | |
//public static CPQ_ApiDataModels.QuoteModel amendContract(Id contractId) { | |
// System.debug(LoggingLevel.WARN, 'amending'); | |
// String jsonResult = load(CONTRACT_AMENDER, (String) contractId, null); | |
// System.debug(LoggingLevel.WARN, 'amended ' + jsonResult); | |
// CPQ_ApiDataModels.QuoteModel quoteModel = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(jsonResult, CPQ_ApiDataModels.QuoteModel.class); | |
// System.debug(LoggingLevel.WARN, 'quoteModel >>> ' + quoteModel); | |
//if(debug) { | |
// System.debug(LoggingLevel.WARN, 'jsonResult: ' + jsonResult); | |
// System.debug(LoggingLevel.WARN, 'quoteModel: ' + quoteModel); | |
//} | |
//return quoteModel; | |
//} | |
/** | |
* ******* QUOTE API EXAMPLES ******** | |
*/ | |
public static CPQ_ApiDataModels.QuoteModel getQuoteModel(Id quoteId) { | |
String jsonResult = read(QUOTE_READER, (String) quoteId); | |
CPQ_ApiDataModels.QuoteModel quoteModel = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(jsonResult, CPQ_ApiDataModels.QuoteModel.class); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'jsonResult: ' + jsonResult); | |
System.debug(LoggingLevel.WARN, 'quoteModel: ' + quoteModel); | |
} | |
return quoteModel; | |
} | |
public static CPQ_ApiDataModels.ProductModel loadProduct(Id productId, Id pricebookId, String currencyCode) { | |
CPQ_ApiDataModels.ProductLoadContext productLoadPayload = new CPQ_ApiDataModels.ProductLoadContext(pricebookId, currencyCode); | |
String jsonResultProduct = load(PRODUCT_LOADER, (String) productId, productLoadPayload); | |
CPQ_ApiDataModels.ProductModel productModel = (CPQ_ApiDataModels.ProductModel) JSON.deserialize(jsonResultProduct, CPQ_ApiDataModels.ProductModel.class); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'jsonResultProduct: ' + jsonResultProduct); | |
System.debug(LoggingLevel.WARN, 'productModel: ' + productModel); | |
} | |
return productModel; | |
} | |
public static CPQ_ApiDataModels.ProductModel setOptionsConfigured(CPQ_ApiDataModels.ProductModel productModel) { | |
if(productModel.configuration != null){ | |
productModel.configuration.configured = true; | |
productModel.configuration.configurationEntered = true; | |
for(CPQ_ApiDataModels.ConfigurationModel configModel : productModel.configuration.optionConfigurations) { | |
configModel.configured = true; | |
configModel.configurationEntered = true; | |
} | |
return productModel; | |
}else{return productModel;} | |
} | |
public static CPQ_ApiDataModels.QuoteModel addProductsToQuote(Id quoteId, Id productId, Id pricebookId, String currencyCode) { | |
return addProductsToQuote(quoteId, pricebookId, productId, currencyCode, false); | |
} | |
public static CPQ_ApiDataModels.QuoteModel addProductsToQuote(Id quoteId, Id productId, Id pricebookId, String currencyCode, Boolean skipCalculate) { | |
CPQ_ApiDataModels.ProductModel productModel = loadProduct(productId, pricebookId, currencyCode); | |
// Set product model as configured and configurationEntered | |
productModel = setOptionsConfigured(productModel); | |
String jsonResultQuote = read(QUOTE_READER, (String) quoteId); | |
CPQ_ApiDataModels.QuoteModel initialQuoteModel = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(jsonResultQuote, CPQ_ApiDataModels.QuoteModel.class); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'jsonResultQuote: ' + jsonResultQuote); | |
System.debug(LoggingLevel.WARN, 'initialQuoteModel: ' + initialQuoteModel); | |
} | |
CPQ_ApiDataModels.ProductAddContext productAddPayload = new CPQ_ApiDataModels.ProductAddContext(skipCalculate, initialQuoteModel, new List<CPQ_ApiDataModels.ProductModel>{productModel}); | |
return addProductsToQuote(productAddPayload); | |
} | |
public static CPQ_ApiDataModels.QuoteModel addProductsToQuote(CPQ_ApiDataModels.ProductAddContext productAddPayload) { | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'productAddPayloadJSON: ' + JSON.serialize(productAddPayload)); | |
} | |
String updatedQuoteJSON = load(QUOTE_PRODUCT_ADDER, null, productAddPayload); | |
CPQ_ApiDataModels.QuoteModel updatedQuoteModel = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(updatedQuoteJSON, CPQ_ApiDataModels.QuoteModel.class); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'updatedQuoteJSON: ' + updatedQuoteJSON); | |
System.debug(LoggingLevel.WARN, 'updatedQuoteModel: ' + updatedQuoteModel); | |
} | |
return updatedQuoteModel; | |
} | |
public static CPQ_ApiDataModels.QuoteModel calculateQuote(CPQ_ApiDataModels.QuoteModel quoteModel) { | |
CPQ_ApiDataModels.CalculatorContext calculatorPayload = new CPQ_ApiDataModels.CalculatorContext(quoteModel); | |
String updatedQuoteJSON = load(QUOTE_CALCULATOR, null, calculatorPayload); | |
CPQ_ApiDataModels.QuoteModel updatedQuoteModel = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(updatedQuoteJSON, CPQ_ApiDataModels.QuoteModel.class); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'updatedQuoteJSON: ' + updatedQuoteJSON); | |
System.debug(LoggingLevel.WARN, 'updatedQuoteModel: ' + updatedQuoteModel); | |
} | |
return updatedQuoteModel; | |
} | |
public static CPQ_ApiDataModels.QuoteModel saveQuote(CPQ_ApiDataModels.QuoteModel quoteModel) { | |
String savedQuoteJSON = save(QUOTE_SAVER, quoteModel); | |
CPQ_ApiDataModels.QuoteModel updatedQuoteModel = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(savedQuoteJSON, CPQ_ApiDataModels.QuoteModel.class); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'updatedQuoteModel: ' + updatedQuoteModel); | |
} | |
return updatedQuoteModel; | |
} | |
public static CPQ_ApiDataModels.QuoteModel calculateAndSaveQuote(CPQ_ApiDataModels.QuoteModel quoteModel) { | |
//String calculatedQuoteJSON = SBQQ.QuoteLineEditorController.calculateQuote2(quoteModel.record.Id, JSON.serialize(quoteModel)); | |
// Attempt to get around uncomitted changes by saving first | |
String savedQuoteJSON = saveStr(QUOTE_SAVER, JSON.serialize(quoteModel)); | |
//String calculatedQuoteJSON = SBQQ.QuoteLineEditorController.calculateQuote2(quoteModel.record.Id, JSON.serialize(savedQuoteJSON)); | |
//savedQuoteJSON = saveStr(QUOTE_SAVER, calculatedQuoteJSON); | |
CPQ_ApiDataModels.QuoteModel savedQuote = (CPQ_ApiDataModels.QuoteModel) JSON.deserialize(savedQuoteJSON, CPQ_ApiDataModels.QuoteModel.class); | |
//String calculatedQuoteJSON = SBQQ.QuoteLineEditorController.calculateQuote2(quoteModel.record.Id, JSON.serialize(savedQuoteJSON)); | |
if(debug) { | |
//System.debug(LoggingLevel.WARN, 'calculatedQuoteJSON: ' + calculatedQuoteJSON); | |
System.debug(LoggingLevel.WARN, 'savedQuoteJSON: ' + savedQuoteJSON); | |
//System.debug(LoggingLevel.WARN, 'savedQuote: ' + savedQuote); | |
} | |
return savedQuote; | |
} | |
//public static void configureBundle(CPQ_ApiDataModels.QuoteModel quoteModel, Id productId) { | |
// //CPQ_ApiDataModels.ConfigLoadContext context = new CPQ_ApiDataModels.ConfigLoadContext(); | |
// // Using alt payload to avoid requiring tiny quoteModel since it is basically the same - could modify contrstructor to convert | |
// CPQ_ApiDataModels.TinyQuoteModel tinyQuoteModel = new CPQ_ApiDataModels.TinyQuoteModel(); | |
// tinyQuoteModel.record = quoteModel.record; | |
// CPQ_ApiDataModels.ConfigLoadContext context = new CPQ_ApiDataModels.ConfigLoadContext(); | |
// context.quote = tinyQuoteModel; | |
// System.debug(LoggingLevel.WARN, JSON.serialize(context)); | |
// String configLoaderJSON = load(CONFIG_LOADER, (String) productId, JSON.serialize(context)); | |
// if(debug) { | |
// System.debug(LoggingLevel.WARN, 'configLoaderJSON: ' + configLoaderJSON); | |
// } | |
//} | |
/** | |
* Force a re-calculation of provided quote | |
* @param quoteId [description] | |
* @return [description] | |
*/ | |
public static CPQ_ApiDataModels.QuoteModel calculateQuote(String quoteId) { | |
CPQ_ApiDataModels.QuoteModel initialQuoteModel = getQuoteModel(quoteId); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'initialQuoteModel: ' + initialQuoteModel); | |
} | |
CPQ_ApiDataModels.QuoteModel calculatedQuoteModel = calculateQuote(initialQuoteModel); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'calculatedQuoteModel: ' + calculatedQuoteModel); | |
} | |
CPQ_ApiDataModels.QuoteModel savedQuoteModel = saveQuote(calculatedQuoteModel); | |
if(debug) { | |
System.debug(LoggingLevel.WARN, 'savedQuoteModel: ' + savedQuoteModel); | |
} | |
return savedQuoteModel; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This test class is only testing for code coverage. Functionality does not require testing for this wrapper class. | |
* | |
*/ | |
@IsTest | |
public class CPQ_ApiWrapperTest { | |
@TestSetup | |
static void setup() { | |
Test.startTest(); | |
CPQ_TestUtils.TestData testData = new CPQ_TestUtils.TestData(); | |
Test.stopTest(); | |
testData.createQuoteData(); | |
} | |
@IsTest | |
public static void testGetQuoteModel() { | |
SBQQ__Quote__c quote = [Select Id FROM SBQQ__Quote__c LIMIT 1]; | |
CPQ_ApiDataModels.QuoteModel quoteModel = CPQ_ApiWrapper.getQuoteModel(quote.Id); | |
} | |
@IsTest | |
public static void testLoadProduct() { | |
Product2 product = CPQ_TestUtils.createProduct(); | |
CPQ_ApiDataModels.ProductModel productModel = CPQ_ApiWrapper.loadProduct(product.Id, Test.getStandardPricebookId(), 'USD'); | |
} | |
@IsTest | |
public static void testAddProductToQuoteAndSaveQuote() { | |
Test.startTest(); | |
SBQQ__Quote__c quote = [Select Id FROM SBQQ__Quote__c LIMIT 1]; | |
Product2 product = [Select Id FROM Product2 LIMIT 1]; | |
CPQ_ApiDataModels.QuoteModel quoteModel = CPQ_ApiWrapper.addProductsToQuote(quote.Id, product.Id, Test.getStandardPricebookId(), 'USD', true); | |
CPQ_ApiWrapper.saveQuote(quoteModel); | |
CPQ_ApiWrapper.calculateQuote(quoteModel); | |
CPQ_ApiWrapper.calculateQuote(quote.Id); | |
CPQ_ApiWrapper.calculateAndSaveQuote(quoteModel); | |
Test.stopTest(); | |
} | |
/** Tests for the various calculator methods available */ | |
//@IsTest | |
//public static void testCalculate() { | |
// //CPQ_TestUtils.TestData testData = new CPQ_TestUtils.TestData(); | |
// //CPQ_ApiDataModels.QuoteModel quoteModel = CPQ_ApiWrapper.getQuoteModel(testData.quote.Id); | |
// //// Theses make a callout and will fail, wrapping in try/catch just for code coverage | |
// //// because it is SBQQ code, not our code and we have functionally tested everything needed | |
// //Test.startTest(); | |
// //try { CPQ_ApiWrapper.calculateQuote(quoteModel); } catch(Exception ex) { } | |
// //Test.stopTest(); | |
//} | |
//@IsTest | |
//public static void testCalculate1() { | |
// //CPQ_TestUtils.TestData testData = new CPQ_TestUtils.TestData(); | |
// //// Theses make a callout and will fail, wrapping in try/catch just for code coverage | |
// //// because it is SBQQ code, not our code and we have functionally tested everything needed | |
// //Test.startTest(); | |
// //try { CPQ_ApiWrapper.calculateQuote(testData.quote.Id); } catch(Exception ex) { } | |
// //Test.stopTest(); | |
//} | |
//@IsTest | |
//public static void testCalculate2() { | |
// //CPQ_TestUtils.TestData testData = new CPQ_TestUtils.TestData(); | |
// //CPQ_ApiDataModels.QuoteModel quoteModel = CPQ_ApiWrapper.getQuoteModel(testData.quote.Id); | |
// //// Theses make a callout and will fail, wrapping in try/catch just for code coverage | |
// //// because it is SBQQ code, not our code and we have functionally tested everything needed | |
// //Test.startTest(); | |
// //try { CPQ_ApiWrapper.calculateAndSaveQuote(quoteModel); } catch(Exception ex) { } | |
// //Test.stopTest(); | |
//} | |
//@IsTest | |
//public static void testCalculate3() { | |
// //CPQ_TestUtils.TestData testData = new CPQ_TestUtils.TestData(); | |
// //// Theses make a callout and will fail, wrapping in try/catch just for code coverage | |
// //// because it is SBQQ code, not our code and we have functionally tested everything needed | |
// //Test.startTest(); | |
// //try { CPQ_ApiWrapper.calculateQuote2(testData.quote.Id); } catch(Exception ex) { } | |
// //Test.stopTest(); | |
//} | |
//@IsTest | |
//public static void testCalculate4() { | |
// //CPQ_TestUtils.TestData testData = new CPQ_TestUtils.TestData(); | |
// ////CPQ_ApiDataModels.QuoteModel quoteModel = CPQ_ApiWrapper.getQuoteModel(testData.quote.Id); | |
// //// Theses make a callout and will fail, wrapping in try/catch just for code coverage | |
// //// because it is SBQQ code, not our code and we have functionally tested everything needed | |
// //Test.startTest(); | |
// //try { CPQ_ApiWrapper.calculateQuote2(quoteModel); } catch(Exception ex) { } | |
// //Test.stopTest(); | |
//} | |
//@IsTest | |
//public static void testAmendContract() { | |
// CPQ_ApiWrapper.debug = true; | |
// Test.startTest(); | |
// Opportunity opp = [SELECT Id, SBQQ__Contracted__c FROM Opportunity LIMIT 1]; | |
// opp.SBQQ__Contracted__c = true; | |
// update opp; | |
// Test.stopTest(); | |
// Contract contract = [SELECT Id FROM Contract LIMIT 1]; | |
// contract.Status = 'Activated'; | |
// update contract; | |
// CPQ_ApiDataModels.QuoteModel quoteModel = CPQ_ApiWrapper.amendContract(contract.Id); | |
// List<SBQQ__Quote__c> quotes = [SELECT Id FROM SBQQ__Quote__c]; | |
// System.assertNotEquals(null, quoteModel); | |
// System.assertEquals(2, quotes.size()); | |
//} | |
} |
Hello @gvpatel,
thank you for your sample script to add a bundle product, I'd have a request: would you also have a sample script to modify the configuration (i.e. add/remove an option) of an existing quote item?
Thanks in advace for your attention!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, I am able to succesfully create new quoteline with this code but the new line is only visible when I refresh the quote line editor. The idea is to automatically add a product(Maintenance) based on the other products in the quote. I could'nt use product rule because I need this product to be added several times in the same quote which is not possible with product rules.
Could you please help? Thanks in advance!
//Get Quote as JSON formatted String
String QuoteId = 'aGaJX000000oFyL0AU';
String productId = '01t200000057ROwAAM';
String pricebookId = '01s3X000007Fj4uQAC';
String currencycode = 'EUR';
String quoteJSON = SBQQ.ServiceRouter.read('SBQQ.QuoteAPI.QuoteReader', QuoteId);
system.debug('QuoteMOdel' +quoteJSON);
//Get Product info as JSON formatted String representing the product to be added to the quote
String productModel = SBQQ.ServiceRouter.load('SBQQ.ProductAPI.ProductLoader',
system.debug('ProductModel'+productModel);
//Now, add the product added to to the Quote
String updatedQuoteModel = SBQQ.ServiceRouter.load('SBQQ.QuoteAPI.QuoteProductAdder', null,
system.debug('Quoteline is inserted');
System.debug('Updated QuoteModel'+updatedQuoteModel);
//Save quote and lines
String savedQuoteModel = SBQQ.ServiceRouter.save('SBQQ.QuoteAPI.QuoteSaver', updatedQuoteModel);
system.debug('Saved Quote Model' +savedQuoteModel);