@aglyn/aglyn 1.0.0-beta.221 → 1.0.0-beta.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -11
- package/src/lib/app-utils/agent-openapi.js +1 -1
- package/src/lib/app-utils/agent-openapi.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +3 -2
- package/src/lib/app-utils/analytics-events.js +3 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/api-plugins.d.ts +30 -0
- package/src/lib/app-utils/api-plugins.js +24 -1
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/author-html.d.ts +1 -1
- package/src/lib/app-utils/author-html.js.map +1 -1
- package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
- package/src/lib/app-utils/bandwidth-cap.js +17 -1
- package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
- package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
- package/src/lib/app-utils/compose-reusable-components.js +116 -26
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +4 -0
- package/src/lib/app-utils/console-routes.js +3 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +71 -0
- package/src/lib/app-utils/contacts.js +55 -0
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/contexts.d.ts +1 -0
- package/src/lib/app-utils/contexts.js +1 -0
- package/src/lib/app-utils/contexts.js.map +1 -1
- package/src/lib/app-utils/crm-email-templates.js +21 -4
- package/src/lib/app-utils/crm-email-templates.js.map +1 -1
- package/src/lib/app-utils/crm-kinds.d.ts +2 -0
- package/src/lib/app-utils/crm-kinds.js +1 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +1199 -67
- package/src/lib/app-utils/crm.js +1951 -310
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/csv-import.d.ts +10 -38
- package/src/lib/app-utils/csv-import.js +17 -42
- package/src/lib/app-utils/csv-import.js.map +1 -1
- package/src/lib/app-utils/csv.d.ts +32 -11
- package/src/lib/app-utils/csv.js +43 -5
- package/src/lib/app-utils/csv.js.map +1 -1
- package/src/lib/app-utils/default-site.d.ts +116 -0
- package/src/lib/app-utils/default-site.js +1024 -0
- package/src/lib/app-utils/default-site.js.map +1 -0
- package/src/lib/app-utils/docs-help.generated.d.ts +22 -22
- package/src/lib/app-utils/docs-help.generated.js +44 -10
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +738 -150
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
- package/src/lib/app-utils/draft-placeholders.js +115 -0
- package/src/lib/app-utils/draft-placeholders.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
- package/src/lib/app-utils/entity-picker-context.js +4 -4
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
- package/src/lib/app-utils/expand-repeatables.js +88 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +33 -22
- package/src/lib/app-utils/forms.js +47 -28
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/full-use-cost.d.ts +277 -0
- package/src/lib/app-utils/full-use-cost.js +421 -0
- package/src/lib/app-utils/full-use-cost.js.map +1 -0
- package/src/lib/app-utils/health-report.js +11 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/llms-txt.d.ts +20 -0
- package/src/lib/app-utils/llms-txt.js +7 -1
- package/src/lib/app-utils/llms-txt.js.map +1 -1
- package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
- package/src/lib/app-utils/media-asset-facts.js +40 -13
- package/src/lib/app-utils/media-asset-facts.js.map +1 -1
- package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
- package/src/lib/app-utils/media-bandwidth.js +94 -0
- package/src/lib/app-utils/media-bandwidth.js.map +1 -0
- package/src/lib/app-utils/media-ref.d.ts +241 -33
- package/src/lib/app-utils/media-ref.js +324 -43
- package/src/lib/app-utils/media-ref.js.map +1 -1
- package/src/lib/app-utils/media-src.d.ts +8 -8
- package/src/lib/app-utils/media-src.js +27 -11
- package/src/lib/app-utils/media-src.js.map +1 -1
- package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
- package/src/lib/app-utils/media-takedown-reach.js +6 -3
- package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +1 -1
- package/src/lib/app-utils/notifications.js +2 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/organizations.d.ts +51 -2
- package/src/lib/app-utils/organizations.js +56 -3
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/page-record-sources.d.ts +87 -0
- package/src/lib/app-utils/page-record-sources.js +53 -0
- package/src/lib/app-utils/page-record-sources.js.map +1 -0
- package/src/lib/app-utils/paid-media-source.d.ts +3 -7
- package/src/lib/app-utils/paid-media-source.js +5 -19
- package/src/lib/app-utils/paid-media-source.js.map +1 -1
- package/src/lib/app-utils/person-erasure.d.ts +5 -2
- package/src/lib/app-utils/person-erasure.js.map +1 -1
- package/src/lib/app-utils/picklists.d.ts +187 -0
- package/src/lib/app-utils/picklists.js +320 -0
- package/src/lib/app-utils/picklists.js.map +1 -0
- package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
- package/src/lib/app-utils/plan-entitlements.js +309 -115
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +1 -1
- package/src/lib/app-utils/release-flags.js +11 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/rendered-widths.d.ts +121 -0
- package/src/lib/app-utils/rendered-widths.js +211 -0
- package/src/lib/app-utils/rendered-widths.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +2 -1
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +154 -36
- package/src/lib/app-utils/screen-route.js +185 -40
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +11 -1
- package/src/lib/app-utils/seo-audit.js +33 -13
- package/src/lib/app-utils/seo-audit.js.map +1 -1
- package/src/lib/app-utils/seo-keywords.d.ts +11 -0
- package/src/lib/app-utils/seo-keywords.js +19 -5
- package/src/lib/app-utils/seo-keywords.js.map +1 -1
- package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
- package/src/lib/app-utils/seo-page-facts.js +136 -31
- package/src/lib/app-utils/seo-page-facts.js.map +1 -1
- package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
- package/src/lib/app-utils/seo-site-scan.js +113 -12
- package/src/lib/app-utils/seo-site-scan.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +5 -4
- package/src/lib/app-utils/server.js +14 -5
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-icon-set.d.ts +195 -0
- package/src/lib/app-utils/site-icon-set.js +351 -0
- package/src/lib/app-utils/site-icon-set.js.map +1 -0
- package/src/lib/app-utils/site-interactions.d.ts +101 -4
- package/src/lib/app-utils/site-interactions.js +120 -4
- package/src/lib/app-utils/site-interactions.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
- package/src/lib/app-utils/starter-template-nodes.js +3 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +5 -19
- package/src/lib/app-utils/starter-templates.js +5 -49
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
- package/src/lib/app-utils/transfer-launcher-context.js +23 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
- package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
- package/src/lib/app-utils/transfer-resume-import.js +128 -0
- package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
- package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
- package/src/lib/app-utils/upgrade-proposal.js +95 -0
- package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
- package/src/lib/app-utils/utc-month.d.ts +33 -0
- package/src/lib/app-utils/utc-month.js +35 -0
- package/src/lib/app-utils/utc-month.js.map +1 -0
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/wire-nodes.d.ts +39 -19
- package/src/lib/app-utils/wire-nodes.js +120 -34
- package/src/lib/app-utils/wire-nodes.js.map +1 -1
- package/src/lib/data-transfer/access.d.ts +74 -0
- package/src/lib/data-transfer/access.js +93 -0
- package/src/lib/data-transfer/access.js.map +1 -0
- package/src/lib/data-transfer/derive.d.ts +246 -0
- package/src/lib/data-transfer/derive.js +1007 -0
- package/src/lib/data-transfer/derive.js.map +1 -0
- package/src/lib/data-transfer/export-file.d.ts +46 -0
- package/src/lib/data-transfer/export-file.js +146 -0
- package/src/lib/data-transfer/export-file.js.map +1 -0
- package/src/lib/data-transfer/field-catalog.d.ts +134 -0
- package/src/lib/data-transfer/field-catalog.js +278 -0
- package/src/lib/data-transfer/field-catalog.js.map +1 -0
- package/src/lib/data-transfer/header-match.d.ts +129 -0
- package/src/lib/data-transfer/header-match.js +367 -0
- package/src/lib/data-transfer/header-match.js.map +1 -0
- package/src/lib/data-transfer/index.d.ts +34 -0
- package/src/lib/data-transfer/index.js +40 -0
- package/src/lib/data-transfer/index.js.map +1 -0
- package/src/lib/data-transfer/job.d.ts +190 -0
- package/src/lib/data-transfer/job.js +229 -0
- package/src/lib/data-transfer/job.js.map +1 -0
- package/src/lib/data-transfer/lookup.d.ts +45 -0
- package/src/lib/data-transfer/lookup.js +71 -0
- package/src/lib/data-transfer/lookup.js.map +1 -0
- package/src/lib/data-transfer/match.d.ts +99 -0
- package/src/lib/data-transfer/match.js +244 -0
- package/src/lib/data-transfer/match.js.map +1 -0
- package/src/lib/data-transfer/package-plan.d.ts +205 -0
- package/src/lib/data-transfer/package-plan.js +475 -0
- package/src/lib/data-transfer/package-plan.js.map +1 -0
- package/src/lib/data-transfer/package.d.ts +181 -0
- package/src/lib/data-transfer/package.js +426 -0
- package/src/lib/data-transfer/package.js.map +1 -0
- package/src/lib/data-transfer/picklist-map.d.ts +139 -0
- package/src/lib/data-transfer/picklist-map.js +271 -0
- package/src/lib/data-transfer/picklist-map.js.map +1 -0
- package/src/lib/data-transfer/plan.d.ts +176 -0
- package/src/lib/data-transfer/plan.js +451 -0
- package/src/lib/data-transfer/plan.js.map +1 -0
- package/src/lib/data-transfer/policy.d.ts +170 -0
- package/src/lib/data-transfer/policy.js +378 -0
- package/src/lib/data-transfer/policy.js.map +1 -0
- package/src/lib/data-transfer/resource.d.ts +211 -0
- package/src/lib/data-transfer/resource.js +241 -0
- package/src/lib/data-transfer/resource.js.map +1 -0
- package/src/lib/data-transfer/review.d.ts +70 -0
- package/src/lib/data-transfer/review.js +233 -0
- package/src/lib/data-transfer/review.js.map +1 -0
- package/src/lib/data-transfer/similarity.d.ts +40 -0
- package/src/lib/data-transfer/similarity.js +106 -0
- package/src/lib/data-transfer/similarity.js.map +1 -0
- package/src/lib/data-transfer/site-package.d.ts +365 -0
- package/src/lib/data-transfer/site-package.js +1077 -0
- package/src/lib/data-transfer/site-package.js.map +1 -0
- package/src/lib/data-transfer/source.d.ts +74 -0
- package/src/lib/data-transfer/source.js +293 -0
- package/src/lib/data-transfer/source.js.map +1 -0
- package/src/lib/data-transfer/transfer-api.d.ts +898 -0
- package/src/lib/data-transfer/transfer-api.js +107 -0
- package/src/lib/data-transfer/transfer-api.js.map +1 -0
- package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
- package/src/lib/plugin-manager/feature-plugins.js +48 -3
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +50 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
- package/src/lib/plugin-manager/interaction-recipes.js +104 -0
- package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
- package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
- package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
- package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
- package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
- package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-jobs.js +7 -7
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
- package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
- package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
- package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
- package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
- package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
- package/src/lib/plugin-manager/plugin-person-records.js +107 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
- package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
- package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
- package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
- package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
- package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
- package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
- package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
- package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
- package/src/lib/plugin-manager/plugin-revocations.js +47 -0
- package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
- package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
- package/src/lib/plugin-manager/plugin-site-export.js +23 -10
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
- package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
- package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
- package/src/lib/app-utils/actions.d.ts +0 -310
- package/src/lib/app-utils/actions.js +0 -506
- package/src/lib/app-utils/actions.js.map +0 -1
- package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
- package/src/lib/app-utils/automation-placeholders.js +0 -207
- package/src/lib/app-utils/automation-placeholders.js.map +0 -1
- package/src/lib/app-utils/dataset-models.d.ts +0 -339
- package/src/lib/app-utils/dataset-models.js +0 -722
- package/src/lib/app-utils/dataset-models.js.map +0 -1
- package/src/lib/app-utils/datasets.d.ts +0 -171
- package/src/lib/app-utils/datasets.js +0 -227
- package/src/lib/app-utils/datasets.js.map +0 -1
- package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
- package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
- package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
- package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
- package/src/lib/plugin-manager/order-fulfilment.js +0 -104
- package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
package/src/lib/app-utils/crm.js
CHANGED
|
@@ -44,10 +44,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
44
44
|
* would otherwise write for itself. No Firestore, no React.
|
|
45
45
|
*/ import { normalizeAddress, normalizePhone } from "../foundation/index.js";
|
|
46
46
|
import { consentGroupScope } from "./consent-groups.js";
|
|
47
|
-
import { CONTACT_LIFECYCLE_STAGES, isContactLifecycleStage } from "./crm-kinds.js";
|
|
48
|
-
import { CONTACT_ALTERNATE_EMAILS_FIELD, CONTACT_FACETS_FIELD, CONTACT_SOURCE_LABELS, normalizeContactEmail, readContactFacet } from "./contacts.js";
|
|
47
|
+
import { CONTACT_LIFECYCLE_STAGES, CRM_TASK_KINDS, isContactLifecycleStage } from "./crm-kinds.js";
|
|
48
|
+
import { CONTACT_ALTERNATE_EMAILS_FIELD, CONTACT_FACETS_FIELD, CONTACT_SOURCE_LABELS, composeContactName, normalizeContactEmail, readContactFacet } from "./contacts.js";
|
|
49
49
|
import { isEmailStateStatus } from "./email-state.js";
|
|
50
50
|
import { NAME_TOKEN_MAX_PREFIX, nameSearchKey, nameSearchTokens, SCOPED_SEARCH_JOIN, scopedSearchTokens } from "./name-search.js";
|
|
51
|
+
import { effectivePicklistValueSet, isStandardPicklistValueId, judgePicklistValue, mintPicklistValueId, normalizePicklistLabel, normalizePicklistValueSet, PICKLIST_VALUES_MAX, picklistActiveValues, picklistDefaultLabel, picklistFromLabels, picklistOptions, picklistRank, picklistRefusalSentence, picklistValueByLabel } from "./picklists.js";
|
|
51
52
|
import { MAX_SCOPE_HOSTS, ORG_SCOPE_TOKEN } from "./scope-tokens.js";
|
|
52
53
|
// The fixed vocabularies and their guards live in a leaf module; every name
|
|
53
54
|
// stays importable from here.
|
|
@@ -201,62 +202,300 @@ export * from "./crm-kinds.js";
|
|
|
201
202
|
var _advanceContactLifecycleStage;
|
|
202
203
|
return (_advanceContactLifecycleStage = advanceContactLifecycleStage(current, 'customer')) != null ? _advanceContactLifecycleStage : 'customer';
|
|
203
204
|
}
|
|
205
|
+
/*==========================================
|
|
206
|
+
* FORECAST CATEGORIES (AGL-3516).
|
|
207
|
+
*
|
|
208
|
+
* Salesforce's Opportunity forecast categories, a FIXED set — not a
|
|
209
|
+
* picklist an org edits, as in Salesforce, because a forecast rolls up by
|
|
210
|
+
* them and a renamed or added category would be one no forecast knows. A
|
|
211
|
+
* stage names the category a deal takes on landing in it; the deal stores
|
|
212
|
+
* its own copy, stamped at every stage move, which the deal page may then
|
|
213
|
+
* change for that deal alone.
|
|
214
|
+
*=========================================*/ export const CRM_FORECAST_CATEGORIES = [
|
|
215
|
+
'omitted',
|
|
216
|
+
'pipeline',
|
|
217
|
+
'bestCase',
|
|
218
|
+
'commit',
|
|
219
|
+
'closed'
|
|
220
|
+
];
|
|
221
|
+
export const CRM_FORECAST_CATEGORY_LABELS = {
|
|
222
|
+
omitted: 'Omitted',
|
|
223
|
+
pipeline: 'Pipeline',
|
|
224
|
+
bestCase: 'Best Case',
|
|
225
|
+
commit: 'Commit',
|
|
226
|
+
closed: 'Closed'
|
|
227
|
+
};
|
|
228
|
+
export function isCrmForecastCategory(value) {
|
|
229
|
+
return typeof value === 'string' && CRM_FORECAST_CATEGORIES.includes(value);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* A category as typed — the stored key or its label in any case and
|
|
233
|
+
* spacing ("Best Case", "best case", `bestCase`) — or `null`.
|
|
234
|
+
*/ export function readCrmForecastCategory(value) {
|
|
235
|
+
var _CRM_FORECAST_CATEGORIES_find;
|
|
236
|
+
if (isCrmForecastCategory(value)) return value;
|
|
237
|
+
const key = String(value != null ? value : '').trim().replace(/\s+/g, ' ').toLowerCase();
|
|
238
|
+
if (!key) return null;
|
|
239
|
+
return (_CRM_FORECAST_CATEGORIES_find = CRM_FORECAST_CATEGORIES.find((category)=>category.toLowerCase() === key || CRM_FORECAST_CATEGORY_LABELS[category].toLowerCase() === key)) != null ? _CRM_FORECAST_CATEGORIES_find : null;
|
|
240
|
+
}
|
|
241
|
+
/** The category a stage stamps: its own, else its kind's. */ export function dealStageForecastCategory(stage) {
|
|
242
|
+
if (isCrmForecastCategory(stage.forecastCategory)) return stage.forecastCategory;
|
|
243
|
+
return stage.kind === 'won' ? 'closed' : stage.kind === 'lost' ? 'omitted' : 'pipeline';
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* A deal's forecast category as every reader takes it: the deal's own,
|
|
247
|
+
* else its stage's, else its status's — a deal written before the field
|
|
248
|
+
* existed forecasts where its stage puts it.
|
|
249
|
+
*/ export function dealForecastCategory(deal, stage) {
|
|
250
|
+
if (isCrmForecastCategory(deal.forecastCategory)) return deal.forecastCategory;
|
|
251
|
+
if (stage) return dealStageForecastCategory(stage);
|
|
252
|
+
return deal.status === 'won' ? 'closed' : deal.status === 'lost' ? 'omitted' : 'pipeline';
|
|
253
|
+
}
|
|
254
|
+
/** The longest Next step a deal keeps — Salesforce's own 255. */ export const DEAL_NEXT_STEP_MAX = 255;
|
|
255
|
+
/**
|
|
256
|
+
* A probability override as stored: a whole number 0–100, or `null` for
|
|
257
|
+
* none (the stage's applies). Anything that is not a number in range is
|
|
258
|
+
* `undefined` — unreadable, for the writer to refuse.
|
|
259
|
+
*/ export function readDealProbability(value) {
|
|
260
|
+
if (value === null || value === undefined) return null;
|
|
261
|
+
if (typeof value === 'string' && !value.trim()) return null;
|
|
262
|
+
const number = typeof value === 'number' ? value : Number(String(value).trim().replace(/%$/, ''));
|
|
263
|
+
if (!Number.isFinite(number) || !Number.isInteger(number) || number < 0 || number > 100) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
return number;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* A deal's probability: its own override when it holds one, else its
|
|
270
|
+
* stage's, else `null` for a stage the pipeline no longer has.
|
|
271
|
+
*/ export function dealProbability(deal, stage) {
|
|
272
|
+
const own = readDealProbability(deal.probability);
|
|
273
|
+
if (typeof own === 'number') return own;
|
|
274
|
+
if (!stage) return null;
|
|
275
|
+
return Math.min(100, Math.max(0, Number(stage.probability) || 0));
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* What a stage move writes besides the stage, the status and the clocks:
|
|
279
|
+
* the new stage's forecast category, and the probability override CLEARED
|
|
280
|
+
* — Salesforce re-defaults a probability when the stage changes, so an
|
|
281
|
+
* override typed for the last stage does not follow the deal into the next.
|
|
282
|
+
* A write that sets either field itself, in the same request, wins.
|
|
283
|
+
*/ export function dealStageMoveFields(stage) {
|
|
284
|
+
return {
|
|
285
|
+
forecastCategory: dealStageForecastCategory(stage),
|
|
286
|
+
probability: null
|
|
287
|
+
};
|
|
288
|
+
}
|
|
204
289
|
/** Whether a pipeline has been retired — see {@link CrmPipeline.archivedAt}. */ export function isPipelineArchived(pipeline) {
|
|
205
290
|
return typeof (pipeline == null ? void 0 : pipeline.archivedAt) === 'number' && pipeline.archivedAt > 0;
|
|
206
291
|
}
|
|
207
292
|
/**
|
|
208
|
-
* The stages a fresh pipeline starts with
|
|
293
|
+
* The stages a fresh pipeline starts with: Salesforce's standard
|
|
294
|
+
* Opportunity stages, with its probabilities and forecast categories
|
|
295
|
+
* (AGL-3516). A pipeline seeded before them keeps the stages it was seeded
|
|
296
|
+
* with — nothing migrates a stored pipeline.
|
|
297
|
+
*
|
|
298
|
+
* The closing stages keep the ids `won` and `lost` every pipeline has had,
|
|
299
|
+
* so an automation filter naming them reads the same on old pipelines and
|
|
300
|
+
* new ones.
|
|
209
301
|
*
|
|
210
302
|
* Readonly on purpose: a pipeline document stores its own COPY (`[...]`), so
|
|
211
303
|
* a merchant editing their stages must not be editing the module's default,
|
|
212
304
|
* and a second pipeline seeded later must start from the original set.
|
|
213
305
|
*/ export const DEFAULT_DEAL_STAGES = [
|
|
214
306
|
{
|
|
215
|
-
id: '
|
|
216
|
-
name: '
|
|
307
|
+
id: 'prospecting',
|
|
308
|
+
name: 'Prospecting',
|
|
217
309
|
order: 0,
|
|
218
310
|
probability: 10,
|
|
219
|
-
kind: 'open'
|
|
311
|
+
kind: 'open',
|
|
312
|
+
forecastCategory: 'pipeline'
|
|
220
313
|
},
|
|
221
314
|
{
|
|
222
|
-
id: '
|
|
223
|
-
name: '
|
|
315
|
+
id: 'qualification',
|
|
316
|
+
name: 'Qualification',
|
|
224
317
|
order: 1,
|
|
225
|
-
probability:
|
|
226
|
-
kind: 'open'
|
|
318
|
+
probability: 10,
|
|
319
|
+
kind: 'open',
|
|
320
|
+
forecastCategory: 'pipeline'
|
|
227
321
|
},
|
|
228
322
|
{
|
|
229
|
-
id: '
|
|
230
|
-
name: '
|
|
323
|
+
id: 'needs-analysis',
|
|
324
|
+
name: 'Needs Analysis',
|
|
231
325
|
order: 2,
|
|
232
|
-
probability:
|
|
233
|
-
kind: 'open'
|
|
326
|
+
probability: 20,
|
|
327
|
+
kind: 'open',
|
|
328
|
+
forecastCategory: 'pipeline'
|
|
234
329
|
},
|
|
235
330
|
{
|
|
236
|
-
id: '
|
|
237
|
-
name: '
|
|
331
|
+
id: 'value-proposition',
|
|
332
|
+
name: 'Value Proposition',
|
|
238
333
|
order: 3,
|
|
334
|
+
probability: 50,
|
|
335
|
+
kind: 'open',
|
|
336
|
+
forecastCategory: 'pipeline'
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: 'id-decision-makers',
|
|
340
|
+
name: 'Id. Decision Makers',
|
|
341
|
+
order: 4,
|
|
239
342
|
probability: 60,
|
|
240
|
-
kind: 'open'
|
|
343
|
+
kind: 'open',
|
|
344
|
+
forecastCategory: 'pipeline'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'perception-analysis',
|
|
348
|
+
name: 'Perception Analysis',
|
|
349
|
+
order: 5,
|
|
350
|
+
probability: 70,
|
|
351
|
+
kind: 'open',
|
|
352
|
+
forecastCategory: 'pipeline'
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'proposal-price-quote',
|
|
356
|
+
name: 'Proposal/Price Quote',
|
|
357
|
+
order: 6,
|
|
358
|
+
probability: 75,
|
|
359
|
+
kind: 'open',
|
|
360
|
+
forecastCategory: 'bestCase'
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: 'negotiation-review',
|
|
364
|
+
name: 'Negotiation/Review',
|
|
365
|
+
order: 7,
|
|
366
|
+
probability: 90,
|
|
367
|
+
kind: 'open',
|
|
368
|
+
forecastCategory: 'commit'
|
|
241
369
|
},
|
|
242
370
|
{
|
|
243
371
|
id: 'won',
|
|
244
|
-
name: 'Won',
|
|
245
|
-
order:
|
|
372
|
+
name: 'Closed Won',
|
|
373
|
+
order: 8,
|
|
246
374
|
probability: 100,
|
|
247
|
-
kind: 'won'
|
|
375
|
+
kind: 'won',
|
|
376
|
+
forecastCategory: 'closed'
|
|
248
377
|
},
|
|
249
378
|
{
|
|
250
379
|
id: 'lost',
|
|
251
|
-
name: 'Lost',
|
|
252
|
-
order:
|
|
380
|
+
name: 'Closed Lost',
|
|
381
|
+
order: 9,
|
|
253
382
|
probability: 0,
|
|
254
|
-
kind: 'lost'
|
|
383
|
+
kind: 'lost',
|
|
384
|
+
forecastCategory: 'omitted'
|
|
255
385
|
}
|
|
256
386
|
];
|
|
257
387
|
/** The most lines one deal carries — a quote, not a catalog. */ export const DEAL_LINE_ITEMS_MAX = 50;
|
|
258
388
|
export const DEAL_LINE_ITEM_NAME_MAX = 120;
|
|
259
389
|
/** Units per line; past this the number is a data-entry slip. */ export const DEAL_LINE_ITEM_QUANTITY_MAX = 1000000;
|
|
390
|
+
/*------------------------------------------
|
|
391
|
+
* OPPORTUNITY CONTACT ROLES (AGL-3521).
|
|
392
|
+
*
|
|
393
|
+
* A deal names any number of contacts, each with the part they play — a
|
|
394
|
+
* label of the org's `opportunityContactRole` picklist, or none — and at
|
|
395
|
+
* most one of them Primary. The Primary is `contactId`, which every reader
|
|
396
|
+
* that existed before roles keeps reading: the won-deal customer floor, the
|
|
397
|
+
* send-email button, the CSV's Contact column, the REST filter. So the two
|
|
398
|
+
* are written together by every door — setting a Primary sets `contactId`,
|
|
399
|
+
* and a door that sets `contactId` the old way makes that contact Primary,
|
|
400
|
+
* adding them without a role when the deal did not name them.
|
|
401
|
+
*
|
|
402
|
+
* A deal written before roles holds `contactId` alone, which reads as one
|
|
403
|
+
* Primary row with no role; its first write through any door stores it.
|
|
404
|
+
* Where the two disagree — a write from a door that knows only
|
|
405
|
+
* `contactId` — `contactId` wins, because it is what every older reader
|
|
406
|
+
* already acted on.
|
|
407
|
+
*-----------------------------------------*/ /** The most contacts one deal names — a buying committee, not a mailing list. */ export const DEAL_CONTACT_ROLES_MAX = 50;
|
|
408
|
+
/**
|
|
409
|
+
* Stored or sent roles, read defensively: a row without a usable contact
|
|
410
|
+
* id is dropped, a contact named twice keeps its first row, only the first
|
|
411
|
+
* Primary stays Primary, and the list stops at {@link DEAL_CONTACT_ROLES_MAX}.
|
|
412
|
+
*/ export function readDealContactRoles(raw) {
|
|
413
|
+
if (!Array.isArray(raw)) return [];
|
|
414
|
+
const roles = [];
|
|
415
|
+
const seen = new Set();
|
|
416
|
+
let primaryTaken = false;
|
|
417
|
+
for (const entry of raw){
|
|
418
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
419
|
+
const row = entry;
|
|
420
|
+
const contactId = typeof row['contactId'] === 'string' ? row['contactId'].trim() : '';
|
|
421
|
+
if (!contactId || contactId.length > 200 || contactId.includes('/') || seen.has(contactId)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
seen.add(contactId);
|
|
425
|
+
const role = typeof row['role'] === 'string' ? normalizePicklistLabel(row['role']) : '';
|
|
426
|
+
const primary = row['primary'] === true && !primaryTaken;
|
|
427
|
+
if (primary) primaryTaken = true;
|
|
428
|
+
roles.push(_extends({
|
|
429
|
+
contactId
|
|
430
|
+
}, role ? {
|
|
431
|
+
role
|
|
432
|
+
} : {}, {
|
|
433
|
+
primary
|
|
434
|
+
}));
|
|
435
|
+
if (roles.length >= DEAL_CONTACT_ROLES_MAX) break;
|
|
436
|
+
}
|
|
437
|
+
return roles;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* `roles` with `contactId` as the one Primary — added first, with no role,
|
|
441
|
+
* when the list does not name them — or, for `null`, with no Primary.
|
|
442
|
+
*/ export function dealContactRolesWithPrimary(roles, contactId) {
|
|
443
|
+
const id = typeof contactId === 'string' ? contactId.trim() : '';
|
|
444
|
+
const next = roles.map((row)=>_extends({}, row, {
|
|
445
|
+
primary: Boolean(id) && row.contactId === id
|
|
446
|
+
}));
|
|
447
|
+
if (id && !next.some((row)=>row.contactId === id)) next.unshift({
|
|
448
|
+
contactId: id,
|
|
449
|
+
primary: true
|
|
450
|
+
});
|
|
451
|
+
return next.slice(0, DEAL_CONTACT_ROLES_MAX);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The roles a deal holds, in step with its `contactId`: the stored list
|
|
455
|
+
* with the contact `contactId` names as its Primary — so a deal written
|
|
456
|
+
* before roles reads as its one contact, Primary, with no role.
|
|
457
|
+
*/ export function dealContactRolesOf(deal) {
|
|
458
|
+
const contactId = typeof deal.contactId === 'string' ? deal.contactId : null;
|
|
459
|
+
return dealContactRolesWithPrimary(readDealContactRoles(deal.contactRoles), contactId);
|
|
460
|
+
}
|
|
461
|
+
/** The Primary's contact id, or `null` for a deal with none. */ export function dealPrimaryContactId(roles) {
|
|
462
|
+
var _ref;
|
|
463
|
+
var _roles_find;
|
|
464
|
+
return (_ref = (_roles_find = roles.find((row)=>row.primary)) == null ? void 0 : _roles_find.contactId) != null ? _ref : null;
|
|
465
|
+
}
|
|
466
|
+
/** `roles` without one contact — a delete or an erasure of that person. */ export function dealContactRolesWithout(roles, contactId) {
|
|
467
|
+
return roles.filter((row)=>row.contactId !== contactId);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* `roles` with every row naming `from` moved to `to` — a contact merge.
|
|
471
|
+
* When both are on the deal the survivor keeps its own row, taking the
|
|
472
|
+
* merged row's role where it has none and its Primary where it was.
|
|
473
|
+
*/ export function dealContactRolesRepointed(roles, from, to) {
|
|
474
|
+
const merged = roles.find((row)=>row.contactId === from);
|
|
475
|
+
if (!merged || from === to) return [
|
|
476
|
+
...roles
|
|
477
|
+
];
|
|
478
|
+
if (!roles.some((row)=>row.contactId === to)) {
|
|
479
|
+
return roles.map((row)=>row.contactId === from ? _extends({}, row, {
|
|
480
|
+
contactId: to
|
|
481
|
+
}) : row);
|
|
482
|
+
}
|
|
483
|
+
return roles.filter((row)=>row.contactId !== from).map((row)=>row.contactId === to ? _extends({}, row, !row.role && merged.role ? {
|
|
484
|
+
role: merged.role
|
|
485
|
+
} : {}, {
|
|
486
|
+
primary: row.primary || merged.primary
|
|
487
|
+
}) : row);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* What a deal stores for a list of roles: the list, and the `contactId`
|
|
491
|
+
* its Primary names — `null` for none, which a writer turns into a delete.
|
|
492
|
+
*/ export function dealContactRoleFields(roles) {
|
|
493
|
+
const contactRoles = readDealContactRoles(roles);
|
|
494
|
+
return {
|
|
495
|
+
contactRoles,
|
|
496
|
+
contactId: dealPrimaryContactId(contactRoles)
|
|
497
|
+
};
|
|
498
|
+
}
|
|
260
499
|
export const CRM_TASK_KIND_LABELS = {
|
|
261
500
|
call: 'Call',
|
|
262
501
|
email: 'Email',
|
|
@@ -386,6 +625,34 @@ export function isCrmEmailDeliveryState(value) {
|
|
|
386
625
|
] : []
|
|
387
626
|
];
|
|
388
627
|
}
|
|
628
|
+
/** The directions each kind takes, in the order a select lists them; none for the rest. */ export const CRM_ACTIVITY_DIRECTIONS = {
|
|
629
|
+
call: [
|
|
630
|
+
'outbound',
|
|
631
|
+
'inbound',
|
|
632
|
+
'internal'
|
|
633
|
+
],
|
|
634
|
+
email: [
|
|
635
|
+
'outbound',
|
|
636
|
+
'inbound'
|
|
637
|
+
]
|
|
638
|
+
};
|
|
639
|
+
export const CRM_ACTIVITY_DIRECTION_LABELS = {
|
|
640
|
+
outbound: 'Outbound',
|
|
641
|
+
inbound: 'Inbound',
|
|
642
|
+
internal: 'Internal'
|
|
643
|
+
};
|
|
644
|
+
/** `value` when `kind` takes it as a direction, else `null`. */ export function crmActivityDirection(kind, value) {
|
|
645
|
+
var _CRM_ACTIVITY_DIRECTIONS_kind;
|
|
646
|
+
const allowed = (_CRM_ACTIVITY_DIRECTIONS_kind = CRM_ACTIVITY_DIRECTIONS[kind]) != null ? _CRM_ACTIVITY_DIRECTIONS_kind : [];
|
|
647
|
+
const text = String(value != null ? value : '').trim().toLowerCase();
|
|
648
|
+
return allowed.includes(text) ? text : null;
|
|
649
|
+
}
|
|
650
|
+
/** "Inbound call", "Outbound email", "Internal call" — or the kind alone with no direction. */ export function crmActivityKindTitle(kind, direction) {
|
|
651
|
+
var _CRM_ACTIVITY_KIND_LABELS_kind;
|
|
652
|
+
const known = crmActivityDirection(kind, direction);
|
|
653
|
+
const noun = (_CRM_ACTIVITY_KIND_LABELS_kind = CRM_ACTIVITY_KIND_LABELS[kind]) != null ? _CRM_ACTIVITY_KIND_LABELS_kind : String(kind);
|
|
654
|
+
return known ? `${CRM_ACTIVITY_DIRECTION_LABELS[known]} ${noun.toLowerCase()}` : noun;
|
|
655
|
+
}
|
|
389
656
|
/**
|
|
390
657
|
* The activity row a sent one-to-one email is logged as (AGL-2615).
|
|
391
658
|
*
|
|
@@ -648,12 +915,15 @@ export function isCrmFieldObject(value) {
|
|
|
648
915
|
* of itself. An open deal with no resolvable stage is worth nothing rather
|
|
649
916
|
* than everything: the pipeline lost the stage, and a forecast that filled
|
|
650
917
|
* the gap with 100% would be the most optimistic number available.
|
|
918
|
+
*
|
|
919
|
+
* An open deal's own probability override (AGL-3516) replaces its stage's
|
|
920
|
+
* — see {@link dealProbability}.
|
|
651
921
|
*/ export function weightedDealAmountCents(deal, stage) {
|
|
652
|
-
var _deal_amountCents;
|
|
922
|
+
var _deal_amountCents, _dealProbability;
|
|
653
923
|
const amount = Math.max(0, Math.round(Number((_deal_amountCents = deal.amountCents) != null ? _deal_amountCents : 0) || 0));
|
|
654
924
|
if (deal.status === 'won') return amount;
|
|
655
925
|
if (deal.status === 'lost' || !stage) return 0;
|
|
656
|
-
const probability =
|
|
926
|
+
const probability = (_dealProbability = dealProbability(deal, stage)) != null ? _dealProbability : 0;
|
|
657
927
|
return Math.round(amount * probability / 100);
|
|
658
928
|
}
|
|
659
929
|
/** Whether a deal's amount is derived — it has at least one line item. */ export function dealHasLineItems(deal) {
|
|
@@ -815,6 +1085,213 @@ export function isCrmFieldObject(value) {
|
|
|
815
1085
|
return null;
|
|
816
1086
|
}
|
|
817
1087
|
}
|
|
1088
|
+
/*==========================================
|
|
1089
|
+
* A COMPANY'S ACCOUNT FIELDS (AGL-3514).
|
|
1090
|
+
*
|
|
1091
|
+
* Salesforce's Account carries more than a name and a domain: a Type, an
|
|
1092
|
+
* Industry, a Rating, an Ownership and an Account Source, each a picklist;
|
|
1093
|
+
* an annual revenue and a head count; a fax, an account number, a site, a
|
|
1094
|
+
* ticker symbol and an SIC code; a shipping address beside the billing one;
|
|
1095
|
+
* and a parent account. Every door that writes a company — the drawer, the
|
|
1096
|
+
* REST resource, the import, a lead's conversion — reads them through this
|
|
1097
|
+
* block, so a value one door refuses is refused by all of them.
|
|
1098
|
+
*
|
|
1099
|
+
* ## Each field's three answers
|
|
1100
|
+
*
|
|
1101
|
+
* A door reads what a request said about a field: not named (`undefined`)
|
|
1102
|
+
* leaves the record alone, `null` or a blank clears it, and anything else
|
|
1103
|
+
* is normalized or refused by name. A picklist field stores the value's
|
|
1104
|
+
* LABEL, judged against the org's list with the record's current value
|
|
1105
|
+
* kept (see the picklist block); the Companies list filters by the key of
|
|
1106
|
+
* Type, Industry and Rating, which `crmCompanyListFields` writes beside
|
|
1107
|
+
* the label.
|
|
1108
|
+
*=========================================*/ /** Each company picklist field: the list it holds a value of, and the key the list filters by. */ export const CRM_COMPANY_PICKLIST_FIELDS = [
|
|
1109
|
+
{
|
|
1110
|
+
field: 'type',
|
|
1111
|
+
picklistId: 'accountType',
|
|
1112
|
+
keyField: 'typeKey'
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
field: 'industry',
|
|
1116
|
+
picklistId: 'industry',
|
|
1117
|
+
keyField: 'industryKey'
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
field: 'rating',
|
|
1121
|
+
picklistId: 'rating',
|
|
1122
|
+
keyField: 'ratingKey'
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
field: 'ownership',
|
|
1126
|
+
picklistId: 'ownership'
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
field: 'accountSource',
|
|
1130
|
+
picklistId: 'leadSource',
|
|
1131
|
+
keyField: 'accountSourceKey'
|
|
1132
|
+
}
|
|
1133
|
+
];
|
|
1134
|
+
/** The short text fields, each capped at Salesforce's own length. */ export const CRM_COMPANY_TEXT_MAX = {
|
|
1135
|
+
accountNumber: 40,
|
|
1136
|
+
site: 80,
|
|
1137
|
+
tickerSymbol: 20,
|
|
1138
|
+
sicCode: 20
|
|
1139
|
+
};
|
|
1140
|
+
/** The most employees a company records — Salesforce's eight digits. */ export const CRM_COMPANY_EMPLOYEES_MAX = 99999999;
|
|
1141
|
+
/** The account fields that are not picklists, as every door names them. */ export const CRM_COMPANY_ACCOUNT_FIELDS = [
|
|
1142
|
+
'annualRevenueCents',
|
|
1143
|
+
'currency',
|
|
1144
|
+
'numberOfEmployees',
|
|
1145
|
+
'fax',
|
|
1146
|
+
'accountNumber',
|
|
1147
|
+
'site',
|
|
1148
|
+
'tickerSymbol',
|
|
1149
|
+
'sicCode',
|
|
1150
|
+
'shippingAddress'
|
|
1151
|
+
];
|
|
1152
|
+
/** A short text field as stored: trimmed, single-spaced. */ export function normalizeCompanyText(value) {
|
|
1153
|
+
return String(value != null ? value : '').trim().replace(/\s+/g, ' ');
|
|
1154
|
+
}
|
|
1155
|
+
const blank = (value)=>value === null || typeof value === 'string' && value.trim() === '';
|
|
1156
|
+
/**
|
|
1157
|
+
* The account fields a request names, normalized, or the refusal of each
|
|
1158
|
+
* one that cannot be stored — see the block above. A value the door has
|
|
1159
|
+
* already parsed from text (the drawer's revenue, a file's head count)
|
|
1160
|
+
* arrives here as the number it parsed.
|
|
1161
|
+
*/ export function readCrmCompanyAccountFields(input) {
|
|
1162
|
+
const values = {};
|
|
1163
|
+
const errors = {};
|
|
1164
|
+
for (const field of CRM_COMPANY_ACCOUNT_FIELDS){
|
|
1165
|
+
const raw = input[field];
|
|
1166
|
+
if (raw === undefined) continue;
|
|
1167
|
+
if (blank(raw)) {
|
|
1168
|
+
values[field] = null;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
switch(field){
|
|
1172
|
+
case 'annualRevenueCents':
|
|
1173
|
+
if (typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0) values[field] = raw;
|
|
1174
|
+
else errors[field] = 'Must be a whole number of cents, 0 or more';
|
|
1175
|
+
break;
|
|
1176
|
+
case 'currency':
|
|
1177
|
+
{
|
|
1178
|
+
const code = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
1179
|
+
if (/^[a-z]{3}$/.test(code)) values[field] = code;
|
|
1180
|
+
else errors[field] = 'Must be a three-letter ISO 4217 code, like usd';
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case 'numberOfEmployees':
|
|
1184
|
+
if (typeof raw === 'number' && Number.isInteger(raw) && raw >= 0 && raw <= CRM_COMPANY_EMPLOYEES_MAX) {
|
|
1185
|
+
values[field] = raw;
|
|
1186
|
+
} else {
|
|
1187
|
+
errors[field] = `Must be a whole number from 0 to ${CRM_COMPANY_EMPLOYEES_MAX}`;
|
|
1188
|
+
}
|
|
1189
|
+
break;
|
|
1190
|
+
case 'fax':
|
|
1191
|
+
{
|
|
1192
|
+
const fax = typeof raw === 'string' ? normalizePhone(raw) : null;
|
|
1193
|
+
if (fax) values[field] = fax;
|
|
1194
|
+
else errors[field] = 'Must be a phone number with a country code, like +15125550123';
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
case 'shippingAddress':
|
|
1198
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
1199
|
+
values[field] = normalizeAddress(raw);
|
|
1200
|
+
} else {
|
|
1201
|
+
errors[field] = 'Must be an address object';
|
|
1202
|
+
}
|
|
1203
|
+
break;
|
|
1204
|
+
default:
|
|
1205
|
+
{
|
|
1206
|
+
const max = CRM_COMPANY_TEXT_MAX[field];
|
|
1207
|
+
if (typeof raw !== 'string' && typeof raw !== 'number') {
|
|
1208
|
+
errors[field] = 'Must be text';
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
const text = normalizeCompanyText(raw);
|
|
1212
|
+
if (text.length > max) errors[field] = `Must be at most ${max} characters`;
|
|
1213
|
+
else values[field] = text;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
values: values,
|
|
1219
|
+
errors
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* The picklist fields a request names, judged against the org's lists —
|
|
1224
|
+
* `lists` holds each list the caller read, and a list it did not read is
|
|
1225
|
+
* judged as the standard values alone. `current` is the record as stored:
|
|
1226
|
+
* its value is kept even when the list no longer offers it, which is how
|
|
1227
|
+
* a company whose industry was typed before Industry became a picklist
|
|
1228
|
+
* keeps it. On a create (`created`), a field not named starts from its
|
|
1229
|
+
* list's default when the org set one.
|
|
1230
|
+
*/ export function judgeCrmCompanyPicklists(lists, requested, options = {}) {
|
|
1231
|
+
const values = {};
|
|
1232
|
+
const errors = {};
|
|
1233
|
+
for (const { field, picklistId } of CRM_COMPANY_PICKLIST_FIELDS){
|
|
1234
|
+
var _lists_picklistId;
|
|
1235
|
+
var _options_current;
|
|
1236
|
+
const list = (_lists_picklistId = lists[picklistId]) != null ? _lists_picklistId : effectiveCrmPicklist(picklistId, undefined);
|
|
1237
|
+
const raw = requested[field];
|
|
1238
|
+
if (raw === undefined) {
|
|
1239
|
+
const fallback = options.created ? picklistDefaultLabel(list) : null;
|
|
1240
|
+
if (fallback) values[field] = fallback;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
if (blank(raw)) {
|
|
1244
|
+
values[field] = null;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (typeof raw !== 'string') {
|
|
1248
|
+
errors[field] = 'Must be text';
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const judged = judgeCrmPicklistValue(picklistId, list, raw, (_options_current = options.current) == null ? void 0 : _options_current[field]);
|
|
1252
|
+
if (judged.ok === false) errors[field] = judged.error;
|
|
1253
|
+
else values[field] = judged.value;
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
values,
|
|
1257
|
+
errors
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
/** How deep a chain of parent companies may run. */ export const CRM_COMPANY_PARENT_DEPTH_MAX = 25;
|
|
1261
|
+
/**
|
|
1262
|
+
* Why `parentId` cannot be the parent of `companyId` (`null` for a company
|
|
1263
|
+
* not yet created), or `null` when it can: a company is never its own
|
|
1264
|
+
* parent, the parent must be a company the writer can see, and a company
|
|
1265
|
+
* already UNDER this one cannot be put above it — the cycle Salesforce
|
|
1266
|
+
* refuses too. Walks up from the parent through `read`, at most
|
|
1267
|
+
* {@link CRM_COMPANY_PARENT_DEPTH_MAX} steps.
|
|
1268
|
+
*/ export async function crmCompanyParentRefusal(companyId, parentId, read) {
|
|
1269
|
+
if (companyId && parentId === companyId) return 'A company cannot be its own parent.';
|
|
1270
|
+
const parent = await read(parentId);
|
|
1271
|
+
if (!parent) return 'There is no such parent company.';
|
|
1272
|
+
const seen = new Set([
|
|
1273
|
+
parentId
|
|
1274
|
+
]);
|
|
1275
|
+
let cursor = parent.parentCompanyId;
|
|
1276
|
+
for(let depth = 1; cursor; depth += 1){
|
|
1277
|
+
var _ref;
|
|
1278
|
+
var _this;
|
|
1279
|
+
if (companyId && cursor === companyId) {
|
|
1280
|
+
return 'That company sits under this one, so it cannot be its parent.';
|
|
1281
|
+
}
|
|
1282
|
+
// A loop above that does not pass through this company is not this write's.
|
|
1283
|
+
if (seen.has(cursor)) return null;
|
|
1284
|
+
if (depth >= CRM_COMPANY_PARENT_DEPTH_MAX) {
|
|
1285
|
+
return `A company can sit at most ${CRM_COMPANY_PARENT_DEPTH_MAX} levels under another.`;
|
|
1286
|
+
}
|
|
1287
|
+
seen.add(cursor);
|
|
1288
|
+
cursor = (_ref = (_this = await read(cursor)) == null ? void 0 : _this.parentCompanyId) != null ? _ref : null;
|
|
1289
|
+
}
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
/** A stored parent id as a reader takes it, `null` for none. */ export function crmCompanyParentId(value) {
|
|
1293
|
+
return typeof value === 'string' && value.trim() !== '' ? value : null;
|
|
1294
|
+
}
|
|
818
1295
|
/**
|
|
819
1296
|
* Mailbox domains that belong to people rather than to a business: the
|
|
820
1297
|
* providers anybody can sign up with, and the ones an internet provider
|
|
@@ -1550,21 +2027,32 @@ export function isCrmLeadStatus(value) {
|
|
|
1550
2027
|
* `sources` is the capture door's record of which SURFACES met the person
|
|
1551
2028
|
* — `signup`, `booking`, `form:{id}`, `import` — and no file may rewrite
|
|
1552
2029
|
* it. `leadSource` is what Salesforce calls Lead Source: one of the org's
|
|
1553
|
-
* own picklist values ("Trade show", "
|
|
1554
|
-
*
|
|
2030
|
+
* own picklist values ("Trade show", "Webinar" — see THE CRM'S PICKLISTS
|
|
2031
|
+
* below), reported on and filtered by, and never derived. Two
|
|
1555
2032
|
* fields because they answer two questions, and a file that could write
|
|
1556
2033
|
* the first would be rewriting the site's own history.
|
|
1557
2034
|
*=========================================*/ /** The longest text one profile field holds — the contact's own cap. */ export const CRM_LEAD_TEXT_MAX = 120;
|
|
1558
2035
|
/** The most tags one lead keeps — the contact's own cap. */ export const CRM_LEAD_TAGS_MAX = 20;
|
|
1559
2036
|
/** The longest note a lead keeps — the lead page's own cap. */ export const CRM_LEAD_NOTES_MAX = 4000;
|
|
1560
2037
|
/** The profile's keys, in the order a card lists them. */ export const CRM_LEAD_PROFILE_KEYS = [
|
|
2038
|
+
'salutation',
|
|
2039
|
+
'firstName',
|
|
2040
|
+
'lastName',
|
|
1561
2041
|
'company',
|
|
1562
2042
|
'jobTitle',
|
|
1563
2043
|
'phone',
|
|
2044
|
+
'mobilePhone',
|
|
2045
|
+
'fax',
|
|
2046
|
+
'doNotCall',
|
|
1564
2047
|
'website',
|
|
1565
2048
|
'address',
|
|
1566
2049
|
'tags',
|
|
1567
|
-
'leadSource'
|
|
2050
|
+
'leadSource',
|
|
2051
|
+
'industry',
|
|
2052
|
+
'rating',
|
|
2053
|
+
'annualRevenueCents',
|
|
2054
|
+
'currency',
|
|
2055
|
+
'numberOfEmployees'
|
|
1568
2056
|
];
|
|
1569
2057
|
/** The sentence an unreadable phone number is refused with, under the field. */ export const CRM_LEAD_PHONE_REFUSAL = 'That phone number could not be read. Enter it with its country code, like ' + '+1 512 555 0107.';
|
|
1570
2058
|
/** The sentence an unreadable website is refused with, under the field. */ export const CRM_LEAD_WEBSITE_REFUSAL = 'Enter a web address, like acme.com';
|
|
@@ -1589,6 +2077,7 @@ export function isCrmLeadStatus(value) {
|
|
|
1589
2077
|
* way a company's is; a phone the way a contact's is; both refuse rather
|
|
1590
2078
|
* than store a value the record would then render as a link to nowhere.
|
|
1591
2079
|
*/ export function normalizeCrmLeadProfile(input) {
|
|
2080
|
+
var _body_annualRevenueCents, _body_currency;
|
|
1592
2081
|
const body = input != null ? input : {};
|
|
1593
2082
|
const patch = {};
|
|
1594
2083
|
const errors = {};
|
|
@@ -1601,14 +2090,60 @@ export function isCrmLeadStatus(value) {
|
|
|
1601
2090
|
text('company');
|
|
1602
2091
|
text('jobTitle');
|
|
1603
2092
|
text('leadSource');
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2093
|
+
text('salutation');
|
|
2094
|
+
text('industry');
|
|
2095
|
+
text('rating');
|
|
2096
|
+
// A name part as the contact's facet keeps one — see `composeContactName`.
|
|
2097
|
+
for (const key of [
|
|
2098
|
+
'firstName',
|
|
2099
|
+
'lastName'
|
|
2100
|
+
]){
|
|
2101
|
+
if (!(key in body)) continue;
|
|
2102
|
+
const value = composeContactName(body[key], '');
|
|
2103
|
+
patch[key] = value || null;
|
|
2104
|
+
}
|
|
2105
|
+
for (const key of [
|
|
2106
|
+
'phone',
|
|
2107
|
+
'mobilePhone',
|
|
2108
|
+
'fax'
|
|
2109
|
+
]){
|
|
2110
|
+
var _body_key;
|
|
2111
|
+
if (!(key in body)) continue;
|
|
2112
|
+
const raw = String((_body_key = body[key]) != null ? _body_key : '').trim();
|
|
2113
|
+
if (!raw) patch[key] = null;
|
|
1608
2114
|
else {
|
|
1609
2115
|
const phone = normalizePhone(raw);
|
|
1610
|
-
if (phone) patch
|
|
1611
|
-
else errors
|
|
2116
|
+
if (phone) patch[key] = phone;
|
|
2117
|
+
else errors[key] = CRM_LEAD_PHONE_REFUSAL;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
if ('doNotCall' in body) {
|
|
2121
|
+
// Stored only as `true`: anything else is "may be called", a clear.
|
|
2122
|
+
patch.doNotCall = body['doNotCall'] === true ? true : null;
|
|
2123
|
+
}
|
|
2124
|
+
/*
|
|
2125
|
+
* The revenue, its currency and the head count, read by the company's
|
|
2126
|
+
* own reader so a value one record refuses the other refuses in the same
|
|
2127
|
+
* words. A head count typed as digits is read as the number it spells.
|
|
2128
|
+
*/ const account = {};
|
|
2129
|
+
if ('annualRevenueCents' in body) account['annualRevenueCents'] = (_body_annualRevenueCents = body['annualRevenueCents']) != null ? _body_annualRevenueCents : null;
|
|
2130
|
+
if ('currency' in body) account['currency'] = (_body_currency = body['currency']) != null ? _body_currency : null;
|
|
2131
|
+
if ('numberOfEmployees' in body) {
|
|
2132
|
+
const raw = body['numberOfEmployees'];
|
|
2133
|
+
const digits = typeof raw === 'string' ? raw.replace(/[\s,]/g, '') : null;
|
|
2134
|
+
account['numberOfEmployees'] = digits === null ? raw != null ? raw : null : digits === '' ? null : /^\d+$/.test(digits) ? Number(digits) : raw;
|
|
2135
|
+
}
|
|
2136
|
+
if (Object.keys(account).length) {
|
|
2137
|
+
const read = readCrmCompanyAccountFields(account);
|
|
2138
|
+
const into = patch;
|
|
2139
|
+
for (const key of [
|
|
2140
|
+
'annualRevenueCents',
|
|
2141
|
+
'currency',
|
|
2142
|
+
'numberOfEmployees'
|
|
2143
|
+
]){
|
|
2144
|
+
var _read_values_key;
|
|
2145
|
+
if (read.errors[key]) errors[key] = read.errors[key];
|
|
2146
|
+
else if (key in read.values) into[key] = (_read_values_key = read.values[key]) != null ? _read_values_key : null;
|
|
1612
2147
|
}
|
|
1613
2148
|
}
|
|
1614
2149
|
if ('website' in body) {
|
|
@@ -1634,282 +2169,1330 @@ export function isCrmLeadStatus(value) {
|
|
|
1634
2169
|
errors
|
|
1635
2170
|
};
|
|
1636
2171
|
}
|
|
1637
|
-
/*==========================================
|
|
1638
|
-
* LEAD SOURCE IS A PICKLIST (AGL-3298).
|
|
1639
|
-
*
|
|
1640
|
-
* Salesforce's Lead Source is a restricted picklist: an admin keeps the
|
|
1641
|
-
* list of values, a rep picks one, and an import or an API write naming
|
|
1642
|
-
* anything else is refused. Here the list is one org document,
|
|
1643
|
-
* `orgs/{orgId}/crmPicklists/leadSource`, holding the values in order as
|
|
1644
|
-
* `{ id, label, active }` and an optional default for new records.
|
|
1645
|
-
*
|
|
1646
|
-
* ## Records store the LABEL
|
|
1647
|
-
*
|
|
1648
|
-
* A lead carries `leadSource: "Trade show"`, not an id. Every reader that
|
|
1649
|
-
* existed before the picklist — the CSV export, the REST resource, the
|
|
1650
|
-
* record facts, a saved view's filter, a report grouped by the field —
|
|
1651
|
-
* reads the text and keeps working, and a file exported from here
|
|
1652
|
-
* re-imports as is. What an id would have bought is a free rename; here a
|
|
1653
|
-
* rename (and a delete) REPLACES the old label on every record in the same
|
|
1654
|
-
* operation, the way Salesforce's own Replace does, so a report grouped
|
|
1655
|
-
* by the field follows the rename rather than splitting in two. The value's
|
|
1656
|
-
* `id` is what the list itself is keyed by — the default, a reorder, the
|
|
1657
|
-
* manage page's rows — and it never changes.
|
|
1658
|
-
*
|
|
1659
|
-
* ## Inactive, not gone
|
|
1660
|
-
*
|
|
1661
|
-
* A deactivated value leaves every picker but stays on the records that
|
|
1662
|
-
* hold it, shown with an "(inactive)" hint, and a write that keeps a
|
|
1663
|
-
* record's current value is never refused for it. A value is removed only
|
|
1664
|
-
* by Delete, which names the value its records move to.
|
|
1665
|
-
*
|
|
1666
|
-
* ## An org that never edited the list
|
|
1667
|
-
*
|
|
1668
|
-
* Has no document, and reads {@link CRM_LEAD_SOURCE_STARTER_LABELS} — a
|
|
1669
|
-
* small Salesforce-like set. The first edit on the manage page writes that
|
|
1670
|
-
* set down as the org's own, so a new org starts with a usable list and no
|
|
1671
|
-
* org-creation step has to seed it.
|
|
1672
|
-
*=========================================*/ /** The picklists an org keeps, by document id — one per standard field. */ export const CRM_PICKLIST_IDS = [
|
|
1673
|
-
'leadSource'
|
|
1674
|
-
];
|
|
1675
|
-
/** The lead source value set's document id. */ export const CRM_LEAD_SOURCE_PICKLIST = 'leadSource';
|
|
1676
|
-
/** The most values one picklist holds — a menu, not a table. */ export const CRM_PICKLIST_VALUES_MAX = 200;
|
|
1677
|
-
/**
|
|
1678
|
-
* The values an org that has never edited its list reads — see the block
|
|
1679
|
-
* header. Salesforce's own standard set, trimmed to what a small team
|
|
1680
|
-
* recognizes.
|
|
1681
|
-
*/ export const CRM_LEAD_SOURCE_STARTER_LABELS = [
|
|
1682
|
-
'Web',
|
|
1683
|
-
'Phone inquiry',
|
|
1684
|
-
'Referral',
|
|
1685
|
-
'Partner',
|
|
1686
|
-
'Purchased list',
|
|
1687
|
-
'Trade show',
|
|
1688
|
-
'Other'
|
|
1689
|
-
];
|
|
1690
|
-
/** A label as a picklist stores it: trimmed, single-spaced, capped. */ export function normalizeCrmPicklistLabel(value) {
|
|
1691
|
-
return String(value != null ? value : '').trim().replace(/\s+/g, ' ').slice(0, CRM_LEAD_TEXT_MAX);
|
|
1692
|
-
}
|
|
1693
|
-
/** The key two labels are compared by: case and spacing do not make a new value. */ function picklistKey(label) {
|
|
1694
|
-
return normalizeCrmPicklistLabel(label).toLowerCase();
|
|
1695
|
-
}
|
|
1696
2172
|
/**
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
const values = [];
|
|
1709
|
-
const seen = new Set();
|
|
1710
|
-
for (const raw of labels){
|
|
1711
|
-
const label = normalizeCrmPicklistLabel(raw);
|
|
1712
|
-
if (!label || seen.has(picklistKey(label))) continue;
|
|
1713
|
-
seen.add(picklistKey(label));
|
|
1714
|
-
values.push({
|
|
1715
|
-
id: crmPicklistValueId(label, values.map((value)=>value.id)),
|
|
1716
|
-
label,
|
|
1717
|
-
active: true
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
return {
|
|
1721
|
-
values: values.slice(0, CRM_PICKLIST_VALUES_MAX),
|
|
1722
|
-
defaultValueId: null
|
|
2173
|
+
* The `name` a write must store beside a lead's first and last names
|
|
2174
|
+
* (AGL-3513), the contact's rule: while either part is set the name is
|
|
2175
|
+
* their composition, written whenever either moves. `undefined` when the
|
|
2176
|
+
* write names neither part, or when it leaves both blank — a name-only
|
|
2177
|
+
* lead keeps its name, and clearing both parts leaves the name as it
|
|
2178
|
+
* stands.
|
|
2179
|
+
*/ export function crmLeadComposedName(stored, patch) {
|
|
2180
|
+
if (patch.firstName === undefined && patch.lastName === undefined) return undefined;
|
|
2181
|
+
const part = (key)=>{
|
|
2182
|
+
var _patch_key;
|
|
2183
|
+
return patch[key] === undefined ? stored == null ? void 0 : stored[key] : (_patch_key = patch[key]) != null ? _patch_key : '';
|
|
1723
2184
|
};
|
|
2185
|
+
return composeContactName(part('firstName'), part('lastName')) || undefined;
|
|
1724
2186
|
}
|
|
1725
2187
|
/**
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
const label = normalizeCrmPicklistLabel(value['label']);
|
|
1745
|
-
if (!label || labels.has(picklistKey(label))) continue;
|
|
1746
|
-
let id = String((_value_id = value['id']) != null ? _value_id : '').trim().slice(0, 64);
|
|
1747
|
-
if (!id || id.includes('/') || ids.has(id)) id = crmPicklistValueId(label, ids);
|
|
1748
|
-
labels.add(picklistKey(label));
|
|
1749
|
-
ids.add(id);
|
|
1750
|
-
values.push({
|
|
1751
|
-
id,
|
|
1752
|
-
label,
|
|
1753
|
-
active: value['active'] !== false
|
|
1754
|
-
});
|
|
1755
|
-
if (values.length >= CRM_PICKLIST_VALUES_MAX) break;
|
|
2188
|
+
* Each lead picklist field beside the lead source (AGL-3513): the list it
|
|
2189
|
+
* holds a value of, and the key the Leads list filters by. The lists are
|
|
2190
|
+
* the contact's and the company's own — one Salutation, one Industry, one
|
|
2191
|
+
* Rating per org — so a lead converts into the same values.
|
|
2192
|
+
*/ export const CRM_LEAD_PICKLIST_FIELDS = [
|
|
2193
|
+
{
|
|
2194
|
+
field: 'salutation',
|
|
2195
|
+
picklistId: 'salutation'
|
|
2196
|
+
},
|
|
2197
|
+
{
|
|
2198
|
+
field: 'industry',
|
|
2199
|
+
picklistId: 'industry',
|
|
2200
|
+
keyField: 'industryKey'
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
field: 'rating',
|
|
2204
|
+
picklistId: 'rating',
|
|
2205
|
+
keyField: 'ratingKey'
|
|
1756
2206
|
}
|
|
1757
|
-
|
|
1758
|
-
return {
|
|
1759
|
-
values,
|
|
1760
|
-
defaultValueId: ids.has(defaultId) ? defaultId : null
|
|
1761
|
-
};
|
|
1762
|
-
}
|
|
1763
|
-
/** The org's lead source list as every reader should take it — see the block header. */ export function effectiveCrmLeadSourcePicklist(raw) {
|
|
1764
|
-
var _normalizeCrmPicklist;
|
|
1765
|
-
return (_normalizeCrmPicklist = normalizeCrmPicklist(raw)) != null ? _normalizeCrmPicklist : crmPicklistFromLabels(CRM_LEAD_SOURCE_STARTER_LABELS);
|
|
1766
|
-
}
|
|
1767
|
-
/** The value a label names, in any case or spacing — `null` for none. */ export function crmPicklistValueByLabel(picklist, label) {
|
|
1768
|
-
var _picklist_values_find;
|
|
1769
|
-
const key = picklistKey(String(label != null ? label : ''));
|
|
1770
|
-
if (!key) return null;
|
|
1771
|
-
return (_picklist_values_find = picklist.values.find((value)=>picklistKey(value.label) === key)) != null ? _picklist_values_find : null;
|
|
1772
|
-
}
|
|
1773
|
-
/** The values a picker offers. */ export function crmPicklistActiveValues(picklist) {
|
|
1774
|
-
return picklist.values.filter((value)=>value.active);
|
|
1775
|
-
}
|
|
1776
|
-
/** The label a new record starts with — only while the default value is active. */ export function crmPicklistDefaultLabel(picklist) {
|
|
1777
|
-
const value = picklist.values.find((entry)=>entry.id === picklist.defaultValueId);
|
|
1778
|
-
return (value == null ? void 0 : value.active) ? value.label : null;
|
|
1779
|
-
}
|
|
2207
|
+
];
|
|
1780
2208
|
/**
|
|
1781
|
-
* The
|
|
1782
|
-
* the
|
|
1783
|
-
*
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2209
|
+
* The lead picklist fields a write names, judged against the org's lists
|
|
2210
|
+
* — the company's {@link judgeCrmCompanyPicklists} over the lead's fields:
|
|
2211
|
+
* `lists` holds each list the caller read (a list it did not read is
|
|
2212
|
+
* judged as the standard values alone), the lead's `current` value is kept
|
|
2213
|
+
* even when the list no longer offers it, and on a create a field not
|
|
2214
|
+
* named starts from its list's default.
|
|
2215
|
+
*/ export function judgeCrmLeadPicklists(lists, requested, options = {}) {
|
|
2216
|
+
const values = {};
|
|
2217
|
+
const errors = {};
|
|
2218
|
+
for (const { field, picklistId } of CRM_LEAD_PICKLIST_FIELDS){
|
|
2219
|
+
var _lists_picklistId;
|
|
2220
|
+
var _options_current;
|
|
2221
|
+
const list = (_lists_picklistId = lists[picklistId]) != null ? _lists_picklistId : effectiveCrmPicklist(picklistId, undefined);
|
|
2222
|
+
const raw = requested[field];
|
|
2223
|
+
if (raw === undefined) {
|
|
2224
|
+
const fallback = options.created ? picklistDefaultLabel(list) : null;
|
|
2225
|
+
if (fallback) values[field] = fallback;
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
if (raw === null || typeof raw === 'string' && !raw.trim()) {
|
|
2229
|
+
values[field] = null;
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
if (typeof raw !== 'string') {
|
|
2233
|
+
errors[field] = 'Must be text';
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
const judged = judgeCrmPicklistValue(picklistId, list, raw, (_options_current = options.current) == null ? void 0 : _options_current[field]);
|
|
2237
|
+
if (judged.ok === false) errors[field] = judged.error;
|
|
2238
|
+
else values[field] = judged.value;
|
|
1788
2239
|
}
|
|
1789
|
-
const shown = active.slice(0, 20).join(', ');
|
|
1790
|
-
const more = active.length > 20 ? `, and ${active.length - 20} more` : '';
|
|
1791
|
-
return `Lead source must be one of: ${shown}${more}.`;
|
|
1792
|
-
}
|
|
1793
|
-
/**
|
|
1794
|
-
* Whether a write may store `value` as a record's lead source, and the
|
|
1795
|
-
* label it stores.
|
|
1796
|
-
*
|
|
1797
|
-
* - Blank or `null` clears the field — lead source is never required.
|
|
1798
|
-
* - An ACTIVE value, in any case or spacing, stores that value's label.
|
|
1799
|
-
* - The record's `current` value is kept even when it is inactive or no
|
|
1800
|
-
* longer listed: re-saving a record must never be refused for a value
|
|
1801
|
-
* somebody else deactivated.
|
|
1802
|
-
* - Anything else is refused with {@link crmLeadSourceRefusal}.
|
|
1803
|
-
*/ export function judgeCrmLeadSource(picklist, value, current) {
|
|
1804
|
-
const label = normalizeCrmPicklistLabel(value);
|
|
1805
|
-
if (!label) return {
|
|
1806
|
-
ok: true,
|
|
1807
|
-
value: null
|
|
1808
|
-
};
|
|
1809
|
-
const held = normalizeCrmPicklistLabel(current);
|
|
1810
|
-
if (held && picklistKey(held) === picklistKey(label)) return {
|
|
1811
|
-
ok: true,
|
|
1812
|
-
value: held
|
|
1813
|
-
};
|
|
1814
|
-
const match = crmPicklistValueByLabel(picklist, label);
|
|
1815
|
-
if (match == null ? void 0 : match.active) return {
|
|
1816
|
-
ok: true,
|
|
1817
|
-
value: match.label
|
|
1818
|
-
};
|
|
1819
2240
|
return {
|
|
1820
|
-
|
|
1821
|
-
|
|
2241
|
+
values,
|
|
2242
|
+
errors
|
|
1822
2243
|
};
|
|
1823
2244
|
}
|
|
1824
2245
|
/**
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
const listed = crmPicklistValueByLabel(picklist, held);
|
|
1837
|
-
options.push({
|
|
1838
|
-
label: held,
|
|
1839
|
-
inactive: Boolean(listed),
|
|
1840
|
-
unlisted: !listed
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
return options;
|
|
1844
|
-
}
|
|
1845
|
-
/**
|
|
1846
|
-
* Where a label sorts: its value's position in the list, unlisted values
|
|
1847
|
-
* after every listed one, and a record with none last. The order the admin
|
|
1848
|
-
* chose is the order a sort by the field reads in, as Salesforce's does.
|
|
1849
|
-
*/ export function crmPicklistRank(picklist, label) {
|
|
1850
|
-
const text = normalizeCrmPicklistLabel(label);
|
|
1851
|
-
if (!text) return Number.MAX_SAFE_INTEGER;
|
|
1852
|
-
const at = picklist.values.findIndex((value)=>picklistKey(value.label) === picklistKey(text));
|
|
1853
|
-
return at < 0 ? CRM_PICKLIST_VALUES_MAX : at;
|
|
1854
|
-
}
|
|
1855
|
-
/** The name a lead is listed under: the name it carries, else its address. */ export function crmLeadDisplayName(lead) {
|
|
1856
|
-
var _ref, _ref1;
|
|
1857
|
-
const name = String((_ref = lead == null ? void 0 : lead['name']) != null ? _ref : '').trim();
|
|
1858
|
-
return name || String((_ref1 = lead == null ? void 0 : lead['email']) != null ? _ref1 : '').trim();
|
|
2246
|
+
* The Fields tabs, in order: every object with custom fields, then Tasks,
|
|
2247
|
+
* which has standard picklists and no custom fields (AGL-3517).
|
|
2248
|
+
*/ export const CRM_PICKLIST_OBJECTS = [
|
|
2249
|
+
...CRM_FIELD_OBJECTS,
|
|
2250
|
+
'task'
|
|
2251
|
+
];
|
|
2252
|
+
/** How each object reads on the Fields tabs and in a refusal. */ export const CRM_PICKLIST_OBJECT_LABELS = _extends({}, CRM_FIELD_OBJECT_LABELS, {
|
|
2253
|
+
task: 'Tasks'
|
|
2254
|
+
});
|
|
2255
|
+
export function isCrmPicklistObject(value) {
|
|
2256
|
+
return typeof value === 'string' && CRM_PICKLIST_OBJECTS.includes(value);
|
|
1859
2257
|
}
|
|
1860
2258
|
/**
|
|
1861
|
-
*
|
|
1862
|
-
*
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2259
|
+
* Lead Source, with Salesforce's standard values and Aglyn's own (AGL-3519).
|
|
2260
|
+
* Each carries the group a report splits pipeline by: Inbound for a lead
|
|
2261
|
+
* who came to the org, Outbound for one the org went to, and Other in
|
|
2262
|
+
* neither. Aglyn's are the doors and the outreach the platform runs itself
|
|
2263
|
+
* — a website form, a booking, a sequence — stamped on a record the door
|
|
2264
|
+
* met first ({@link CRM_LEAD_SOURCE_ORIGINS}); a vendor the platform does
|
|
2265
|
+
* not run (a data provider, a sending tool) is an org's own value.
|
|
2266
|
+
*/ const LEAD_SOURCE_DEFINITION = {
|
|
2267
|
+
id: 'leadSource',
|
|
2268
|
+
label: 'Lead source',
|
|
2269
|
+
plural: 'lead sources',
|
|
2270
|
+
object: 'lead',
|
|
2271
|
+
restricted: true,
|
|
2272
|
+
groups: [
|
|
2273
|
+
{
|
|
2274
|
+
id: 'inbound',
|
|
2275
|
+
label: 'Inbound'
|
|
2276
|
+
},
|
|
2277
|
+
{
|
|
2278
|
+
id: 'outbound',
|
|
2279
|
+
label: 'Outbound'
|
|
2280
|
+
}
|
|
2281
|
+
],
|
|
2282
|
+
standardValues: [
|
|
2283
|
+
{
|
|
2284
|
+
id: 'web',
|
|
2285
|
+
label: 'Web',
|
|
2286
|
+
group: 'inbound'
|
|
2287
|
+
},
|
|
2288
|
+
{
|
|
2289
|
+
id: 'phone-inquiry',
|
|
2290
|
+
label: 'Phone inquiry',
|
|
2291
|
+
group: 'inbound'
|
|
2292
|
+
},
|
|
2293
|
+
{
|
|
2294
|
+
id: 'email-inquiry',
|
|
2295
|
+
label: 'Email inquiry',
|
|
2296
|
+
group: 'inbound'
|
|
2297
|
+
},
|
|
2298
|
+
{
|
|
2299
|
+
id: 'partner-referral',
|
|
2300
|
+
label: 'Partner referral',
|
|
2301
|
+
group: 'inbound'
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
id: 'employee-referral',
|
|
2305
|
+
label: 'Employee referral',
|
|
2306
|
+
group: 'inbound'
|
|
2307
|
+
},
|
|
2308
|
+
{
|
|
2309
|
+
id: 'external-referral',
|
|
2310
|
+
label: 'External referral',
|
|
2311
|
+
group: 'inbound'
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
id: 'advertisement',
|
|
2315
|
+
label: 'Advertisement',
|
|
2316
|
+
group: 'inbound'
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
id: 'trade-show',
|
|
2320
|
+
label: 'Trade show',
|
|
2321
|
+
group: 'inbound'
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
id: 'webinar',
|
|
2325
|
+
label: 'Webinar',
|
|
2326
|
+
group: 'inbound'
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
id: 'word-of-mouth',
|
|
2330
|
+
label: 'Word of mouth',
|
|
2331
|
+
group: 'inbound'
|
|
2332
|
+
},
|
|
2333
|
+
// Aglyn's own doors (AGL-3519), each stamped by the door that met the person.
|
|
2334
|
+
{
|
|
2335
|
+
id: 'website-form',
|
|
2336
|
+
label: 'Website form',
|
|
2337
|
+
group: 'inbound'
|
|
2338
|
+
},
|
|
2339
|
+
{
|
|
2340
|
+
id: 'booking',
|
|
2341
|
+
label: 'Booking',
|
|
2342
|
+
group: 'inbound'
|
|
2343
|
+
},
|
|
2344
|
+
{
|
|
2345
|
+
id: 'newsletter-sign-up',
|
|
2346
|
+
label: 'Newsletter sign-up',
|
|
2347
|
+
group: 'inbound'
|
|
2348
|
+
},
|
|
2349
|
+
{
|
|
2350
|
+
id: 'site-member-sign-up',
|
|
2351
|
+
label: 'Site member sign-up',
|
|
2352
|
+
group: 'inbound'
|
|
2353
|
+
},
|
|
2354
|
+
{
|
|
2355
|
+
id: 'online-purchase',
|
|
2356
|
+
label: 'Online purchase',
|
|
2357
|
+
group: 'inbound'
|
|
2358
|
+
},
|
|
2359
|
+
{
|
|
2360
|
+
id: 'account-sign-up',
|
|
2361
|
+
label: 'Account sign-up',
|
|
2362
|
+
group: 'inbound'
|
|
2363
|
+
},
|
|
2364
|
+
{
|
|
2365
|
+
id: 'purchased-list',
|
|
2366
|
+
label: 'Purchased list',
|
|
2367
|
+
group: 'outbound'
|
|
2368
|
+
},
|
|
2369
|
+
// Aglyn's own outreach (AGL-3519).
|
|
2370
|
+
{
|
|
2371
|
+
id: 'sequence',
|
|
2372
|
+
label: 'Sequence',
|
|
2373
|
+
group: 'outbound'
|
|
2374
|
+
},
|
|
2375
|
+
{
|
|
2376
|
+
id: 'email-campaign',
|
|
2377
|
+
label: 'Email campaign',
|
|
2378
|
+
group: 'outbound'
|
|
2379
|
+
},
|
|
2380
|
+
{
|
|
2381
|
+
id: 'other',
|
|
2382
|
+
label: 'Other'
|
|
2383
|
+
}
|
|
2384
|
+
],
|
|
2385
|
+
targets: [
|
|
2386
|
+
// The key the Leads list filters by moves with the label (AGL-3321).
|
|
2387
|
+
{
|
|
2388
|
+
object: 'lead',
|
|
2389
|
+
field: 'leadSource',
|
|
2390
|
+
keyField: 'leadSourceKey'
|
|
2391
|
+
},
|
|
2392
|
+
// A contact's lead source is each holder's own, like the rest of its profile.
|
|
2393
|
+
{
|
|
2394
|
+
object: 'contact',
|
|
2395
|
+
field: 'leadSource',
|
|
2396
|
+
facet: true
|
|
2397
|
+
},
|
|
2398
|
+
// A company's Account Source is a lead source value (AGL-3514).
|
|
2399
|
+
{
|
|
2400
|
+
object: 'company',
|
|
2401
|
+
field: 'accountSource',
|
|
2402
|
+
keyField: 'accountSourceKey'
|
|
2403
|
+
},
|
|
2404
|
+
// A deal's, stamped by the conversion that opened it (AGL-3516).
|
|
2405
|
+
{
|
|
2406
|
+
object: 'deal',
|
|
2407
|
+
field: 'leadSource',
|
|
2408
|
+
keyField: 'leadSourceKey'
|
|
2409
|
+
}
|
|
2410
|
+
]
|
|
2411
|
+
};
|
|
2412
|
+
/*------------------------------------------
|
|
2413
|
+
* LEAD STATUS (AGL-3512) — a SEMANTIC picklist.
|
|
2414
|
+
*
|
|
2415
|
+
* Every value MEANS one of {@link CRM_LEAD_STATUSES}, and the meaning stays
|
|
2416
|
+
* on the lead as `status`, which every query, count, index, automation and
|
|
2417
|
+
* rule reads exactly as before. The value's label sits beside it as
|
|
2418
|
+
* `statusLabel` — see {@link crmLeadStatusLabel}. Salesforce ships one
|
|
2419
|
+
* standard value per meaning; an org adds its own ("Contacted", "Meeting
|
|
2420
|
+
* set") under a meaning, never under Qualified, which only a conversion
|
|
2421
|
+
* sets.
|
|
2422
|
+
*-----------------------------------------*/ const LEAD_STATUS_DEFINITION = {
|
|
2423
|
+
id: 'leadStatus',
|
|
2424
|
+
label: 'Lead status',
|
|
2425
|
+
plural: 'lead statuses',
|
|
2426
|
+
object: 'lead',
|
|
2427
|
+
restricted: true,
|
|
2428
|
+
meanings: CRM_LEAD_STATUSES,
|
|
2429
|
+
meaningLabels: CRM_LEAD_STATUS_LABELS,
|
|
2430
|
+
reservedMeanings: [
|
|
2431
|
+
'qualified'
|
|
2432
|
+
],
|
|
2433
|
+
standardValues: [
|
|
2434
|
+
{
|
|
2435
|
+
id: 'new',
|
|
2436
|
+
label: CRM_LEAD_STATUS_LABELS.new,
|
|
2437
|
+
meaning: 'new'
|
|
2438
|
+
},
|
|
2439
|
+
{
|
|
2440
|
+
id: 'nurturing',
|
|
2441
|
+
label: CRM_LEAD_STATUS_LABELS.nurturing,
|
|
2442
|
+
meaning: 'nurturing'
|
|
2443
|
+
},
|
|
2444
|
+
{
|
|
2445
|
+
id: 'working',
|
|
2446
|
+
label: CRM_LEAD_STATUS_LABELS.working,
|
|
2447
|
+
meaning: 'working'
|
|
2448
|
+
},
|
|
2449
|
+
{
|
|
2450
|
+
id: 'qualified',
|
|
2451
|
+
label: CRM_LEAD_STATUS_LABELS.qualified,
|
|
2452
|
+
meaning: 'qualified'
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
id: 'unqualified',
|
|
2456
|
+
label: CRM_LEAD_STATUS_LABELS.unqualified,
|
|
2457
|
+
meaning: 'unqualified'
|
|
2458
|
+
}
|
|
2459
|
+
],
|
|
2460
|
+
targets: [
|
|
2461
|
+
{
|
|
2462
|
+
object: 'lead',
|
|
2463
|
+
field: 'statusLabel'
|
|
2464
|
+
}
|
|
2465
|
+
]
|
|
2466
|
+
};
|
|
2467
|
+
/*------------------------------------------
|
|
2468
|
+
* THE COMPANY PICKLISTS (AGL-3514).
|
|
2469
|
+
*
|
|
2470
|
+
* Salesforce's Account Type, Industry, Rating and Ownership, each kept on
|
|
2471
|
+
* the Companies tab. Industry and Rating are shared with leads, whose
|
|
2472
|
+
* targets join these definitions rather than repeating them. Account
|
|
2473
|
+
* Source is not a list of its own: it holds a lead source value, so it is
|
|
2474
|
+
* a target of the lead source definition above.
|
|
2475
|
+
*-----------------------------------------*/ /** Salesforce's Account Type. */ const ACCOUNT_TYPE_DEFINITION = {
|
|
2476
|
+
id: 'accountType',
|
|
2477
|
+
label: 'Type',
|
|
2478
|
+
plural: 'account types',
|
|
2479
|
+
object: 'company',
|
|
2480
|
+
restricted: true,
|
|
2481
|
+
standardValues: [
|
|
2482
|
+
{
|
|
2483
|
+
id: 'analyst',
|
|
2484
|
+
label: 'Analyst'
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
id: 'press',
|
|
2488
|
+
label: 'Press'
|
|
2489
|
+
},
|
|
2490
|
+
{
|
|
2491
|
+
id: 'competitor',
|
|
2492
|
+
label: 'Competitor'
|
|
2493
|
+
},
|
|
2494
|
+
{
|
|
2495
|
+
id: 'prospect',
|
|
2496
|
+
label: 'Prospect'
|
|
2497
|
+
},
|
|
2498
|
+
{
|
|
2499
|
+
id: 'customer',
|
|
2500
|
+
label: 'Customer'
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
id: 'reseller',
|
|
2504
|
+
label: 'Reseller'
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
id: 'integrator',
|
|
2508
|
+
label: 'Integrator'
|
|
2509
|
+
},
|
|
2510
|
+
{
|
|
2511
|
+
id: 'investor',
|
|
2512
|
+
label: 'Investor'
|
|
2513
|
+
},
|
|
2514
|
+
{
|
|
2515
|
+
id: 'partner',
|
|
2516
|
+
label: 'Partner'
|
|
2517
|
+
},
|
|
2518
|
+
{
|
|
2519
|
+
id: 'consulting',
|
|
2520
|
+
label: 'Consulting'
|
|
2521
|
+
},
|
|
2522
|
+
{
|
|
2523
|
+
id: 'other',
|
|
2524
|
+
label: 'Other'
|
|
2525
|
+
}
|
|
2526
|
+
],
|
|
2527
|
+
targets: [
|
|
2528
|
+
{
|
|
2529
|
+
object: 'company',
|
|
2530
|
+
field: 'type',
|
|
2531
|
+
keyField: 'typeKey'
|
|
2532
|
+
}
|
|
2533
|
+
]
|
|
2534
|
+
};
|
|
2535
|
+
/**
|
|
2536
|
+
* Salesforce's Industry. A company written while the field was free text
|
|
2537
|
+
* keeps its text; a write that keeps it is never refused.
|
|
2538
|
+
*/ const INDUSTRY_DEFINITION = {
|
|
2539
|
+
id: 'industry',
|
|
2540
|
+
label: 'Industry',
|
|
2541
|
+
plural: 'industries',
|
|
2542
|
+
object: 'company',
|
|
2543
|
+
restricted: true,
|
|
2544
|
+
standardValues: [
|
|
2545
|
+
{
|
|
2546
|
+
id: 'agriculture',
|
|
2547
|
+
label: 'Agriculture'
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
id: 'apparel',
|
|
2551
|
+
label: 'Apparel'
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
id: 'banking',
|
|
2555
|
+
label: 'Banking'
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
id: 'biotechnology',
|
|
2559
|
+
label: 'Biotechnology'
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
id: 'chemicals',
|
|
2563
|
+
label: 'Chemicals'
|
|
2564
|
+
},
|
|
2565
|
+
{
|
|
2566
|
+
id: 'communications',
|
|
2567
|
+
label: 'Communications'
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
id: 'construction',
|
|
2571
|
+
label: 'Construction'
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
id: 'consulting',
|
|
2575
|
+
label: 'Consulting'
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
id: 'education',
|
|
2579
|
+
label: 'Education'
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
id: 'electronics',
|
|
2583
|
+
label: 'Electronics'
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
id: 'energy',
|
|
2587
|
+
label: 'Energy'
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
id: 'engineering',
|
|
2591
|
+
label: 'Engineering'
|
|
2592
|
+
},
|
|
2593
|
+
{
|
|
2594
|
+
id: 'entertainment',
|
|
2595
|
+
label: 'Entertainment'
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
id: 'environmental',
|
|
2599
|
+
label: 'Environmental'
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
id: 'finance',
|
|
2603
|
+
label: 'Finance'
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
id: 'food-and-beverage',
|
|
2607
|
+
label: 'Food & Beverage'
|
|
2608
|
+
},
|
|
2609
|
+
{
|
|
2610
|
+
id: 'government',
|
|
2611
|
+
label: 'Government'
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
id: 'healthcare',
|
|
2615
|
+
label: 'Healthcare'
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
id: 'hospitality',
|
|
2619
|
+
label: 'Hospitality'
|
|
2620
|
+
},
|
|
2621
|
+
{
|
|
2622
|
+
id: 'insurance',
|
|
2623
|
+
label: 'Insurance'
|
|
2624
|
+
},
|
|
2625
|
+
{
|
|
2626
|
+
id: 'machinery',
|
|
2627
|
+
label: 'Machinery'
|
|
2628
|
+
},
|
|
2629
|
+
{
|
|
2630
|
+
id: 'manufacturing',
|
|
2631
|
+
label: 'Manufacturing'
|
|
2632
|
+
},
|
|
2633
|
+
{
|
|
2634
|
+
id: 'media',
|
|
2635
|
+
label: 'Media'
|
|
2636
|
+
},
|
|
2637
|
+
{
|
|
2638
|
+
id: 'not-for-profit',
|
|
2639
|
+
label: 'Not For Profit'
|
|
2640
|
+
},
|
|
2641
|
+
{
|
|
2642
|
+
id: 'recreation',
|
|
2643
|
+
label: 'Recreation'
|
|
2644
|
+
},
|
|
2645
|
+
{
|
|
2646
|
+
id: 'retail',
|
|
2647
|
+
label: 'Retail'
|
|
2648
|
+
},
|
|
2649
|
+
{
|
|
2650
|
+
id: 'shipping',
|
|
2651
|
+
label: 'Shipping'
|
|
2652
|
+
},
|
|
2653
|
+
{
|
|
2654
|
+
id: 'technology',
|
|
2655
|
+
label: 'Technology'
|
|
2656
|
+
},
|
|
2657
|
+
{
|
|
2658
|
+
id: 'telecommunications',
|
|
2659
|
+
label: 'Telecommunications'
|
|
2660
|
+
},
|
|
2661
|
+
{
|
|
2662
|
+
id: 'transportation',
|
|
2663
|
+
label: 'Transportation'
|
|
2664
|
+
},
|
|
2665
|
+
{
|
|
2666
|
+
id: 'utilities',
|
|
2667
|
+
label: 'Utilities'
|
|
2668
|
+
},
|
|
2669
|
+
{
|
|
2670
|
+
id: 'other',
|
|
2671
|
+
label: 'Other'
|
|
2672
|
+
}
|
|
2673
|
+
],
|
|
2674
|
+
targets: [
|
|
2675
|
+
{
|
|
2676
|
+
object: 'company',
|
|
2677
|
+
field: 'industry',
|
|
2678
|
+
keyField: 'industryKey'
|
|
2679
|
+
},
|
|
2680
|
+
// Leads (AGL-3513): the same list, so a lead converts into its value.
|
|
2681
|
+
{
|
|
2682
|
+
object: 'lead',
|
|
2683
|
+
field: 'industry',
|
|
2684
|
+
keyField: 'industryKey'
|
|
2685
|
+
}
|
|
2686
|
+
]
|
|
2687
|
+
};
|
|
2688
|
+
/** Salesforce's Rating. */ const RATING_DEFINITION = {
|
|
2689
|
+
id: 'rating',
|
|
2690
|
+
label: 'Rating',
|
|
2691
|
+
plural: 'ratings',
|
|
2692
|
+
object: 'company',
|
|
2693
|
+
restricted: true,
|
|
2694
|
+
standardValues: [
|
|
2695
|
+
{
|
|
2696
|
+
id: 'hot',
|
|
2697
|
+
label: 'Hot'
|
|
2698
|
+
},
|
|
2699
|
+
{
|
|
2700
|
+
id: 'warm',
|
|
2701
|
+
label: 'Warm'
|
|
2702
|
+
},
|
|
2703
|
+
{
|
|
2704
|
+
id: 'cold',
|
|
2705
|
+
label: 'Cold'
|
|
2706
|
+
}
|
|
2707
|
+
],
|
|
2708
|
+
targets: [
|
|
2709
|
+
{
|
|
2710
|
+
object: 'company',
|
|
2711
|
+
field: 'rating',
|
|
2712
|
+
keyField: 'ratingKey'
|
|
2713
|
+
},
|
|
2714
|
+
// Leads (AGL-3513).
|
|
2715
|
+
{
|
|
2716
|
+
object: 'lead',
|
|
2717
|
+
field: 'rating',
|
|
2718
|
+
keyField: 'ratingKey'
|
|
2719
|
+
}
|
|
2720
|
+
]
|
|
2721
|
+
};
|
|
2722
|
+
/** Salesforce's Ownership. */ const OWNERSHIP_DEFINITION = {
|
|
2723
|
+
id: 'ownership',
|
|
2724
|
+
label: 'Ownership',
|
|
2725
|
+
plural: 'ownership values',
|
|
2726
|
+
object: 'company',
|
|
2727
|
+
restricted: true,
|
|
2728
|
+
standardValues: [
|
|
2729
|
+
{
|
|
2730
|
+
id: 'public',
|
|
2731
|
+
label: 'Public'
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
id: 'private',
|
|
2735
|
+
label: 'Private'
|
|
2736
|
+
},
|
|
2737
|
+
{
|
|
2738
|
+
id: 'subsidiary',
|
|
2739
|
+
label: 'Subsidiary'
|
|
2740
|
+
},
|
|
2741
|
+
{
|
|
2742
|
+
id: 'other',
|
|
2743
|
+
label: 'Other'
|
|
2744
|
+
}
|
|
2745
|
+
],
|
|
2746
|
+
targets: [
|
|
2747
|
+
{
|
|
2748
|
+
object: 'company',
|
|
2749
|
+
field: 'ownership'
|
|
2750
|
+
}
|
|
2751
|
+
]
|
|
2752
|
+
};
|
|
2753
|
+
/*------------------------------------------
|
|
2754
|
+
* CONTACT picklists (AGL-3515).
|
|
2755
|
+
*-----------------------------------------*/ /**
|
|
2756
|
+
* Salutation, with Salesforce's standard values. Shared by every object
|
|
2757
|
+
* that addresses a person: defined here with the contact target, where the
|
|
2758
|
+
* label lives in each holder's facet like the rest of the profile.
|
|
2759
|
+
*/ const SALUTATION_DEFINITION = {
|
|
2760
|
+
id: 'salutation',
|
|
2761
|
+
label: 'Salutation',
|
|
2762
|
+
plural: 'salutations',
|
|
2763
|
+
object: 'contact',
|
|
2764
|
+
restricted: true,
|
|
2765
|
+
standardValues: [
|
|
2766
|
+
{
|
|
2767
|
+
id: 'mr',
|
|
2768
|
+
label: 'Mr.'
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
id: 'ms',
|
|
2772
|
+
label: 'Ms.'
|
|
2773
|
+
},
|
|
2774
|
+
{
|
|
2775
|
+
id: 'mrs',
|
|
2776
|
+
label: 'Mrs.'
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
id: 'dr',
|
|
2780
|
+
label: 'Dr.'
|
|
2781
|
+
},
|
|
2782
|
+
{
|
|
2783
|
+
id: 'prof',
|
|
2784
|
+
label: 'Prof.'
|
|
2785
|
+
}
|
|
2786
|
+
],
|
|
2787
|
+
targets: [
|
|
2788
|
+
{
|
|
2789
|
+
object: 'contact',
|
|
2790
|
+
field: 'salutation',
|
|
2791
|
+
facet: true
|
|
2792
|
+
},
|
|
2793
|
+
// Leads (AGL-3513): on the lead itself, carried to the facet on convert.
|
|
2794
|
+
{
|
|
2795
|
+
object: 'lead',
|
|
2796
|
+
field: 'salutation'
|
|
2797
|
+
}
|
|
2798
|
+
]
|
|
2799
|
+
};
|
|
2800
|
+
/*------------------------------------------
|
|
2801
|
+
* DEALS (AGL-3516).
|
|
2802
|
+
*-----------------------------------------*/ /**
|
|
2803
|
+
* Salesforce's Opportunity Type. Plain: the deal stores the label, and
|
|
2804
|
+
* `typeKey` beside it for the Deals list's filter.
|
|
2805
|
+
*/ const OPPORTUNITY_TYPE_DEFINITION = {
|
|
2806
|
+
id: 'opportunityType',
|
|
2807
|
+
label: 'Type',
|
|
2808
|
+
plural: 'deal types',
|
|
2809
|
+
object: 'deal',
|
|
2810
|
+
restricted: true,
|
|
2811
|
+
standardValues: [
|
|
2812
|
+
{
|
|
2813
|
+
id: 'existing-business',
|
|
2814
|
+
label: 'Existing Business'
|
|
2815
|
+
},
|
|
2816
|
+
{
|
|
2817
|
+
id: 'new-business',
|
|
2818
|
+
label: 'New Business'
|
|
2819
|
+
}
|
|
2820
|
+
],
|
|
2821
|
+
targets: [
|
|
2822
|
+
{
|
|
2823
|
+
object: 'deal',
|
|
2824
|
+
field: 'type',
|
|
2825
|
+
keyField: 'typeKey'
|
|
2826
|
+
}
|
|
2827
|
+
]
|
|
2828
|
+
};
|
|
2829
|
+
/**
|
|
2830
|
+
* Salesforce's Opportunity Contact Role (AGL-3521): the part a contact
|
|
2831
|
+
* plays on a deal. Plain; the label sits in each entry of the deal's
|
|
2832
|
+
* `contactRoles`, and `contactRoleKeys` lists their keys, which is how a
|
|
2833
|
+
* rename or a delete finds the deals to rewrite.
|
|
2834
|
+
*/ const OPPORTUNITY_CONTACT_ROLE_DEFINITION = {
|
|
2835
|
+
id: 'opportunityContactRole',
|
|
2836
|
+
label: 'Contact role',
|
|
2837
|
+
plural: 'contact roles',
|
|
2838
|
+
object: 'deal',
|
|
2839
|
+
restricted: true,
|
|
2840
|
+
standardValues: [
|
|
2841
|
+
{
|
|
2842
|
+
id: 'business-user',
|
|
2843
|
+
label: 'Business User'
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
id: 'decision-maker',
|
|
2847
|
+
label: 'Decision Maker'
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
id: 'economic-buyer',
|
|
2851
|
+
label: 'Economic Buyer'
|
|
2852
|
+
},
|
|
2853
|
+
{
|
|
2854
|
+
id: 'economic-decision-maker',
|
|
2855
|
+
label: 'Economic Decision Maker'
|
|
2856
|
+
},
|
|
2857
|
+
{
|
|
2858
|
+
id: 'evaluator',
|
|
2859
|
+
label: 'Evaluator'
|
|
2860
|
+
},
|
|
2861
|
+
{
|
|
2862
|
+
id: 'executive-sponsor',
|
|
2863
|
+
label: 'Executive Sponsor'
|
|
2864
|
+
},
|
|
2865
|
+
{
|
|
2866
|
+
id: 'influencer',
|
|
2867
|
+
label: 'Influencer'
|
|
2868
|
+
},
|
|
2869
|
+
{
|
|
2870
|
+
id: 'technical-buyer',
|
|
2871
|
+
label: 'Technical Buyer'
|
|
2872
|
+
},
|
|
2873
|
+
{
|
|
2874
|
+
id: 'other',
|
|
2875
|
+
label: 'Other'
|
|
2876
|
+
}
|
|
2877
|
+
],
|
|
2878
|
+
targets: [
|
|
2879
|
+
{
|
|
2880
|
+
object: 'deal',
|
|
2881
|
+
field: 'contactRoles',
|
|
2882
|
+
arrayKey: 'role',
|
|
2883
|
+
keyField: 'contactRoleKeys'
|
|
2884
|
+
}
|
|
2885
|
+
]
|
|
2886
|
+
};
|
|
2887
|
+
/*
|
|
2888
|
+
* TASKS (AGL-3517) — Salesforce's Task Status, Priority, Type and Subject.
|
|
2889
|
+
*
|
|
2890
|
+
* Status, Priority and Type are SEMANTIC: each value means one of the
|
|
2891
|
+
* task's existing `status`, `priority` or `kind` values, which stay on the
|
|
2892
|
+
* document untouched for every query, reminder, due state, digest and
|
|
2893
|
+
* automation. The value's LABEL sits beside it in `statusLabel`,
|
|
2894
|
+
* `priorityLabel` or `typeLabel`; a task with no label shows the first
|
|
2895
|
+
* active value of its meaning (see `crmTaskPicklistLabel`). Subject is an
|
|
2896
|
+
* unrestricted combobox over the title: its values are suggestions, the
|
|
2897
|
+
* title stays free text, and no record holds a subject "value", so a
|
|
2898
|
+
* rename or a delete rewrites nothing.
|
|
2899
|
+
*/ const TASK_STATUS_DEFINITION = {
|
|
2900
|
+
id: 'taskStatus',
|
|
2901
|
+
label: 'Status',
|
|
2902
|
+
plural: 'task statuses',
|
|
2903
|
+
object: 'task',
|
|
2904
|
+
restricted: true,
|
|
2905
|
+
meanings: [
|
|
2906
|
+
'open',
|
|
2907
|
+
'done'
|
|
2908
|
+
],
|
|
2909
|
+
standardValues: [
|
|
2910
|
+
{
|
|
2911
|
+
id: 'not-started',
|
|
2912
|
+
label: 'Not Started',
|
|
2913
|
+
meaning: 'open'
|
|
2914
|
+
},
|
|
2915
|
+
{
|
|
2916
|
+
id: 'in-progress',
|
|
2917
|
+
label: 'In Progress',
|
|
2918
|
+
meaning: 'open'
|
|
2919
|
+
},
|
|
2920
|
+
{
|
|
2921
|
+
id: 'waiting',
|
|
2922
|
+
label: 'Waiting on someone else',
|
|
2923
|
+
meaning: 'open'
|
|
2924
|
+
},
|
|
2925
|
+
{
|
|
2926
|
+
id: 'deferred',
|
|
2927
|
+
label: 'Deferred',
|
|
2928
|
+
meaning: 'open'
|
|
2929
|
+
},
|
|
2930
|
+
{
|
|
2931
|
+
id: 'completed',
|
|
2932
|
+
label: 'Completed',
|
|
2933
|
+
meaning: 'done'
|
|
2934
|
+
}
|
|
2935
|
+
],
|
|
2936
|
+
defaultValueId: 'not-started',
|
|
2937
|
+
targets: [
|
|
2938
|
+
{
|
|
2939
|
+
object: 'task',
|
|
2940
|
+
field: 'statusLabel'
|
|
2941
|
+
}
|
|
2942
|
+
]
|
|
2943
|
+
};
|
|
2944
|
+
const TASK_PRIORITY_DEFINITION = {
|
|
2945
|
+
id: 'taskPriority',
|
|
2946
|
+
label: 'Priority',
|
|
2947
|
+
plural: 'task priorities',
|
|
2948
|
+
object: 'task',
|
|
2949
|
+
restricted: true,
|
|
2950
|
+
meanings: [
|
|
2951
|
+
'low',
|
|
2952
|
+
'normal',
|
|
2953
|
+
'high'
|
|
2954
|
+
],
|
|
2955
|
+
standardValues: [
|
|
2956
|
+
{
|
|
2957
|
+
id: 'high',
|
|
2958
|
+
label: 'High',
|
|
2959
|
+
meaning: 'high'
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
id: 'normal',
|
|
2963
|
+
label: 'Normal',
|
|
2964
|
+
meaning: 'normal'
|
|
2965
|
+
},
|
|
2966
|
+
{
|
|
2967
|
+
id: 'low',
|
|
2968
|
+
label: 'Low',
|
|
2969
|
+
meaning: 'low'
|
|
2970
|
+
}
|
|
2971
|
+
],
|
|
2972
|
+
defaultValueId: 'normal',
|
|
2973
|
+
targets: [
|
|
2974
|
+
{
|
|
2975
|
+
object: 'task',
|
|
2976
|
+
field: 'priorityLabel'
|
|
2977
|
+
}
|
|
2978
|
+
]
|
|
2979
|
+
};
|
|
2980
|
+
const TASK_TYPE_DEFINITION = {
|
|
2981
|
+
id: 'taskType',
|
|
2982
|
+
label: 'Type',
|
|
2983
|
+
plural: 'task types',
|
|
2984
|
+
object: 'task',
|
|
2985
|
+
restricted: true,
|
|
2986
|
+
meanings: CRM_TASK_KINDS,
|
|
2987
|
+
standardValues: [
|
|
2988
|
+
{
|
|
2989
|
+
id: 'call',
|
|
2990
|
+
label: 'Call',
|
|
2991
|
+
meaning: 'call'
|
|
2992
|
+
},
|
|
2993
|
+
{
|
|
2994
|
+
id: 'email',
|
|
2995
|
+
label: 'Email',
|
|
2996
|
+
meaning: 'email'
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
id: 'meeting',
|
|
3000
|
+
label: 'Meeting',
|
|
3001
|
+
meaning: 'meeting'
|
|
3002
|
+
},
|
|
3003
|
+
// Salesforce's "Other": a task that is none of the three conversations.
|
|
3004
|
+
{
|
|
3005
|
+
id: 'todo',
|
|
3006
|
+
label: 'To-do',
|
|
3007
|
+
meaning: 'todo'
|
|
3008
|
+
}
|
|
3009
|
+
],
|
|
3010
|
+
defaultValueId: 'todo',
|
|
3011
|
+
targets: [
|
|
3012
|
+
{
|
|
3013
|
+
object: 'task',
|
|
3014
|
+
field: 'typeLabel'
|
|
3015
|
+
}
|
|
3016
|
+
]
|
|
3017
|
+
};
|
|
3018
|
+
const TASK_SUBJECT_DEFINITION = {
|
|
3019
|
+
id: 'taskSubject',
|
|
3020
|
+
label: 'Subject',
|
|
3021
|
+
plural: 'task subjects',
|
|
3022
|
+
object: 'task',
|
|
3023
|
+
restricted: false,
|
|
3024
|
+
standardValues: [
|
|
3025
|
+
{
|
|
3026
|
+
id: 'call',
|
|
3027
|
+
label: 'Call'
|
|
3028
|
+
},
|
|
3029
|
+
{
|
|
3030
|
+
id: 'send-letter',
|
|
3031
|
+
label: 'Send Letter'
|
|
3032
|
+
},
|
|
3033
|
+
{
|
|
3034
|
+
id: 'send-quote',
|
|
3035
|
+
label: 'Send Quote'
|
|
3036
|
+
},
|
|
3037
|
+
{
|
|
3038
|
+
id: 'other',
|
|
3039
|
+
label: 'Other'
|
|
3040
|
+
}
|
|
3041
|
+
],
|
|
3042
|
+
// Suggestions for a free-text title: nothing holds them, nothing moves.
|
|
3043
|
+
targets: []
|
|
3044
|
+
};
|
|
3045
|
+
/** Every standard picklist field the CRM keeps, one document each. */ export const CRM_PICKLIST_DEFINITIONS = [
|
|
3046
|
+
LEAD_SOURCE_DEFINITION,
|
|
3047
|
+
LEAD_STATUS_DEFINITION,
|
|
3048
|
+
// Companies (AGL-3514).
|
|
3049
|
+
ACCOUNT_TYPE_DEFINITION,
|
|
3050
|
+
INDUSTRY_DEFINITION,
|
|
3051
|
+
RATING_DEFINITION,
|
|
3052
|
+
OWNERSHIP_DEFINITION,
|
|
3053
|
+
// Contacts (AGL-3515).
|
|
3054
|
+
SALUTATION_DEFINITION,
|
|
3055
|
+
// Deals (AGL-3516).
|
|
3056
|
+
OPPORTUNITY_TYPE_DEFINITION,
|
|
3057
|
+
// Deal contact roles (AGL-3521).
|
|
3058
|
+
OPPORTUNITY_CONTACT_ROLE_DEFINITION,
|
|
3059
|
+
// Tasks (AGL-3517).
|
|
3060
|
+
TASK_STATUS_DEFINITION,
|
|
3061
|
+
TASK_PRIORITY_DEFINITION,
|
|
3062
|
+
TASK_TYPE_DEFINITION,
|
|
3063
|
+
TASK_SUBJECT_DEFINITION
|
|
3064
|
+
];
|
|
3065
|
+
/** The picklists an org keeps, by document id. */ export const CRM_PICKLIST_IDS = CRM_PICKLIST_DEFINITIONS.map((definition)=>definition.id);
|
|
3066
|
+
/** The lead source value set's document id. */ export const CRM_LEAD_SOURCE_PICKLIST = 'leadSource';
|
|
3067
|
+
/** The salutation value set's document id (AGL-3515). */ export const CRM_SALUTATION_PICKLIST = 'salutation';
|
|
3068
|
+
/** A deal's Type value set's document id (AGL-3516). */ export const CRM_OPPORTUNITY_TYPE_PICKLIST = 'opportunityType';
|
|
3069
|
+
/** A deal contact's role value set's document id (AGL-3521). */ export const CRM_OPPORTUNITY_CONTACT_ROLE_PICKLIST = 'opportunityContactRole';
|
|
3070
|
+
/**
|
|
3071
|
+
* The built-in Lead source value each first-party door stamps (AGL-3519), by
|
|
3072
|
+
* the door's word for how it met a person — a capture's source, or the
|
|
3073
|
+
* outreach that reached them. `api`, `import` and `manual` are ways a record
|
|
3074
|
+
* was ADDED, not where a person came from, and stamp nothing.
|
|
3075
|
+
*/ export const CRM_LEAD_SOURCE_ORIGINS = {
|
|
3076
|
+
form: 'website-form',
|
|
3077
|
+
booking: 'booking',
|
|
3078
|
+
newsletter: 'newsletter-sign-up',
|
|
3079
|
+
member: 'site-member-sign-up',
|
|
3080
|
+
order: 'online-purchase',
|
|
3081
|
+
account: 'account-sign-up',
|
|
3082
|
+
sequence: 'sequence',
|
|
3083
|
+
emailCampaign: 'email-campaign'
|
|
3084
|
+
};
|
|
3085
|
+
export function isCrmPicklistId(value) {
|
|
3086
|
+
return typeof value === 'string' && CRM_PICKLIST_IDS.includes(value);
|
|
3087
|
+
}
|
|
3088
|
+
/** The definition an id names, or `null` for one the registry does not hold. */ export function crmPicklistDefinition(id) {
|
|
3089
|
+
var _CRM_PICKLIST_DEFINITIONS_find;
|
|
3090
|
+
return (_CRM_PICKLIST_DEFINITIONS_find = CRM_PICKLIST_DEFINITIONS.find((definition)=>definition.id === id)) != null ? _CRM_PICKLIST_DEFINITIONS_find : null;
|
|
3091
|
+
}
|
|
3092
|
+
/** The definitions whose values are managed on one Fields tab, in registry order. */ export function crmPicklistDefinitionsFor(object) {
|
|
3093
|
+
return CRM_PICKLIST_DEFINITIONS.filter((definition)=>definition.object === object);
|
|
3094
|
+
}
|
|
3095
|
+
/** The definition behind a registered id — total, because the id is typed. */ function definitionOf(id) {
|
|
3096
|
+
return crmPicklistDefinition(id);
|
|
3097
|
+
}
|
|
3098
|
+
/** The most values one picklist holds — a menu, not a table. */ export const CRM_PICKLIST_VALUES_MAX = PICKLIST_VALUES_MAX;
|
|
3099
|
+
/**
|
|
3100
|
+
* The values an org without a lead source document offered before the
|
|
3101
|
+
* standard set was built in. "Referral" and "Partner" are not standard: a
|
|
3102
|
+
* record holding one, in an org that never stored its list, reads as not in
|
|
3103
|
+
* the list until the value is written down as the org's own.
|
|
3104
|
+
*/ export const CRM_LEAD_SOURCE_STARTER_LABELS = [
|
|
3105
|
+
'Web',
|
|
3106
|
+
'Phone inquiry',
|
|
3107
|
+
'Referral',
|
|
3108
|
+
'Partner',
|
|
3109
|
+
'Purchased list',
|
|
3110
|
+
'Trade show',
|
|
3111
|
+
'Other'
|
|
3112
|
+
];
|
|
3113
|
+
/** A label as a picklist stores it: trimmed, single-spaced, capped. */ export const normalizeCrmPicklistLabel = normalizePicklistLabel;
|
|
3114
|
+
/** A new value's id — see `mintPicklistValueId`. */ export const crmPicklistValueId = mintPicklistValueId;
|
|
3115
|
+
/** A picklist of active values from labels, in order, with no default. */ export const crmPicklistFromLabels = picklistFromLabels;
|
|
3116
|
+
/**
|
|
3117
|
+
* A stored document as a picklist, or `null` for one that holds none —
|
|
3118
|
+
* tolerant, as `normalizePicklistValueSet` reads it, and without the
|
|
3119
|
+
* standard values merged in. {@link effectiveCrmPicklist} is what a reader
|
|
3120
|
+
* judges against.
|
|
3121
|
+
*/ export function normalizeCrmPicklist(raw) {
|
|
3122
|
+
return normalizePicklistValueSet(raw);
|
|
3123
|
+
}
|
|
3124
|
+
/** An org's list for one picklist as every reader should take it — see the block header. */ export function effectiveCrmPicklist(id, raw) {
|
|
3125
|
+
return effectivePicklistValueSet(definitionOf(id), raw);
|
|
3126
|
+
}
|
|
3127
|
+
/** The org's lead source list as every reader should take it. */ export function effectiveCrmLeadSourcePicklist(raw) {
|
|
3128
|
+
return effectiveCrmPicklist(CRM_LEAD_SOURCE_PICKLIST, raw);
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* The label a door stamps for `origin` (AGL-3519): its built-in value as the
|
|
3132
|
+
* org spells it, while that value is ACTIVE — an org that deactivated it has
|
|
3133
|
+
* said not to file people under it — else `null`, as for a word with none.
|
|
3134
|
+
*/ export function crmLeadSourceForOrigin(picklist, origin) {
|
|
3135
|
+
const id = typeof origin === 'string' ? CRM_LEAD_SOURCE_ORIGINS[origin] : undefined;
|
|
3136
|
+
if (!id || !Object.hasOwn(CRM_LEAD_SOURCE_ORIGINS, origin)) return null;
|
|
3137
|
+
const value = picklist.values.find((entry)=>entry.id === id);
|
|
3138
|
+
return (value == null ? void 0 : value.active) ? value.label : null;
|
|
3139
|
+
}
|
|
3140
|
+
/** Whether a value is one of a picklist's standard values — computed from its id, never stored. */ export function isStandardCrmPicklistValue(id, valueId) {
|
|
3141
|
+
return isStandardPicklistValueId(definitionOf(id), valueId);
|
|
3142
|
+
}
|
|
3143
|
+
/** The value a label names, in any case or spacing — `null` for none. */ export const crmPicklistValueByLabel = picklistValueByLabel;
|
|
3144
|
+
/** The values a picker offers. */ export const crmPicklistActiveValues = picklistActiveValues;
|
|
3145
|
+
/** The label a new record starts with — only while the default value is active. */ export const crmPicklistDefaultLabel = picklistDefaultLabel;
|
|
3146
|
+
/**
|
|
3147
|
+
* The sentence a value outside a restricted picklist is refused with,
|
|
3148
|
+
* naming what the list allows, and where to add one when it allows none.
|
|
3149
|
+
*/ export function crmPicklistRefusal(id, picklist) {
|
|
3150
|
+
const definition = definitionOf(id);
|
|
3151
|
+
return picklistRefusalSentence(picklist, {
|
|
3152
|
+
field: definition.label,
|
|
3153
|
+
empty: `This organization has no active ${definition.plural}. Add one under ` + `CRM › Fields › ${CRM_PICKLIST_OBJECT_LABELS[definition.object]}.`
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
/** The sentence a lead source outside the list is refused with. */ export function crmLeadSourceRefusal(picklist) {
|
|
3157
|
+
return crmPicklistRefusal(CRM_LEAD_SOURCE_PICKLIST, picklist);
|
|
3158
|
+
}
|
|
3159
|
+
/**
|
|
3160
|
+
* Whether a write may store `value` as a record's value of picklist `id`,
|
|
3161
|
+
* and the label it stores — `judgePicklistValue` under the definition's
|
|
3162
|
+
* restriction: blank clears, an active value stores the list's spelling,
|
|
3163
|
+
* the record's `current` value is always kept, and on a restricted
|
|
3164
|
+
* picklist anything else is refused with {@link crmPicklistRefusal}.
|
|
3165
|
+
*/ export function judgeCrmPicklistValue(id, picklist, value, current) {
|
|
3166
|
+
return judgePicklistValue(picklist, value, {
|
|
3167
|
+
restricted: definitionOf(id).restricted,
|
|
3168
|
+
current,
|
|
3169
|
+
refusal: ()=>crmPicklistRefusal(id, picklist)
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
/**
|
|
3173
|
+
* A deal's contact roles with each role judged against the org's
|
|
3174
|
+
* `opportunityContactRole` list (AGL-3521): an active value stored as the
|
|
3175
|
+
* list spells it, a blank cleared, and the role a contact already holds on
|
|
3176
|
+
* the deal (`current`) kept — or the first refusal, naming what the list
|
|
3177
|
+
* allows.
|
|
3178
|
+
*/ export function judgeDealContactRoles(picklist, roles, current = []) {
|
|
3179
|
+
const judged = [];
|
|
3180
|
+
for (const row of roles){
|
|
3181
|
+
var _row_role;
|
|
3182
|
+
var _current_find;
|
|
3183
|
+
const held = (_current_find = current.find((entry)=>entry.contactId === row.contactId)) == null ? void 0 : _current_find.role;
|
|
3184
|
+
const verdict = judgeCrmPicklistValue('opportunityContactRole', picklist, (_row_role = row.role) != null ? _row_role : '', held);
|
|
3185
|
+
if (verdict.ok === false) return verdict;
|
|
3186
|
+
judged.push(_extends({
|
|
3187
|
+
contactId: row.contactId
|
|
3188
|
+
}, verdict.value ? {
|
|
3189
|
+
role: verdict.value
|
|
3190
|
+
} : {}, {
|
|
3191
|
+
primary: row.primary
|
|
3192
|
+
}));
|
|
3193
|
+
}
|
|
3194
|
+
return {
|
|
3195
|
+
ok: true,
|
|
3196
|
+
roles: judged
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
/** {@link judgeCrmPicklistValue} for the lead source. */ export function judgeCrmLeadSource(picklist, value, current) {
|
|
3200
|
+
return judgeCrmPicklistValue(CRM_LEAD_SOURCE_PICKLIST, picklist, value, current);
|
|
3201
|
+
}
|
|
3202
|
+
/**
|
|
3203
|
+
* What a select offers for a record holding `current`: every active value
|
|
3204
|
+
* in order, then the record's own value when the list would not otherwise
|
|
3205
|
+
* show it, so the select keeps what the record holds until it is changed.
|
|
3206
|
+
*/ export const crmPicklistOptions = picklistOptions;
|
|
3207
|
+
/**
|
|
3208
|
+
* Where a label sorts: its value's position in the list, unlisted values
|
|
3209
|
+
* after every listed one, and a record with none last. The order the admin
|
|
3210
|
+
* chose is the order a sort by the field reads in, as Salesforce's does.
|
|
3211
|
+
*/ export const crmPicklistRank = picklistRank;
|
|
3212
|
+
/*------------------------------------------
|
|
3213
|
+
* A LEAD'S STATUS LABEL (AGL-3512).
|
|
3214
|
+
*
|
|
3215
|
+
* `status` is the meaning and stays the truth: a writer that knows only the
|
|
3216
|
+
* meaning — an automation, an inbox reply, a rule — sets `status` alone and
|
|
3217
|
+
* the lead still reads right. `statusLabel` names which of the org's values
|
|
3218
|
+
* of that meaning the lead holds, and is believed only while it IS one of
|
|
3219
|
+
* that meaning; otherwise, or when absent, the lead shows the default value
|
|
3220
|
+
* when it is active and of that meaning, else the first active value of
|
|
3221
|
+
* that meaning, else the standard label.
|
|
3222
|
+
*-----------------------------------------*/ /** The lead status value set's document id. */ export const CRM_LEAD_STATUS_PICKLIST = 'leadStatus';
|
|
3223
|
+
/** The field a lead's status label is stored in, beside `status`. */ export const CRM_LEAD_STATUS_LABEL_FIELD = 'statusLabel';
|
|
3224
|
+
/** The org's lead status list as every reader should take it. */ export function effectiveCrmLeadStatusPicklist(raw) {
|
|
3225
|
+
return effectiveCrmPicklist(CRM_LEAD_STATUS_PICKLIST, raw);
|
|
3226
|
+
}
|
|
3227
|
+
/** The standard lead statuses alone — what a reader answers before the org's list is read. */ export const STANDARD_CRM_LEAD_STATUS_PICKLIST = effectiveCrmLeadStatusPicklist(null);
|
|
3228
|
+
/**
|
|
3229
|
+
* The value a lead with `status` shows when it names none of its own: the
|
|
3230
|
+
* default when active and of that meaning, else the first active value of
|
|
3231
|
+
* that meaning, else the first of that meaning at all.
|
|
3232
|
+
*/ export function crmLeadStatusValueFor(picklist, status) {
|
|
3233
|
+
var _ref, _ref1;
|
|
3234
|
+
const ofMeaning = picklist.values.filter((value)=>value.meaning === status);
|
|
3235
|
+
const preferred = ofMeaning.find((value)=>value.id === picklist.defaultValueId && value.active);
|
|
3236
|
+
return (_ref = (_ref1 = preferred != null ? preferred : ofMeaning.find((value)=>value.active)) != null ? _ref1 : ofMeaning[0]) != null ? _ref : null;
|
|
3237
|
+
}
|
|
3238
|
+
/** The label a writer stamps beside `status` when it sets a meaning and names no value. */ export function crmLeadStatusLabelFor(picklist, status) {
|
|
3239
|
+
var _ref;
|
|
3240
|
+
var _crmLeadStatusValueFor;
|
|
3241
|
+
return (_ref = (_crmLeadStatusValueFor = crmLeadStatusValueFor(picklist, status)) == null ? void 0 : _crmLeadStatusValueFor.label) != null ? _ref : CRM_LEAD_STATUS_LABELS[status];
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* How a lead's status reads: its own `statusLabel` while that is a value of
|
|
3245
|
+
* its `status`'s meaning (as the list spells it), else the meaning's label
|
|
3246
|
+
* by {@link crmLeadStatusLabelFor}. A label the list no longer holds is shown
|
|
3247
|
+
* as stored while nothing contradicts it — a value deleted without moving
|
|
3248
|
+
* every lead off it reads as what the lead was given.
|
|
3249
|
+
*/ export function crmLeadStatusLabel(lead, picklist = STANDARD_CRM_LEAD_STATUS_PICKLIST) {
|
|
3250
|
+
const status = crmLeadStatus(lead);
|
|
3251
|
+
const held = normalizeCrmPicklistLabel(lead == null ? void 0 : lead.statusLabel);
|
|
3252
|
+
if (held) {
|
|
3253
|
+
const value = picklistValueByLabel(picklist, held);
|
|
3254
|
+
if ((value == null ? void 0 : value.meaning) === status) return value.label;
|
|
3255
|
+
if (!value) return held;
|
|
3256
|
+
}
|
|
3257
|
+
return crmLeadStatusLabelFor(picklist, status);
|
|
3258
|
+
}
|
|
3259
|
+
/**
|
|
3260
|
+
* What a write naming a lead status stores (AGL-3512): an ACTIVE value's
|
|
3261
|
+
* label, in any case or spacing, stores its meaning and its label; one of
|
|
3262
|
+
* the meanings themselves (`working`) stores that meaning and the label
|
|
3263
|
+
* {@link crmLeadStatusLabelFor} gives it; the lead's `current` label is
|
|
3264
|
+
* kept even when inactive. `allowed` narrows the meanings this door may
|
|
3265
|
+
* set — every door but a conversion leaves Qualified out — and anything
|
|
3266
|
+
* else is refused naming what the door accepts.
|
|
3267
|
+
*/ export function resolveCrmLeadStatusWrite(picklist, requested, options = {}) {
|
|
3268
|
+
var _options_allowed;
|
|
3269
|
+
const allowed = (_options_allowed = options.allowed) != null ? _options_allowed : CRM_LEAD_STATUSES;
|
|
3270
|
+
const text = normalizeCrmPicklistLabel(requested);
|
|
3271
|
+
const refuse = ()=>{
|
|
3272
|
+
const labels = picklist.values.filter((value)=>value.active && allowed.includes(value.meaning)).map((value)=>value.label);
|
|
3273
|
+
return {
|
|
3274
|
+
ok: false,
|
|
3275
|
+
error: `Lead status must be one of: ${labels.join(', ')}.`
|
|
3276
|
+
};
|
|
3277
|
+
};
|
|
3278
|
+
if (!text) return refuse();
|
|
3279
|
+
const value = picklistValueByLabel(picklist, text);
|
|
3280
|
+
const current = options.current ? normalizeCrmPicklistLabel(options.current.statusLabel) : '';
|
|
3281
|
+
const keeps = Boolean(value && current && picklistLabelKeyOf(current) === picklistLabelKeyOf(value.label));
|
|
3282
|
+
if (value && isCrmLeadStatus(value.meaning) && (value.active || keeps)) {
|
|
3283
|
+
return allowed.includes(value.meaning) ? {
|
|
3284
|
+
ok: true,
|
|
3285
|
+
status: value.meaning,
|
|
3286
|
+
statusLabel: value.label
|
|
3287
|
+
} : refuse();
|
|
3288
|
+
}
|
|
3289
|
+
const meaning = text.toLowerCase();
|
|
3290
|
+
if (isCrmLeadStatus(meaning) && allowed.includes(meaning)) {
|
|
3291
|
+
return {
|
|
3292
|
+
ok: true,
|
|
3293
|
+
status: meaning,
|
|
3294
|
+
statusLabel: crmLeadStatusLabelFor(picklist, meaning)
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
return refuse();
|
|
3298
|
+
}
|
|
3299
|
+
const picklistLabelKeyOf = (label)=>normalizeCrmPicklistLabel(label).toLowerCase();
|
|
3300
|
+
/**
|
|
3301
|
+
* The values a lead status select offers for a lead holding `current`:
|
|
3302
|
+
* every active value whose meaning `allowed` admits, in the list's order,
|
|
3303
|
+
* then the lead's own value when the list would not otherwise show it.
|
|
3304
|
+
*/ export function crmLeadStatusOptions(picklist, allowed = CRM_LEAD_STATUSES, current) {
|
|
3305
|
+
const options = picklist.values.filter((value)=>value.active && allowed.includes(value.meaning)).map((value)=>({
|
|
3306
|
+
label: value.label,
|
|
3307
|
+
status: value.meaning,
|
|
3308
|
+
inactive: false
|
|
3309
|
+
}));
|
|
3310
|
+
if (current) {
|
|
3311
|
+
const label = crmLeadStatusLabel(current, picklist);
|
|
3312
|
+
if (!options.some((option)=>option.label === label)) {
|
|
3313
|
+
options.push({
|
|
3314
|
+
label,
|
|
3315
|
+
status: crmLeadStatus(current),
|
|
3316
|
+
inactive: true
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
return options;
|
|
3321
|
+
}
|
|
3322
|
+
/*==========================================
|
|
3323
|
+
* SEMANTIC PICKLISTS ON A TASK (AGL-3517).
|
|
3324
|
+
*
|
|
3325
|
+
* Status, Priority and Type keep the task's own `status`, `priority` and
|
|
3326
|
+
* `kind` as the MEANING every query, reminder and automation reads, and
|
|
3327
|
+
* store the org's label beside it. These helpers are the two directions:
|
|
3328
|
+
* what a task shows for its meaning and label, and what a write stores for
|
|
3329
|
+
* a label or a meaning a person, a file or an API caller named.
|
|
3330
|
+
*=========================================*/ /** The label a value of `meaning` reads as: the first active one, else the first at all. */ export function crmPicklistMeaningLabel(picklist, meaning) {
|
|
3331
|
+
var _ref, _held_find;
|
|
3332
|
+
var _this;
|
|
3333
|
+
if (typeof meaning !== 'string' || !meaning) return null;
|
|
3334
|
+
const held = picklist.values.filter((value)=>value.meaning === meaning);
|
|
3335
|
+
return (_ref = (_this = (_held_find = held.find((value)=>value.active)) != null ? _held_find : held[0]) == null ? void 0 : _this.label) != null ? _ref : null;
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* What a record shows for a semantic field: its own label when it holds
|
|
3339
|
+
* one, else the first active value of its meaning, else the meaning itself.
|
|
3340
|
+
*/ export function crmPicklistShownLabel(picklist, meaning, label) {
|
|
3341
|
+
var _crmPicklistMeaningLabel;
|
|
3342
|
+
const own = normalizeCrmPicklistLabel(label);
|
|
3343
|
+
if (own) return own;
|
|
3344
|
+
return (_crmPicklistMeaningLabel = crmPicklistMeaningLabel(picklist, meaning)) != null ? _crmPicklistMeaningLabel : String(meaning != null ? meaning : '');
|
|
3345
|
+
}
|
|
3346
|
+
/**
|
|
3347
|
+
* The label a NEW record of `meaning` starts with: the list's default when
|
|
3348
|
+
* it means that, else the first active value that does.
|
|
3349
|
+
*/ export function crmPicklistLabelForNew(picklist, meaning) {
|
|
3350
|
+
const fallback = crmPicklistDefaultLabel(picklist);
|
|
3351
|
+
const value = fallback ? crmPicklistValueByLabel(picklist, fallback) : null;
|
|
3352
|
+
return value && value.meaning === meaning ? value.label : crmPicklistMeaningLabel(picklist, meaning);
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* A label OR a meaning, as a write names it, resolved against picklist `id`:
|
|
3356
|
+
*
|
|
3357
|
+
* - an active value's label (any case or spacing) stores that value's
|
|
3358
|
+
* label and its meaning;
|
|
3359
|
+
* - the record's `current` label is kept, with its value's meaning, even
|
|
3360
|
+
* when the value has since been deactivated;
|
|
3361
|
+
* - one of the definition's meanings (`high`, `done`, `call`) stores that
|
|
3362
|
+
* meaning with the first active value's label — or the record's current
|
|
3363
|
+
* label when that already means it;
|
|
3364
|
+
* - anything else is refused naming the values the list allows.
|
|
3365
|
+
*
|
|
3366
|
+
* `null` answers a blank input: the caller keeps its own default.
|
|
3367
|
+
*/ export function resolveCrmSemanticPicklistWrite(id, picklist, input, current) {
|
|
3368
|
+
var _definition_meanings;
|
|
3369
|
+
const text = normalizeCrmPicklistLabel(input);
|
|
3370
|
+
if (!text) return null;
|
|
3371
|
+
const definition = definitionOf(id);
|
|
3372
|
+
const meanings = (_definition_meanings = definition.meanings) != null ? _definition_meanings : [];
|
|
3373
|
+
const held = normalizeCrmPicklistLabel(current);
|
|
3374
|
+
const byLabel = crmPicklistValueByLabel(picklist, text);
|
|
3375
|
+
const keeps = held && held.toLowerCase() === text.toLowerCase();
|
|
3376
|
+
if ((byLabel == null ? void 0 : byLabel.meaning) && (byLabel.active || keeps)) {
|
|
3377
|
+
return {
|
|
3378
|
+
ok: true,
|
|
3379
|
+
meaning: byLabel.meaning,
|
|
3380
|
+
label: keeps ? held : byLabel.label
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
const meaning = meanings.find((entry)=>entry === text.toLowerCase());
|
|
3384
|
+
if (meaning) {
|
|
3385
|
+
const heldValue = held ? crmPicklistValueByLabel(picklist, held) : null;
|
|
3386
|
+
return {
|
|
3387
|
+
ok: true,
|
|
3388
|
+
meaning,
|
|
3389
|
+
label: (heldValue == null ? void 0 : heldValue.meaning) === meaning ? held : crmPicklistMeaningLabel(picklist, meaning)
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
return {
|
|
3393
|
+
ok: false,
|
|
3394
|
+
error: crmPicklistRefusal(id, picklist)
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
/** The picklist id behind each of a task's semantic fields. */ export const CRM_TASK_PICKLIST_IDS = {
|
|
3398
|
+
status: 'taskStatus',
|
|
3399
|
+
priority: 'taskPriority',
|
|
3400
|
+
type: 'taskType'
|
|
3401
|
+
};
|
|
3402
|
+
/** The org's three task lists from their stored documents — standard values alone for none. */ export function effectiveCrmTaskPicklists(raw = {}) {
|
|
3403
|
+
return {
|
|
3404
|
+
status: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.status, raw.status),
|
|
3405
|
+
priority: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.priority, raw.priority),
|
|
3406
|
+
type: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.type, raw.type)
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
/** What a task shows for its status, priority and type — see `crmPicklistShownLabel`. */ export function crmTaskPicklistLabels(task, picklists) {
|
|
3410
|
+
var _task_status, _task_priority, _task_kind;
|
|
3411
|
+
return {
|
|
3412
|
+
status: crmPicklistShownLabel(picklists.status, (_task_status = task.status) != null ? _task_status : 'open', task.statusLabel),
|
|
3413
|
+
priority: crmPicklistShownLabel(picklists.priority, (_task_priority = task.priority) != null ? _task_priority : 'normal', task.priorityLabel),
|
|
3414
|
+
type: crmPicklistShownLabel(picklists.type, (_task_kind = task.kind) != null ? _task_kind : 'todo', task.typeLabel)
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3417
|
+
/**
|
|
3418
|
+
* The status a tick or an untick writes: done with the first active done
|
|
3419
|
+
* value's label ("Completed"), or open with the label a new task starts
|
|
3420
|
+
* with ("Not Started").
|
|
3421
|
+
*/ export function crmTaskStatusWrite(picklist, done) {
|
|
3422
|
+
return done ? {
|
|
3423
|
+
status: 'done',
|
|
3424
|
+
statusLabel: crmPicklistMeaningLabel(picklist, 'done')
|
|
3425
|
+
} : {
|
|
3426
|
+
status: 'open',
|
|
3427
|
+
statusLabel: crmPicklistLabelForNew(picklist, 'open')
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
/** The labels a new task of these meanings starts with. */ export function crmTaskLabelsForNew(picklists, meanings) {
|
|
3431
|
+
var _meanings_status;
|
|
3432
|
+
return {
|
|
3433
|
+
statusLabel: crmPicklistLabelForNew(picklists.status, (_meanings_status = meanings.status) != null ? _meanings_status : 'open'),
|
|
3434
|
+
priorityLabel: crmPicklistLabelForNew(picklists.priority, meanings.priority),
|
|
3435
|
+
typeLabel: crmPicklistLabelForNew(picklists.type, meanings.kind)
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
/** The name a lead is listed under: the name it carries, else its address. */ export function crmLeadDisplayName(lead) {
|
|
3439
|
+
var _ref, _ref1;
|
|
3440
|
+
const name = String((_ref = lead == null ? void 0 : lead['name']) != null ? _ref : '').trim();
|
|
3441
|
+
return name || String((_ref1 = lead == null ? void 0 : lead['email']) != null ? _ref1 : '').trim();
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* A lead's status as the list and the filter should read it.
|
|
3445
|
+
*
|
|
3446
|
+
* `new` for a lead that carries no status at all — every lead captured
|
|
3447
|
+
* before the CRM existed, and every lead the capture door writes today,
|
|
3448
|
+
* because `addHostLead` stamps none. Reading the absence as `new` is what
|
|
3449
|
+
* lets the section open on the leads a site already holds rather than on an
|
|
3450
|
+
* empty list until each one has been touched once. A stored value the union
|
|
3451
|
+
* does not name also reads as `new`: it is a document some other writer
|
|
3452
|
+
* produced, and refusing to list it would hide a person.
|
|
3453
|
+
*/ export function crmLeadStatus(lead) {
|
|
3454
|
+
const status = lead == null ? void 0 : lead.status;
|
|
3455
|
+
return isCrmLeadStatus(status) ? status : 'new';
|
|
3456
|
+
}
|
|
3457
|
+
/** Whether a lead still needs working — see {@link CRM_LEAD_OPEN_STATUSES}. */ export function isCrmLeadOpen(lead) {
|
|
3458
|
+
return CRM_LEAD_OPEN_STATUSES.includes(crmLeadStatus(lead));
|
|
3459
|
+
}
|
|
3460
|
+
/*==========================================
|
|
3461
|
+
* SAVED VIEWS (AGL-2617) — a list the way one person works it.
|
|
3462
|
+
*
|
|
3463
|
+
* "My open leads in Texas" is a list a rep opens every morning, and it is
|
|
3464
|
+
* three things at once: the filters that pick the people, the columns they
|
|
3465
|
+
* want to see about them, and the order they want them in. A `ContactSegment`
|
|
3466
|
+
* kept the first of those for two dimensions — tags and capture sources —
|
|
3467
|
+
* and nothing kept the rest, so every reload put the reader back at the
|
|
3468
|
+
* whole list. A view keeps all three, for every list in the CRM, under a
|
|
3469
|
+
* name, and is addressable: `?view=<id>` on the section's own URL opens it.
|
|
3470
|
+
*
|
|
3471
|
+
* ## The filters are the LIST'S grammar, not a second one
|
|
3472
|
+
*
|
|
3473
|
+
* A clause here is `{ field, op, value }` — structurally the
|
|
3474
|
+
* `ListFilterRequest` the shared list-filter grammar already speaks, the
|
|
3475
|
+
* request a grid's filter panel produces and the one the query translator
|
|
3476
|
+
* and the in-memory matcher both read. A view stores a list of them and
|
|
3477
|
+
* nothing more, so a view can only ask what the list can already answer,
|
|
3478
|
+
* and a list that gains a filterable field gains it for its views at no
|
|
3479
|
+
* cost. The type is restated here rather than imported because this module
|
|
3480
|
+
* is pure data and the grammar's home is a UI library; the shape is the
|
|
3481
|
+
* contract, and `normalizeCrmViewFilters` is what holds a stored document
|
|
3482
|
+
* to it.
|
|
3483
|
+
*
|
|
3484
|
+
* ## Mine, and shared
|
|
3485
|
+
*
|
|
3486
|
+
* A view is one person's working arrangement until they say otherwise, so
|
|
3487
|
+
* it lists only for its owner; `shared` puts it in front of everyone who
|
|
3488
|
+
* can read the section. Both are stamped `visibleTo` like every CRM row,
|
|
3489
|
+
* because a view is a description of the people it selects and answers to
|
|
3490
|
+
* the same authority they do — a private view is hidden by the LISTING, not
|
|
3491
|
+
* by the rules, and the rules let the creator or an org-wide member change
|
|
3492
|
+
* or remove it.
|
|
3493
|
+
*
|
|
3494
|
+
* ## The default is the reader's, not the view's
|
|
3495
|
+
*
|
|
1913
3496
|
* Which view a section opens on is a preference of one person for one
|
|
1914
3497
|
* organization, so it lives on the reader's own profile document beside
|
|
1915
3498
|
* their notification mutes rather than on the view — a shared view somebody
|
|
@@ -2328,11 +3911,17 @@ export function crmEmailStatusKey(record) {
|
|
|
2328
3911
|
// A status outside the vocabulary reads as nothing known, as `readEmailState` reads it.
|
|
2329
3912
|
return isEmailStateStatus(status) ? status : CRM_EMAIL_STATUS_NONE;
|
|
2330
3913
|
}
|
|
2331
|
-
/**
|
|
2332
|
-
|
|
3914
|
+
/**
|
|
3915
|
+
* A picklist label as a list query compares it — the key a target's
|
|
3916
|
+
* `keyField` holds beside the label — `null` for none.
|
|
3917
|
+
*/ export function crmPicklistKey(value) {
|
|
2333
3918
|
const key = SEARCH_KEY(normalizeCrmPicklistLabel(value));
|
|
2334
3919
|
return key || null;
|
|
2335
3920
|
}
|
|
3921
|
+
/** A lead's lead source as the list compares it: the picklist's key, `null` for none. */ export const CRM_LEAD_SOURCE_KEY_FIELD = 'leadSourceKey';
|
|
3922
|
+
export function crmLeadSourceKey(value) {
|
|
3923
|
+
return crmPicklistKey(value);
|
|
3924
|
+
}
|
|
2336
3925
|
/** What a lead's search box reads (AGL-3246). */ export const CRM_LEAD_SEARCH_SOURCES = [
|
|
2337
3926
|
'name',
|
|
2338
3927
|
'email',
|
|
@@ -2362,28 +3951,67 @@ function leadCampaignIds(lead) {
|
|
|
2362
3951
|
scopedCampaignIds: scopedSearchTokens(lead['visibleTo'], leadCampaignIds(lead)),
|
|
2363
3952
|
status: crmLeadStatus(lead),
|
|
2364
3953
|
leadSourceKey: crmLeadSourceKey(lead['leadSource']),
|
|
2365
|
-
emailStatus: crmEmailStatusKey(lead)
|
|
3954
|
+
emailStatus: crmEmailStatusKey(lead),
|
|
3955
|
+
// The Industry and Rating the Leads list filters by (AGL-3513).
|
|
3956
|
+
industryKey: crmPicklistKey(lead['industry']),
|
|
3957
|
+
ratingKey: crmPicklistKey(lead['rating'])
|
|
2366
3958
|
});
|
|
2367
3959
|
}
|
|
2368
3960
|
/** What a company's search box reads: its name and its domain. */ export const CRM_COMPANY_SEARCH_SOURCES = [
|
|
2369
3961
|
'name',
|
|
2370
3962
|
'domain'
|
|
2371
3963
|
];
|
|
2372
|
-
|
|
3964
|
+
/**
|
|
3965
|
+
* Every field the Companies list queries by: the search tokens, and the
|
|
3966
|
+
* key of each picklist field that names one (AGL-3514) — `null` for none,
|
|
3967
|
+
* so "no value" is a value a query can ask for.
|
|
3968
|
+
*/ export function crmCompanyListFields(record) {
|
|
2373
3969
|
const company = record;
|
|
2374
|
-
return crmSearchFields(company['visibleTo'], CRM_COMPANY_SEARCH_SOURCES.map((field)=>company[field]))
|
|
3970
|
+
return _extends({}, crmSearchFields(company['visibleTo'], CRM_COMPANY_SEARCH_SOURCES.map((field)=>company[field])), {
|
|
3971
|
+
typeKey: crmPicklistKey(company['type']),
|
|
3972
|
+
industryKey: crmPicklistKey(company['industry']),
|
|
3973
|
+
ratingKey: crmPicklistKey(company['rating']),
|
|
3974
|
+
accountSourceKey: crmPicklistKey(company['accountSource'])
|
|
3975
|
+
});
|
|
2375
3976
|
}
|
|
2376
3977
|
/**
|
|
2377
3978
|
* What a deal's search box reads: its title (AGL-3315) — as word prefixes,
|
|
2378
3979
|
* and as `titleLower`, the whole title's key, which a reader whose access
|
|
2379
|
-
* is some sites searches by its start.
|
|
3980
|
+
* is some sites searches by its start. And the keys the Deals list filters
|
|
3981
|
+
* its Type and Lead source by (AGL-3516): each label as the picklist
|
|
3982
|
+
* compares it, `null` for none.
|
|
2380
3983
|
*/ export function crmDealListFields(record) {
|
|
2381
3984
|
const deal = record;
|
|
2382
3985
|
return _extends({}, crmSearchFields(deal['visibleTo'], [
|
|
2383
3986
|
deal['title']
|
|
2384
3987
|
]), {
|
|
2385
|
-
titleLower: SEARCH_KEY(typeof deal['title'] === 'string' ? deal['title'] : '')
|
|
2386
|
-
|
|
3988
|
+
titleLower: SEARCH_KEY(typeof deal['title'] === 'string' ? deal['title'] : ''),
|
|
3989
|
+
typeKey: crmPicklistKey(deal['type']),
|
|
3990
|
+
leadSourceKey: crmPicklistKey(deal['leadSource'])
|
|
3991
|
+
}, crmDealContactRoleListFields(deal));
|
|
3992
|
+
}
|
|
3993
|
+
/**
|
|
3994
|
+
* The arrays a deal's contact roles are found by (AGL-3521): every contact
|
|
3995
|
+
* on it — `contactRoleContactIds`, which a contact's page asks
|
|
3996
|
+
* `array-contains` at the organization level, and the same ids behind each
|
|
3997
|
+
* scope token (`scopedContactRoleContactIds`, `host:x~contactId`), which it
|
|
3998
|
+
* asks under a site in the one array clause a query has — and the keys of
|
|
3999
|
+
* the roles they hold, which a rename of a role finds the deals by.
|
|
4000
|
+
*/ export function crmDealContactRoleListFields(deal) {
|
|
4001
|
+
const roles = dealContactRolesOf(deal);
|
|
4002
|
+
const contactRoleContactIds = roles.map((row)=>row.contactId);
|
|
4003
|
+
const keys = new Set();
|
|
4004
|
+
for (const row of roles){
|
|
4005
|
+
const key = crmPicklistKey(row.role);
|
|
4006
|
+
if (key) keys.add(key);
|
|
4007
|
+
}
|
|
4008
|
+
return {
|
|
4009
|
+
contactRoleContactIds,
|
|
4010
|
+
scopedContactRoleContactIds: scopedSearchTokens(deal['visibleTo'], contactRoleContactIds),
|
|
4011
|
+
contactRoleKeys: [
|
|
4012
|
+
...keys
|
|
4013
|
+
]
|
|
4014
|
+
};
|
|
2387
4015
|
}
|
|
2388
4016
|
/** What a task's search box reads: its title. */ export function crmTaskListFields(record) {
|
|
2389
4017
|
const task = record;
|
|
@@ -2403,8 +4031,8 @@ export function crmCompanyListFields(record) {
|
|
|
2403
4031
|
* `{groupId}:{field}` this holder has one (for "is set")
|
|
2404
4032
|
* `*:{field}={value}`, `*:{field}` any holder's, for the organization level
|
|
2405
4033
|
*
|
|
2406
|
-
* `field` is `owner`, `stage`, `source`, `company`, `tag
|
|
2407
|
-
* plus two presence-only fields: `orders` for a holder the person has bought
|
|
4034
|
+
* `field` is `owner`, `stage`, `source`, `company`, `tag`, `custom.{key}` or
|
|
4035
|
+
* `leadSource` (AGL-3511), plus two presence-only fields: `orders` for a holder the person has bought
|
|
2408
4036
|
* from, and `ltv` for one they are worth something to — the per-holder
|
|
2409
4037
|
* figures a range cannot reach, asked only whether there are any.
|
|
2410
4038
|
* A text value is keyed lower-cased and single-spaced; a number or a flag
|
|
@@ -2455,6 +4083,8 @@ export function crmCompanyListFields(record) {
|
|
|
2455
4083
|
if (/^[A-Za-z0-9_-]{1,64}$/.test(key)) add(`custom.${key}`, value);
|
|
2456
4084
|
}
|
|
2457
4085
|
}
|
|
4086
|
+
// Keyed as `crmPicklistKey` compares a label, so a rename's rewrite restamps it.
|
|
4087
|
+
add('leadSource', facet['leadSource']);
|
|
2458
4088
|
}
|
|
2459
4089
|
/** Every facet key a contact carries — see the block above. */ export function crmContactFacetKeys(record) {
|
|
2460
4090
|
const facets = record[CONTACT_FACETS_FIELD];
|
|
@@ -2523,7 +4153,10 @@ export function crmContactListFields(record) {
|
|
|
2523
4153
|
'status',
|
|
2524
4154
|
'leadSource',
|
|
2525
4155
|
'emailState',
|
|
2526
|
-
'campaignIds'
|
|
4156
|
+
'campaignIds',
|
|
4157
|
+
// AGL-3513.
|
|
4158
|
+
'industry',
|
|
4159
|
+
'rating'
|
|
2527
4160
|
],
|
|
2528
4161
|
contacts: [
|
|
2529
4162
|
'visibleTo',
|
|
@@ -2534,11 +4167,19 @@ export function crmContactListFields(record) {
|
|
|
2534
4167
|
],
|
|
2535
4168
|
companies: [
|
|
2536
4169
|
'visibleTo',
|
|
2537
|
-
...CRM_COMPANY_SEARCH_SOURCES
|
|
4170
|
+
...CRM_COMPANY_SEARCH_SOURCES,
|
|
4171
|
+
'type',
|
|
4172
|
+
'industry',
|
|
4173
|
+
'rating',
|
|
4174
|
+
'accountSource'
|
|
2538
4175
|
],
|
|
2539
4176
|
deals: [
|
|
2540
4177
|
'visibleTo',
|
|
2541
|
-
'title'
|
|
4178
|
+
'title',
|
|
4179
|
+
'type',
|
|
4180
|
+
'leadSource',
|
|
4181
|
+
'contactRoles',
|
|
4182
|
+
'contactId'
|
|
2542
4183
|
],
|
|
2543
4184
|
crmTasks: [
|
|
2544
4185
|
'visibleTo',
|