diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 6dcf66f2..01fa7f1d 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -11,6 +11,9 @@ on: schedule: - cron: "0 4 * * 5" # every Friday 04:00 +env: + CPQ: ${{ vars.CPQ }} + jobs: default: runs-on: ubuntu-latest diff --git a/config/project-scratch-def.json b/config/project-scratch-def.json index 12352af6..426bddfa 100644 --- a/config/project-scratch-def.json +++ b/config/project-scratch-def.json @@ -2,5 +2,9 @@ "orgName": "Browserforce", "edition": "Developer", "language": "en_US", - "features": ["DeferSharingCalc", "HighVelocitySales"] + "features": [ + "CPQ", + "DeferSharingCalc", + "HighVelocitySales" + ] } diff --git a/package.json b/package.json index ed5301d3..fab543e4 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "generate:plugin": "npx hygen plugin new", "prepack": "yarn build", "prepare": "yarn build", - "test": "tsc -p test && nyc --reporter=lcov --reporter=text mocha --require ts-node/register \"test/**/*.test.ts\" \"src/**/*.test.ts\"", - "test:e2e": "tsc -p test && mocha --require ts-node/register --slow 30s --timeout 2m --file test/e2e-setup.ts \"test/**/*.e2e-spec.ts\" \"src/**/*.e2e-spec.ts\"" + "test": "tsc --resolveJsonModule -p test && nyc --reporter=lcov --reporter=text mocha --require ts-node/register \"test/**/*.test.ts\" \"src/**/*.test.ts\"", + "test:e2e": "tsc --resolveJsonModule -p test && mocha --require ts-node/register --slow 30s --timeout 2m --file test/e2e-setup.ts \"test/**/*.e2e-spec.ts\" \"src/**/*.e2e-spec.ts\"" } } diff --git a/scripts/develop.sh b/scripts/develop.sh index 046aa415..e2613690 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -39,6 +39,10 @@ _main() { ${POSITIONAL_ARGS[@]} sf org display -o "$alias" sf project deploy start -o "$alias" + if [[ "${CPQ}" == "true" ]]; then + # Salesforce CPQ (SBQQ) 252.3.0.1 + sf package install --package "04t6T000000t6QXQAY" --no-prompt --wait 30 + fi } if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 3324a778..d26259c7 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -17,8 +17,10 @@ import { Security as security } from './security'; import { Slack as slack } from './slack'; import { CompanyInformation as companyInformation } from './company-information'; import { LinkedInSalesNavigatorSettings as linkedInSalesNavigatorSettings } from './linkedin-sales-navigator-settings'; +import { SalesforceCpqConfig as salesforceCpqConfig } from './salesforce-cpq-config'; export { + salesforceCpqConfig, activitySettings, communities, customerPortal, diff --git a/src/plugins/salesforce-cpq-config/default.json b/src/plugins/salesforce-cpq-config/default.json new file mode 100644 index 00000000..194af2b1 --- /dev/null +++ b/src/plugins/salesforce-cpq-config/default.json @@ -0,0 +1,109 @@ +{ + "$schema": "../schema.json", + "settings": { + "salesforceCpqConfig": { + "documents": { + "documentFolder": "User Personal Documents", + "hideDocumentName": false, + "fullPagePreview": false, + "excludeHiddenLinesInGroupTotals": false, + "attachmentTarget": "Document Only", + "postToFeed": false, + "enableMultiLanguageTranslations": false + }, + "groups": { + "requireGroupName": true + }, + "lineEditor": { + "hideRenewedAssetsWhenEditing": false, + "visualizeProductHierarchy": false, + "preserveBundleStructure": true, + "keepBundleTogether": true, + "totalsField": "Default", + "lineSubtotalsTotalField": "Default", + "defaultBundleSetting": "Expanded", + "actionsColumnPlacement": "Right", + "enableMultiLineDelete": false, + "productConfigurationInitializer": "", + "enableAssetUpgrades": false, + "groupSubtotalsField": "Default", + "wrapButtons": false, + "validateLargeQuotes": false, + "enableCompactMode": true, + "enableLargeQuoteExperience": false, + "enableColumnResizing": false + }, + "plugins": { + "legacyPageSecurityPlugin": "", + "electronicSignaturePlugin": "", + "billingPlugin": "", + "productSearchPlugin": "", + "recommendedProductsPlugin": "", + "legacyQuoteCalculatorPlugin": "", + "documentStorePlugin": "", + "quoteCalculatorPlugin": "", + "orderManagementPlugin": "", + "qleCustomActionPlugin": "" + }, + "pricingAndCalculation": { + "currencySymbol": "", + "enableQuickCalculate": false, + "allowNonConsecutiveCustomSegments": false, + "enablePricingGuidance": false, + "quoteLineEditsForUsageBasedPricing": false, + "useInactivePrices": false, + "calculateImmediately": false, + "disableBackgroundCalculationRefresh": false, + "enableUsageBasedPricing": false, + "hideUncalculatedQuoteWarning": false, + "useIntegrationUserForCalculations": true + }, + "subscriptionsAndRenewals": { + "renewalModel": "Contract Based", + "disableAddSubscriptions": false, + "allowRenewalQuotesWithoutAssets": false, + "subscriptionTermUnit": "Month", + "subscriptionProratePrecision": "Monthly + Daily", + "includeNetNewProductsInMaintenance": false, + "contractInForeground": false, + "enableEvergreenSubscriptions": false, + "poTRenewalsContractingFromOrders": true, + "amendContractsInBackground": false, + "disableProductBundleSubTypeChecks": false, + "bypassPreserveBundleStructure": false, + "useCurrentSubscriptionOnAssets": false + }, + "quote": { + "disableInitialQuoteSync": false, + "disableQuoteContactDefaulting": false, + "allowOptionDeletion": false, + "disableQuoteAddressDefaulting": false, + "primaryQuoteKeepsOpportunityProducts": false + }, + "order": { + "requireApprovedQuote": false, + "defaultOrderStartDate": "-- None --", + "allowMultipleOrders": false, + "createOrdersWithoutOpportunities": false + }, + "additionalSettings": { + "triggersDisabled": false, + "sortProductsInMemory": false, + "multipleBundlesView": "Wizard", + "externalConfiguratorUrl": "", + "thirdPartyConfigurator": false, + "openSearchFilterByDefault": false, + "hideFeaturesWithHiddenOptions": false, + "hideProductSearchBar": false, + "productResultsGroupFieldName": "-- None --", + "productDescriptionField": "Product Description", + "theme": "-- None --", + "useGlobalHeaderPermission": false, + "serviceRegion": "Default", + "enableProductOptionDrawer": false, + "enableLargeConfigurations": false, + "nestedBundlesForExternalConfigurator": false + } + } + } +} diff --git a/src/plugins/salesforce-cpq-config/formConfig.json b/src/plugins/salesforce-cpq-config/formConfig.json new file mode 100644 index 00000000..ce5f934e --- /dev/null +++ b/src/plugins/salesforce-cpq-config/formConfig.json @@ -0,0 +1,648 @@ +{ + "documents": { + "label": "Documents", + "component": "tab", + "id": "page:form:pb:j_id71_lbl", + "properties": { + "documentFolder": { + "label": "Document Folder", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id72:j_id73:j_id75" + }, + "hideDocumentName": { + "label": "Hide Document Name", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id72:j_id84" + }, + "fullPagePreview": { + "label": "Full Page Preview", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id72:j_id88" + }, + "excludeHiddenLinesInGroupTotals": { + "label": "Exclude Hidden Lines In Group Totals", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id72:j_id90" + }, + "attachmentTarget": { + "label": "Attachment Target", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id72:j_id78:j_id80" + }, + "postToFeed": { + "label": "Post to Feed?", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id72:j_id85:j_id87" + }, + "enableMultiLanguageTranslations": { + "label": "Enable Multi-Language Translations", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id72:j_id89" + } + } + }, + "groups": { + "label": "Groups", + "component": "tab", + "id": "page:form:pb:j_id105_lbl", + "properties": { + "solutionGroupsEnabled": { + "label": "Solution Groups Enabled", + "component": "input", + "type": "boolean", + "name": "page:form:pb:groupSettings:j_id106" + }, + "object": { + "label": "Object", + "component": "input", + "type": "string", + "name": "page:form:pb:groupSettings:j_id109:j_id111" + }, + "nameField": { + "label": "Name Field", + "component": "select", + "type": "string", + "name": "page:form:pb:groupSettings:j_id114:j_id116" + }, + "descriptionField": { + "label": "Description Field", + "component": "select", + "type": "string", + "name": "page:form:pb:groupSettings:j_id120:j_id122" + }, + "requireGroupName": { + "label": "Require Group Name", + "component": "input", + "type": "boolean", + "name": "page:form:pb:groupSettings:j_id108" + } + } + }, + "lineEditor": { + "label": "Line Editor", + "component": "tab", + "id": "page:form:pb:j_id125_lbl", + "properties": { + "hideRenewedAssetsWhenEditing": { + "label": "Hide Renewed Assets When Editing", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id127:j_id129" + }, + "visualizeProductHierarchy": { + "label": "Visualize Product Hierarchy", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id135" + }, + "preserveBundleStructure": { + "label": "Preserve Bundle Structure", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id137" + }, + "keepBundleTogether": { + "label": "Keep Bundle Together", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id139" + }, + "totalsField": { + "label": "Totals Field", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id126:j_id141:j_id143" + }, + "lineSubtotalsTotalField": { + "label": "Line Subtotals Total Field", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id126:j_id153:j_id155" + }, + "largeQuoteThreshold": { + "label": "Large Quote Threshold", + "component": "input", + "type": "number", + "name": "page:form:pb:j_id126:j_id160" + }, + "quoteBatchSize": { + "label": "Quote Batch Size", + "component": "input", + "type": "number", + "name": "page:form:pb:j_id126:j_id162" + }, + "enableExpandCollapseBundles": { + "label": "Enable Expand/Collapse Bundles", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id164" + }, + "defaultBundleSetting": { + "label": "Default Bundle Setting", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id126:j_id166:j_id168" + }, + "actionsColumnPlacement": { + "label": "Actions Column Placement", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id126:j_id130:j_id132" + }, + "enableMultiLineDelete": { + "label": "Enable Multi Line Delete", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id136" + }, + "productConfigurationInitializer": { + "label": "Product Configuration Initializer", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id126:j_id138" + }, + "enableAssetUpgrades": { + "label": "Enable Asset Upgrades", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id140" + }, + "groupSubtotalsField": { + "label": "Group Subtotals Field", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id126:j_id147:j_id149" + }, + "wrapButtons": { + "label": "Wrap Buttons", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id159" + }, + "validateLargeQuotes": { + "label": "Validate Large Quotes", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id161" + }, + "enableCompactMode": { + "label": "Enable Compact Mode", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id163" + }, + "enableLargeQuoteExperience": { + "label": "Enable Large Quote Experience", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id165" + }, + "enableColumnResizing": { + "label": "Enable Column Resizing", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id126:j_id171" + } + } + }, + "plugins": { + "label": "Plugins", + "component": "tab", + "id": "page:form:pb:j_id172_lbl", + "properties": { + "legacyPageSecurityPlugin": { + "label": "Legacy Page Security Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id174" + }, + "electronicSignaturePlugin": { + "label": "Electronic Signature Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id175" + }, + "billingPlugin": { + "label": "Billing Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id176" + }, + "productSearchPlugin": { + "label": "Product Search Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id177" + }, + "recommendedProductsPlugin": { + "label": "Recommended Products Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id178" + }, + "legacyQuoteCalculatorPlugin": { + "label": "Legacy Quote Calculator Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id178" + }, + "documentStorePlugin": { + "label": "Document Store Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id180" + }, + "quoteCalculatorPlugin": { + "label": "Quote Calculator Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id181" + }, + "orderManagementPlugin": { + "label": "Order Management Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id182" + }, + "qleCustomActionPlugin": { + "label": "QLE Custom Action Plugin", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id173:j_id183" + } + } + }, + "pricingAndCalculation": { + "label": "Pricing and Calculation", + "component": "tab", + "id": "page:form:pb:j_id184_lbl", + "properties": { + "currencySymbol": { + "label": "Currency Symbol", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id185:j_id186" + }, + "unitPriceScale": { + "label": "Unit Price Scale", + "component": "input", + "type": "number", + "name": "page:form:pb:j_id185:j_id188" + }, + "enableQuickCalculate": { + "label": "Enable Quick Calculate", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id190" + }, + "allowNonConsecutiveCustomSegments": { + "label": "Allow Non-Consecutive Custom Segments", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id192" + }, + "enablePricingGuidance": { + "label": "Enable Pricing Guidance", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id194" + }, + "useLegacyCalculator": { + "label": "Use Legacy Calculator", + "component": "input", + "type": "boolean", + "name": "page:form:pb:calculatorOptions:j_id198" + }, + "useInactivePrices": { + "label": "Use Inactive Prices", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id187" + }, + "calculateImmediately": { + "label": "Calculate Immediately", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id189" + }, + "disableBackgroundCalculationRefresh": { + "label": "Disable Background Calculation Refresh", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id191" + }, + "enableUsageBasedPricing": { + "label": "Enable Usage Based Pricing", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id193", + "immediatelySave": true + }, + "quoteLineEditsForUsageBasedPricing": { + "label": "Quote Line Edits for Usage Based Pricing", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id196" + }, + "hideUncalculatedQuoteWarning": { + "label": "Hide Uncalculated Quote Warning", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id195" + }, + "useIntegrationUserForCalculations": { + "label": "Use Integration User for Calculations", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id185:j_id197" + } + } + }, + "subscriptionsAndRenewals": { + "label": "Subscriptions and Renewals", + "component": "tab", + "id": "page:form:pb:j_id206_lbl", + "properties": { + "renewalModel": { + "label": "Renewal Model", + "component": "select", + "type": "string", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id207:j_id209" + }, + "disableAddSubscriptions": { + "label": "Disable Add Subscriptions", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id211" + }, + "allowRenewalQuotesWithoutAssets": { + "label": "Allow Renewal Quotes Without Assets", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id212:j_id214" + }, + "subscriptionTermUnit": { + "label": "Subscription Term Unit", + "component": "select", + "type": "string", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id215:j_id217" + }, + "subscriptionProratePrecision": { + "label": "Subscription Prorate Precision", + "component": "select", + "type": "string", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id221:j_id223" + }, + "includeNetNewProductsInMaintenance": { + "label": "Include Net-new Products in Maintenance", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id232" + }, + "contractInForeground": { + "label": "Contract In Foreground", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id233" + }, + "reEvaluateBundleLogicOnRenewals": { + "label": "Re-evaluate Bundle Logic on Renewals", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id234" + }, + "enableEvergreenSubscriptions": { + "label": "Enable Evergreen Subscriptions", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id235:j_id237" + }, + "poTRenewalsContractingFromOrders": { + "label": "PoT Renewals (Contracting from Orders)", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id238:j_id240" + }, + "legacyAmendRenewService": { + "label": "Legacy Amend/Renew Service", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id241" + }, + "amendContractsInBackground": { + "label": "Amend Contracts in Background", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id243" + }, + "disableProductBundleSubTypeChecks": { + "label": "Disable Product Bundle Sub Type Checks", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id243" + }, + "bypassPreserveBundleStructure": { + "label": "Bypass Preserve Bundle Structure", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id247:j_id249" + }, + "useCurrentSubscriptionOnAssets": { + "label": "Use Current Subscription on Assets", + "component": "input", + "type": "boolean", + "name": "page:form:pb:subscriptionAndRenewalSettings:j_id250:j_id252" + } + } + }, + "quote": { + "label": "Quote", + "component": "tab", + "id": "page:form:pb:j_id253_lbl", + "properties": { + "disableInitialQuoteSync": { + "label": "Disable Initial Quote Sync", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id254:j_id255" + }, + "disableQuoteContactDefaulting": { + "label": "Disable Quote Contact Defaulting", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id254:j_id257" + }, + "allowOptionDeletion": { + "label": "Allow Option Deletion", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id254:j_id261" + }, + "disableQuoteAddressDefaulting": { + "label": "Disable Quote Address Defaulting", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id254:j_id256" + }, + "primaryQuoteKeepsOpportunityProducts": { + "label": "Primary Quote Keeps Opportunity Products", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id254:j_id258:j_id260" + }, + "defaultQuoteValidityDays": { + "label": "Default Quote Validity (Days)", + "component": "input", + "type": "number", + "name": "page:form:pb:j_id254:j_id262" + } + } + }, + "order": { + "label": "Order", + "component": "tab", + "id": "page:form:pb:j_id263_lbl", + "properties": { + "requireApprovedQuote": { + "label": "Require Approved Quote", + "component": "input", + "type": "boolean", + "name": "page:form:pb:orderOptions:j_id264" + }, + "defaultOrderStartDate": { + "label": "Default Order Start Date", + "component": "select", + "type": "string", + "name": "page:form:pb:orderOptions:j_id267:j_id269" + }, + "allowMultipleOrders": { + "label": "Allow Multiple Orders", + "component": "input", + "type": "boolean", + "name": "page:form:pb:orderOptions:j_id265" + }, + "createOrdersWithoutOpportunities": { + "label": "Create Orders Without Opportunities", + "component": "input", + "type": "boolean", + "name": "page:form:pb:orderOptions:j_id271" + } + } + }, + "additionalSettings": { + "label": "Additional Settings", + "component": "tab", + "id": "page:form:pb:j_id273_lbl", + "properties": { + "triggersDisabled": { + "label": "Triggers Disabled", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id275" + }, + "quantityScale": { + "label": "Quantity Scale", + "component": "input", + "type": "number", + "name": "page:form:pb:j_id274:j_id280" + }, + "sortProductsInMemory": { + "label": "Sort Products In Memory", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id285" + }, + "multipleBundlesView": { + "label": "Multiple Bundles View", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id274:j_id290:j_id292" + }, + "externalConfiguratorUrl": { + "label": "External Configurator URL", + "component": "input", + "type": "string", + "name": "page:form:pb:j_id274:j_id304" + }, + "thirdPartyConfigurator": { + "label": "Third Party Configurator", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id309" + }, + "openSearchFilterByDefault": { + "label": "Open Search Filter By Default", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id311" + }, + "hideFeaturesWithHiddenOptions": { + "label": "Hide Features with Hidden Options", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id313" + }, + "hideProductSearchBar": { + "label": "Hide Product Search Bar", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id313" + }, + "productResultsGroupFieldName": { + "label": "Product Results Group Field Name", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id274:j_id276:j_id278" + }, + "productDescriptionField": { + "label": "Product Description Field", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id274:j_id281:j_id283" + }, + "theme": { + "label": "Theme", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id274:j_id286:j_id288" + }, + "useGlobalHeaderPermission": { + "label": "Use Global Header Permission", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id303" + }, + "serviceRegion": { + "label": "Service Region", + "component": "select", + "type": "string", + "name": "page:form:pb:j_id274:j_id305:j_id307" + }, + "enableProductOptionDrawer": { + "label": "Enable Product Option Drawer", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id310" + }, + "enableLargeConfigurations": { + "label": "Enable Large Configurations", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id310" + }, + "nestedBundlesForExternalConfigurator": { + "label": "Nested Bundles for External Configurator", + "component": "input", + "type": "boolean", + "name": "page:form:pb:j_id274:j_id314" + } + } + } +} diff --git a/src/plugins/salesforce-cpq-config/index.e2e-spec.ts b/src/plugins/salesforce-cpq-config/index.e2e-spec.ts new file mode 100644 index 00000000..2167c983 --- /dev/null +++ b/src/plugins/salesforce-cpq-config/index.e2e-spec.ts @@ -0,0 +1,41 @@ +import assert from 'assert'; +import { SalesforceCpqConfig } from '.'; +import defaultConfig from './default.json'; + +const describeOrSkip = process.env['CPQ'] === 'true' ? describe : describe.skip; +describeOrSkip(SalesforceCpqConfig.name, function () { + let plugin: SalesforceCpqConfig; + before(() => { + plugin = new SalesforceCpqConfig(global.bf); + }); + + const configDefault: any = defaultConfig.settings.salesforceCpqConfig; + const configCustom = { + documents: { + documentFolder: 'Quotes', + hideDocumentName: true + }, + quote: { + disableInitialQuoteSync: true + } + }; + + it('should apply default config', async () => { + await plugin.run(configDefault); + }); + + it('should already be configured', async () => { + const res = await plugin.run(configDefault); + assert.deepStrictEqual(res, { message: 'no action necessary' }); + }); + + it('should apply custom config', async () => { + const res = await plugin.run(configCustom); + assert.notDeepStrictEqual(res, { message: 'no action necessary' }); + }); + + it('should already be configured', async () => { + const res = await plugin.run(configCustom); + assert.deepStrictEqual(res, { message: 'no action necessary' }); + }); +}); diff --git a/src/plugins/salesforce-cpq-config/index.ts b/src/plugins/salesforce-cpq-config/index.ts new file mode 100644 index 00000000..bb8d1369 --- /dev/null +++ b/src/plugins/salesforce-cpq-config/index.ts @@ -0,0 +1,233 @@ +import { BrowserforcePlugin } from '../../plugin'; +import formConfigData from './formConfig.json'; +import { FormConfig } from './types'; + +const formConfig: FormConfig = formConfigData; + +const PATHS = { + BASE: '0A3?setupid=ImportedPackage&retURL=%2Fui%2Fsetup%2FSetup%3Fsetupid%3DStudio' +}; + +const SELECTORS = { + CONFIGURE: '.actionLink[title*="Configure"][title*="Salesforce CPQ"]', + GENERATE_INTEGRATION_USER_PERMISSIONS: 'input[name="page:form:pb:j_id185:j_id197:setupIntegrationUserPermissions"]', + SAVE: 'input[name="page:form:j_id2:j_id3:j_id11"]', + AUTHORIZE_NEW_CALCULATION_SERVICE: 'span#page\\:form\\:pb\\:calculatorOptions\\:j_id201\\:j_id203 a', + ALLOW: 'input[name="save"]' +}; + +const authorizationURL = 'setup/secur/RemoteAccessAuthorizationPage.apexp'; + +export type Config = any; + +export class SalesforceCpqConfig extends BrowserforcePlugin { + private logger = this.browserforce.logger; + public async retrieve(definition?: Config): Promise { + const page = await this.browserforce.openPage(PATHS.BASE); + await page.waitForSelector(SELECTORS.CONFIGURE); + await Promise.all([page.waitForNavigation(), page.click(SELECTORS.CONFIGURE)]); + + const response = {} as Config; + if (definition) { + for (const [keyTab, valueTab] of Object.entries(formConfig)) { + if (definition[keyTab]) { + await page.waitForSelector(`td[id="${valueTab.id}"]`); + await page.click(`td[id="${valueTab.id}"]`); + for (const [keyItem, valueItem] of Object.entries(valueTab.properties)) { + if (!(definition[keyTab][keyItem] === undefined)) { + const item = valueItem; + response[keyTab] = response[keyTab] || {}; + try { + if (item.component === 'input' && item.type === 'boolean') { + response[keyTab][keyItem] = await page.$eval( + `${item.component}[name="${item.name}"]`, + (el: HTMLInputElement) => el.checked + ); + } else if (item.component === 'input' && item.type === 'string') { + response[keyTab][keyItem] = await page.$eval( + `${item.component}[name="${item.name}"]`, + (el: HTMLInputElement) => el.value + ); + } else if (item.component === 'select') { + response[keyTab][keyItem] = await page.$eval( + `${item.component}[name="${item.name}"]`, + (el: HTMLSelectElement) => el.selectedOptions[0].text + ); + } + } catch (e) { + if ( + e.message === + `Error: failed to find element matching selector "${item.component}[name="${item.name}"]"` + ) { + this.logger?.warn( + `Label '${item.label}' '${keyTab}.${keyItem}' with component '${item.component}[name="${item.name}"]' is not found` + ); + } else { + throw e; + } + } + } + } + } + } + } + await page.close(); + return response; + } + + public async apply(config: Config): Promise { + const page = await this.browserforce.openPage(PATHS.BASE); + await Promise.all([page.waitForNavigation(), page.click(SELECTORS.CONFIGURE)]); + + /* + This to click on the 'Generate Integration User Permissions button' for first time setup. + Once the button is clicked, it will not be available for the next time. + */ + try { + this.logger?.log(`Clicking on 'Generate Integration User Permissions' button`); + await page.waitForSelector(`td[id="${formConfig.pricingAndCalculation.id}"]`); + await page.click(`td[id="${formConfig.pricingAndCalculation.id}"]`); + await page.click(SELECTORS.GENERATE_INTEGRATION_USER_PERMISSIONS); + } catch (e) { + if (e.message === `No element found for selector: ${SELECTORS.GENERATE_INTEGRATION_USER_PERMISSIONS}`) { + this.logger?.log( + `The button 'Generate Integration User Permissions' is not found. It might be already clicked before.` + ); + } else { + this.logger?.warn(`Error clicking 'Generate Integration User Permissions' button with message: ${e.message}`); + throw e; + } + } + + /* + This to loop through the formConfig and set the value based on the config provided. + */ + for (const [keyTab, valueTab] of Object.entries(formConfig)) { + if (config[keyTab]) { + await page.waitForSelector(`td[id="${valueTab.id}"]`); + await page.click(`td[id="${valueTab.id}"]`); + for (const [keyItem, valueItem] of Object.entries(valueTab.properties)) { + if (!(config[keyTab][keyItem] === undefined)) { + const item = valueItem; + try { + this.logger?.log( + `Updating: '${keyTab}.${keyItem}' (${item.label}) with component '${item.component}[name="${item.name}"]' with value: '${config[keyTab][keyItem]}'` + ); + if (item.component === 'input' && item.type === 'boolean') { + await page.$eval( + `input[name="${item.name}"]`, + (e: HTMLInputElement, v: boolean) => { + e.checked = v; + }, + config[keyTab][keyItem] + ); + } else if (item.component === 'input' && item.type === 'string') { + await page.$eval( + `input[name="${item.name}"]`, + (e: HTMLInputElement, v: string) => { + e.value = v; + }, + config[keyTab][keyItem] + ); + } else if (item.component === 'select') { + const selectFieldOptions = await page.$$eval( + `select[name="${item.name}"] option`, + (options: HTMLOptionElement[]) => { + return options.map((option) => { + return { + text: option.text, + value: option.value + }; + }); + } + ); + const chooseFieldOption = selectFieldOptions.find((x) => x.text === config[keyTab][keyItem]); + if (!chooseFieldOption) { + const availableOption = selectFieldOptions.map((option) => option.text); + throw new Error( + `Fail to set '${item.label}' with value '${ + config[keyTab][keyItem] + }'. \nPlease make sure to select one of this available options: ${JSON.stringify( + availableOption + )}\n` + ); + } + await page.select(`select[name="${item.name}"]`, chooseFieldOption.value); + } + if (item.immediatelySave) { + await Promise.all([page.waitForNavigation(), page.click(SELECTORS.SAVE)]); + } + } catch (e) { + if ( + e.message === `Error: failed to find element matching selector "${item.component}[name="${item.name}"]"` + ) { + this.logger?.warn( + `Label '${item.label}' '${keyTab}.${keyItem}' with component '${item.component}[name="${item.name}"]' is not found` + ); + } else { + this.logger?.warn( + `Error: at Label '${item.label}' '${keyTab}.${keyItem}' with component '${item.component}[name="${item.name}"]' with message: ${e.message}` + ); + throw e; + } + } + } + } + } + await Promise.all([page.waitForNavigation(), page.click(SELECTORS.SAVE)]); + } + + /* + This to click on the 'Authorize New Calculation Service' link under Pricing and Calculation tab. + Once authorized, it will not appear the next time. + */ + try { + this.logger?.log(`'Authorize New Calculation Service' link`); + await page.waitForSelector(`td[id="${formConfig.pricingAndCalculation.id}"]`); + await page.click(`td[id="${formConfig.pricingAndCalculation.id}"]`); + + const authorizeLink = await page.$(SELECTORS.AUTHORIZE_NEW_CALCULATION_SERVICE); + + if (authorizeLink) { + // Click on 'Authorize New Calculation Service' link + await page.waitForSelector(SELECTORS.AUTHORIZE_NEW_CALCULATION_SERVICE); + await page.click(SELECTORS.AUTHORIZE_NEW_CALCULATION_SERVICE); + + // Wait for popup window with the expected URL + const newWindowTarget = await page.browser().waitForTarget((target) => target.url().includes(authorizationURL)); + const newPage = await newWindowTarget.page(); + + if (newPage) { + // Click on'Allow' button + await newPage.waitForSelector(SELECTORS.ALLOW, { visible: true }); + await newPage.click(SELECTORS.ALLOW); + await page.waitForNavigation(); // Wait for the main page to refresh + + this.logger?.log('The main page has refreshed after allowing.'); + } else { + this.logger?.warn('Failed to retrieve the new page from the popup.'); + } + this.logger?.log('The authorization process has been completed.'); + } else { + this.logger?.log( + `The link 'Authorize New Calculation Service' was not found. It might be already clicked before.` + ); + } + } catch (e) { + if (e.message === `No element found for selector: ${SELECTORS.AUTHORIZE_NEW_CALCULATION_SERVICE}`) { + this.logger?.log( + `The link Authorize New Calculation Service' is not found. It might be already clicked before.` + ); + } else if ( + e.message === `Waiting for selector \`input[name="save"]\` failed: waitForFunction failed: frame got detached.` + ) { + this.logger?.log(`ALLOW button is not found. It might be already clicked before.`); + } else { + this.logger?.warn(`Error clicking Authorize New Calculation Service' button with message: ${e.message}`); + throw e; + } + } + + await page.close(); + } +} diff --git a/src/plugins/salesforce-cpq-config/schema.json b/src/plugins/salesforce-cpq-config/schema.json new file mode 100644 index 00000000..2e8ff0e8 --- /dev/null +++ b/src/plugins/salesforce-cpq-config/schema.json @@ -0,0 +1,450 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://github.com/amtrack/sfdx-browserforce-plugin/src/plugins/salesforce-cpq-config/schema.json", + "title": "SalesforceCpqConfig Settings", + "type": "object", + "properties": { + "documents": { + "type": "object", + "title": "Documents", + "properties": { + "documentFolder": { + "type": "string", + "title": "Document Folder" + }, + "hideDocumentName": { + "type": "boolean", + "title": "Hide Document Name" + }, + "fullPagePreview": { + "type": "boolean", + "title": "Full Page Preview" + }, + "excludeHiddenLinesInGroupTotals": { + "type": "boolean", + "title": "Exclude Hidden Lines In Group Totals" + }, + "attachmentTarget": { + "type": "string", + "title": "Attachment Target" + }, + "postToFeed": { + "type": "boolean", + "title": "Post to Feed?" + }, + "enableMultiLanguageTranslations": { + "type": "boolean", + "title": "Enable Multi-Language Translations" + } + } + }, + "groups": { + "type": "object", + "title": "Groups", + "properties": { + "solutionGroupsEnabled": { + "type": "boolean", + "title": "Solution Groups Enabled" + }, + "object": { + "type": "string", + "title": "Object" + }, + "nameField": { + "type": "string", + "title": "Name Field" + }, + "descriptionField": { + "type": "string", + "title": "Description Field" + }, + "requireGroupName": { + "type": "boolean", + "title": "Require Group Name" + } + } + }, + "lineEditor": { + "type": "object", + "title": "Line Editor", + "properties": { + "hideRenewedAssetsWhenEditing": { + "type": "boolean", + "title": "Hide Renewed Assets When Editing" + }, + "visualizeProductHierarchy": { + "type": "boolean", + "title": "Visualize Product Hierarchy" + }, + "preserveBundleStructure": { + "type": "boolean", + "title": "Preserve Bundle Structure" + }, + "keepBundleTogether": { + "type": "boolean", + "title": "Keep Bundle Together" + }, + "totalsField": { + "type": "string", + "title": "Totals Field" + }, + "lineSubtotalsTotalField": { + "type": "string", + "title": "Line Subtotals Total Field" + }, + "largeQuoteThreshold": { + "type": "number", + "title": "Large Quote Threshold" + }, + "quoteBatchSize": { + "type": "number", + "title": "Quote Batch Size" + }, + "enableExpandCollapseBundles": { + "type": "boolean", + "title": "Enable Expand/Collapse Bundles" + }, + "defaultBundleSetting": { + "type": "string", + "title": "Default Bundle Setting" + }, + "actionsColumnPlacement": { + "type": "string", + "title": "Actions Column Placement" + }, + "enableMultiLineDelete": { + "type": "boolean", + "title": "Enable Multi Line Delete" + }, + "productConfigurationInitializer": { + "type": "string", + "title": "Product Configuration Initializer" + }, + "enableAssetUpgrades": { + "type": "boolean", + "title": "Enable Asset Upgrades" + }, + "groupSubtotalsField": { + "type": "string", + "title": "Group Subtotals Field" + }, + "wrapButtons": { + "type": "boolean", + "title": "Wrap Buttons" + }, + "validateLargeQuotes": { + "type": "boolean", + "title": "Validate Large Quotes" + }, + "enableCompactMode": { + "type": "boolean", + "title": "Enable Compact Mode" + }, + "enableLargeQuoteExperience": { + "type": "boolean", + "title": "Enable Large Quote Experience" + }, + "enableColumnResizing": { + "type": "boolean", + "title": "Enable Column Resizing" + } + } + }, + "plugins": { + "type": "object", + "title": "Plugins", + "properties": { + "legacyPageSecurityPlugin": { + "type": "string", + "title": "Legacy Page Security Plugin" + }, + "electronicSignaturePlugin": { + "type": "string", + "title": "Electronic Signature Plugin" + }, + "billingPlugin": { + "type": "string", + "title": "Billing Plugin" + }, + "productSearchPlugin": { + "type": "string", + "title": "Product Search Plugin" + }, + "recommendedProductsPlugin": { + "type": "string", + "title": "Recommended Products Plugin" + }, + "legacyQuoteCalculatorPlugin": { + "type": "string", + "title": "Legacy Quote Calculator Plugin" + }, + "documentStorePlugin": { + "type": "string", + "title": "Document Store Plugin" + }, + "quoteCalculatorPlugin": { + "type": "string", + "title": "Quote Calculator Plugin" + }, + "orderManagementPlugin": { + "type": "string", + "title": "Order Management Plugin" + }, + "qleCustomActionPlugin": { + "type": "string", + "title": "QLE Custom Action Plugin" + } + } + }, + "pricingAndCalculation": { + "type": "object", + "title": "Pricing and Calculation", + "properties": { + "currencySymbol": { + "type": "string", + "title": "Currency Symbol" + }, + "useInactivePrices": { + "type": "boolean", + "title": "Use Inactive Prices" + }, + "unitPriceScale": { + "type": "number", + "title": "Unit Price Scale" + }, + "calculateImmediately": { + "type": "boolean", + "title": "Calculate Immediately" + }, + "enableQuickCalculate": { + "type": "boolean", + "title": "Enable Quick Calculate" + }, + "allowNonConsecutiveCustomSegments": { + "type": "boolean", + "title": "Allow Non-Consecutive Custom Segments" + }, + "enablePricingGuidance": { + "type": "boolean", + "title": "Enable Pricing Guidance" + }, + "quoteLineEditsForUsageBasedPricing": { + "type": "boolean", + "title": "Quote Line Edits for Usage Based Pricing" + }, + "useLegacyCalculator": { + "type": "boolean", + "title": "Use Legacy Calculator" + }, + "disableBackgroundCalculationRefresh": { + "type": "boolean", + "title": "Disable Background Calculation Refresh" + }, + "enableUsageBasedPricing": { + "type": "boolean", + "title": "Enable Usage Based Pricing" + }, + "hideUncalculatedQuoteWarning": { + "type": "boolean", + "title": "Hide Uncalculated Quote Warning" + }, + "useIntegrationUserForCalculations": { + "type": "boolean", + "title": "Use Integration User for Calculations" + } + } + }, + "subscriptionsAndRenewals": { + "type": "object", + "title": "Subscriptions and Renewals", + "properties": { + "renewalModel": { + "type": "string", + "title": "Renewal Model" + }, + "disableAddSubscriptions": { + "type": "boolean", + "title": "Disable Add Subscriptions" + }, + "allowRenewalQuotesWithoutAssets": { + "type": "boolean", + "title": "Allow Renewal Quotes Without Assets" + }, + "subscriptionProratePrecision": { + "type": "string", + "title": "Subscription Prorate Precision" + }, + "includeNetNewProductsInMaintenance": { + "type": "boolean", + "title": "Include Net-new Products in Maintenance" + }, + "contractInForeground": { + "type": "boolean", + "title": "Contract In Foreground" + }, + "enableEvergreenSubscriptions": { + "type": "boolean", + "title": "Enable Evergreen Subscriptions" + }, + "legacyAmendRenewService": { + "type": "boolean", + "title": "Legacy Amend/Renew Service" + }, + "amendContractsInBackground": { + "type": "boolean", + "title": "Amend Contracts in Background" + }, + "bypassPreserveBundleStructure": { + "type": "boolean", + "title": "Bypass Preserve Bundle Structure" + }, + "subscriptionTermUnit": { + "type": "string", + "title": "Subscription Term Unit" + }, + "reEvaluateBundleLogicOnRenewals": { + "type": "boolean", + "title": "Re-evaluate Bundle Logic on Renewals" + }, + "poTRenewalsContractingFromOrders": { + "type": "boolean", + "title": "PoT Renewals (Contracting from Orders)" + }, + "disableProductBundleSubTypeChecks": { + "type": "boolean", + "title": "Disable Product Bundle Sub Type Checks" + }, + "useCurrentSubscriptionOnAssets": { + "type": "boolean", + "title": "Use Current Subscription on Assets" + } + } + }, + "quote": { + "type": "object", + "title": "Quote", + "properties": { + "disableInitialQuoteSync": { + "type": "boolean", + "title": "Disable Initial Quote Sync" + }, + "disableQuoteContactDefaulting": { + "type": "boolean", + "title": "Disable Quote Contact Defaulting" + }, + "allowOptionDeletion": { + "type": "boolean", + "title": "Allow Option Deletion" + }, + "disableQuoteAddressDefaulting": { + "type": "boolean", + "title": "Disable Quote Address Defaulting" + }, + "primaryQuoteKeepsOpportunityProducts": { + "type": "boolean", + "title": "Primary Quote Keeps Opportunity Products" + }, + "defaultQuoteValidityDays": { + "type": "number", + "title": "Default Quote Validity (Days)" + } + } + }, + "order": { + "type": "object", + "title": "Order", + "properties": { + "requireApprovedQuote": { + "type": "boolean", + "title": "Require Approved Quote" + }, + "defaultOrderStartDate": { + "type": "string", + "title": "Default Order Start Date" + }, + "allowMultipleOrders": { + "type": "boolean", + "title": "Allow Multiple Orders" + }, + "createOrdersWithoutOpportunities": { + "type": "boolean", + "title": "Create Orders Without Opportunities" + } + } + }, + "additionalSettings": { + "type": "object", + "title": "Additional Settings", + "properties": { + "triggersDisabled": { + "type": "boolean", + "title": "Triggers Disabled" + }, + "quantityScale": { + "type": "number", + "title": "Quantity Scale" + }, + "sortProductsInMemory": { + "type": "boolean", + "title": "Sort Products In Memory" + }, + "multipleBundlesView": { + "type": "string", + "title": "Multiple Bundles View" + }, + "externalConfiguratorUrl": { + "type": "string", + "title": "External Configurator URL" + }, + "thirdPartyConfigurator": { + "type": "boolean", + "title": "Third Party Configurator" + }, + "openSearchFilterByDefault": { + "type": "boolean", + "title": "Open Search Filter By Default" + }, + "hideFeaturesWithHiddenOptions": { + "type": "boolean", + "title": "Hide Features with Hidden Options" + }, + "hideProductSearchBar": { + "type": "boolean", + "title": "Hide Product Search Bar" + }, + "productResultsGroupFieldName": { + "type": "string", + "title": "Product Results Group Field Name" + }, + "productDescriptionField": { + "type": "string", + "title": "Product Description Field" + }, + "theme": { + "type": "string", + "title": "Theme" + }, + "useGlobalHeaderPermission": { + "type": "boolean", + "title": "Use Global Header Permission" + }, + "serviceRegion": { + "type": "string", + "title": "Service Region" + }, + "enableProductOptionDrawer": { + "type": "boolean", + "title": "Enable Product Option Drawer" + }, + "enableLargeConfigurations": { + "type": "boolean", + "title": "Enable Large Configurations" + }, + "nestedBundlesForExternalConfigurator": { + "type": "boolean", + "title": "Nested Bundles for External Configurator" + } + } + } + } +} diff --git a/src/plugins/salesforce-cpq-config/types.ts b/src/plugins/salesforce-cpq-config/types.ts new file mode 100644 index 00000000..59322904 --- /dev/null +++ b/src/plugins/salesforce-cpq-config/types.ts @@ -0,0 +1,16 @@ +export type FormConfig = { + [key: string]: { + label: string; + component: string; // 'tab' + id: string; + properties: { + [key: string]: { + label: string; + component: string; // 'input' | 'select' + type: string; // 'string' | 'boolean' | 'number' + name: string; + immediatelySave?: boolean; + }; + }; + }; +}; diff --git a/src/plugins/schema.json b/src/plugins/schema.json index e15be492..4c5b38be 100644 --- a/src/plugins/schema.json +++ b/src/plugins/schema.json @@ -7,6 +7,9 @@ "properties": { "settings": { "properties": { + "salesforceCpqConfig": { + "$ref": "./salesforce-cpq-config/schema.json" + }, "companyInformation": { "$ref": "./company-information/schema.json" }, diff --git a/tsconfig.json b/tsconfig.json index 9a5d2dd9..e550cf8b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,5 +9,6 @@ "strictNullChecks": true, "esModuleInterop": true, "declaration": false, + "resolveJsonModule": true } }