@aglyn/plugins-crm 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/README.md +2 -1
- package/package.json +13 -13
- package/src/lib/components/activity-list.js +12 -5
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.d.ts +1 -4
- package/src/lib/components/companies-bulk-bar.js +7 -20
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.d.ts +3 -3
- package/src/lib/components/companies-section.js +149 -46
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +281 -20
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-properties-card.js +99 -8
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +3 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +33 -10
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +0 -69
- package/src/lib/components/contact-properties-card.js +350 -60
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-reports-to-field.d.ts +46 -0
- package/src/lib/components/contact-reports-to-field.js +67 -0
- package/src/lib/components/contact-reports-to-field.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
- package/src/lib/components/contacts-bulk-bar.js +7 -19
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +46 -33
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-call-actions.d.ts +10 -0
- package/src/lib/components/crm-call-actions.js +26 -9
- package/src/lib/components/crm-call-actions.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +15 -0
- package/src/lib/components/crm-record-picker.js +16 -10
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
- package/src/lib/components/crm-transfer-buttons.js +167 -0
- package/src/lib/components/crm-transfer-buttons.js.map +1 -0
- package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
- package/src/lib/components/deal-contact-roles-card.js +368 -0
- package/src/lib/components/deal-contact-roles-card.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +3 -2
- package/src/lib/components/deal-detail-page.js +25 -6
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.d.ts +10 -0
- package/src/lib/components/deal-edit-drawer.js +219 -10
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-properties-card.d.ts +7 -0
- package/src/lib/components/deal-properties-card.js +34 -3
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.d.ts +4 -0
- package/src/lib/components/deal-stage-card.js +10 -2
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.d.ts +0 -3
- package/src/lib/components/deals-bulk-bar.js +7 -20
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/deals-section.d.ts +3 -3
- package/src/lib/components/deals-section.js +123 -63
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +31 -17
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-convert-dialog.d.ts +4 -0
- package/src/lib/components/lead-convert-dialog.js +23 -1
- package/src/lib/components/lead-convert-dialog.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +8 -2
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-profile-fields.d.ts +71 -0
- package/src/lib/components/lead-profile-fields.js +386 -0
- package/src/lib/components/lead-profile-fields.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +2 -1
- package/src/lib/components/lead-properties-card.js +85 -176
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-source-select.d.ts +4 -23
- package/src/lib/components/lead-source-select.js +9 -73
- package/src/lib/components/lead-source-select.js.map +1 -1
- package/src/lib/components/lead-status-chip.d.ts +9 -3
- package/src/lib/components/lead-status-chip.js +8 -4
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-status-options.d.ts +20 -0
- package/src/lib/components/lead-status-options.js +51 -0
- package/src/lib/components/lead-status-options.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
- package/src/lib/components/lead-unqualify-dialog.js +50 -18
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.d.ts +0 -3
- package/src/lib/components/leads-bulk-bar.js +29 -29
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +120 -68
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.d.ts +4 -0
- package/src/lib/components/linked-deals-card.js +47 -3
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.js +43 -3
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +8 -0
- package/src/lib/components/new-contact-drawer.js +108 -6
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/new-lead-drawer.d.ts +9 -2
- package/src/lib/components/new-lead-drawer.js +88 -121
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/picklist-select.d.ts +36 -0
- package/src/lib/components/picklist-select.js +114 -0
- package/src/lib/components/picklist-select.js.map +1 -0
- package/src/lib/components/picklist-values-card.d.ts +34 -0
- package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
- package/src/lib/components/picklist-values-card.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
- package/src/lib/components/pipeline-stages-dialog.js +26 -4
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/pipelines-dialog.js +15 -6
- package/src/lib/components/pipelines-dialog.js.map +1 -1
- package/src/lib/components/recent-activity-feed.js +20 -5
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/recipes-card.js +4 -2
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-tasks-card.js +15 -8
- package/src/lib/components/record-tasks-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.d.ts +6 -0
- package/src/lib/components/reports/forecast-card.js +101 -3
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
- package/src/lib/components/reports/lead-funnel-card.js +9 -5
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.d.ts +23 -0
- package/src/lib/components/reports/lead-source-card.js +318 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -0
- package/src/lib/components/reports/report-export.js +1 -1
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports-section.js +8 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
- package/src/lib/components/sharing-rule-drawer.js +108 -19
- package/src/lib/components/sharing-rule-drawer.js.map +1 -1
- package/src/lib/components/sharing-rules-card.js +11 -3
- package/src/lib/components/sharing-rules-card.js.map +1 -1
- package/src/lib/components/task-cells.d.ts +9 -1
- package/src/lib/components/task-cells.js +9 -4
- package/src/lib/components/task-cells.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +111 -43
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
- package/src/lib/components/tasks-bulk-bar.js +7 -20
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
- package/src/lib/components/tasks-calendar.d.ts +3 -0
- package/src/lib/components/tasks-calendar.js +8 -1
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.d.ts +3 -3
- package/src/lib/components/tasks-section.js +58 -43
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +2 -9
- package/src/lib/constants/api-routes.js +1 -7
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +11 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +14 -0
- package/src/lib/constants/company-filters.js +51 -1
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +2 -0
- package/src/lib/constants/contact-filters.js +18 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +22 -1
- package/src/lib/constants/deal-filters.js +67 -4
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +3 -0
- package/src/lib/declarations.console-server.js +35 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +35 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +11 -0
- package/src/lib/declarations.server.js +92 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-company-picklists.d.ts +15 -0
- package/src/lib/hooks/use-company-picklists.js +55 -0
- package/src/lib/hooks/use-company-picklists.js.map +1 -0
- package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
- package/src/lib/hooks/use-crm-picklist.js +53 -0
- package/src/lib/hooks/use-crm-picklist.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
- package/src/lib/hooks/use-crm-record-names.js +45 -19
- package/src/lib/hooks/use-crm-record-names.js.map +1 -1
- package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
- package/src/lib/hooks/use-crm-task-picklists.js +45 -0
- package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +14 -0
- package/src/lib/hooks/use-deals.js +29 -0
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
- package/src/lib/hooks/use-lead-picklists.js +46 -0
- package/src/lib/hooks/use-lead-picklists.js.map +1 -0
- package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
- package/src/lib/hooks/use-lead-source-picklist.js +12 -22
- package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
- package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
- package/src/lib/hooks/use-lead-status-picklist.js +40 -0
- package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
- package/src/lib/model/companies.d.ts +32 -3
- package/src/lib/model/companies.js +133 -16
- package/src/lib/model/companies.js.map +1 -1
- package/src/lib/model/contact-merge.d.ts +27 -3
- package/src/lib/model/contact-merge.js +81 -7
- package/src/lib/model/contact-merge.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +17 -0
- package/src/lib/model/contact-record.js +15 -1
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +31 -2
- package/src/lib/model/contact-update.js +14 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +5 -0
- package/src/lib/model/crm-list-query.js +8 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +244 -0
- package/src/lib/model/crm-recipes.js.map +1 -0
- package/src/lib/model/crm-record-counts.d.ts +36 -0
- package/src/lib/model/crm-record-counts.js +54 -0
- package/src/lib/model/crm-record-counts.js.map +1 -0
- package/src/lib/model/crm-record-lists.d.ts +23 -0
- package/src/lib/model/crm-record-lists.js +161 -0
- package/src/lib/model/crm-record-lists.js.map +1 -0
- package/src/lib/model/crm-reports.d.ts +73 -3
- package/src/lib/model/crm-reports.js +120 -1
- package/src/lib/model/crm-reports.js.map +1 -1
- package/src/lib/model/crm-sharing.d.ts +21 -3
- package/src/lib/model/crm-sharing.js +27 -6
- package/src/lib/model/crm-sharing.js.map +1 -1
- package/src/lib/model/deal-board-model.d.ts +8 -2
- package/src/lib/model/deal-board-model.js +15 -2
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +45 -0
- package/src/lib/model/deal-contact-roles.js +73 -0
- package/src/lib/model/deal-contact-roles.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +56 -0
- package/src/lib/model/deal-form-model.js +79 -7
- package/src/lib/model/deal-form-model.js.map +1 -1
- package/src/lib/model/download-text-file.d.ts +23 -0
- package/src/lib/model/download-text-file.js +34 -0
- package/src/lib/model/download-text-file.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +36 -2
- package/src/lib/model/lead-filters.js +123 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +38 -0
- package/src/lib/model/lead-source-direction.js +52 -0
- package/src/lib/model/lead-source-direction.js.map +1 -0
- package/src/lib/model/picklist-values.d.ts +119 -0
- package/src/lib/model/picklist-values.js +256 -0
- package/src/lib/model/picklist-values.js.map +1 -0
- package/src/lib/model/record-facts-disclosed.d.ts +81 -0
- package/src/lib/model/record-facts-disclosed.js +281 -0
- package/src/lib/model/record-facts-disclosed.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +215 -34
- package/src/lib/model/record-facts.js +307 -128
- package/src/lib/model/record-facts.js.map +1 -1
- package/src/lib/model/task-picklists.d.ts +52 -0
- package/src/lib/model/task-picklists.js +83 -0
- package/src/lib/model/task-picklists.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +13 -0
- package/src/lib/model/task-routes.js +23 -4
- package/src/lib/model/task-routes.js.map +1 -1
- package/src/lib/plan-entitlements.js +6 -4
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts.js +220 -14
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +14 -1
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-companies.js +123 -14
- package/src/lib/server/api-v1/crm-companies.js.map +1 -1
- package/src/lib/server/api-v1/crm-deals.js +291 -26
- package/src/lib/server/api-v1/crm-deals.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +144 -38
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-tasks.js +75 -33
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +0 -11
- package/src/lib/server/api-v1/openapi.js +171 -16
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/assign-contact-owner.d.ts +162 -0
- package/src/lib/server/assign-contact-owner.js +340 -0
- package/src/lib/server/assign-contact-owner.js.map +1 -0
- package/src/lib/server/associate-company-by-domain.d.ts +70 -0
- package/src/lib/server/associate-company-by-domain.js +153 -0
- package/src/lib/server/associate-company-by-domain.js.map +1 -0
- package/src/lib/server/automation-steps.d.ts +139 -0
- package/src/lib/server/automation-steps.js +472 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.js +65 -21
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/capture-host-contact.d.ts +104 -0
- package/src/lib/server/capture-host-contact.js +151 -0
- package/src/lib/server/capture-host-contact.js.map +1 -0
- package/src/lib/server/company-children.d.ts +25 -0
- package/src/lib/server/company-children.js +42 -0
- package/src/lib/server/company-children.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +18 -0
- package/src/lib/server/company-delete.js +44 -21
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/contact-email-history.js +21 -19
- package/src/lib/server/contact-email-history.js.map +1 -1
- package/src/lib/server/contact-remove.js +27 -2
- package/src/lib/server/contact-remove.js.map +1 -1
- package/src/lib/server/contact-reports-to.d.ts +61 -0
- package/src/lib/server/contact-reports-to.js +114 -0
- package/src/lib/server/contact-reports-to.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +42 -1
- package/src/lib/server/contact-update.js +204 -23
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +46 -1
- package/src/lib/server/convert-host-lead.js +173 -15
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +12 -5
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-record-indexes.d.ts +45 -0
- package/src/lib/server/crm-record-indexes.js +78 -0
- package/src/lib/server/crm-record-indexes.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +9 -1
- package/src/lib/server/crm-sharing.js +23 -3
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/deal-contact-roles.d.ts +27 -0
- package/src/lib/server/deal-contact-roles.js +89 -0
- package/src/lib/server/deal-contact-roles.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +5 -0
- package/src/lib/server/lead-convert.js +19 -3
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +17 -2
- package/src/lib/server/lead-create.js +46 -14
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +6 -0
- package/src/lib/server/lead-nurturing.js +57 -20
- package/src/lib/server/lead-nurturing.js.map +1 -1
- package/src/lib/server/lead-picklists.d.ts +35 -0
- package/src/lib/server/lead-picklists.js +52 -0
- package/src/lib/server/lead-picklists.js.map +1 -0
- package/src/lib/server/lead-source-picklist.d.ts +1 -13
- package/src/lib/server/lead-source-picklist.js +6 -33
- package/src/lib/server/lead-source-picklist.js.map +1 -1
- package/src/lib/server/merge-contacts.js +22 -4
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +53 -0
- package/src/lib/server/person-eraser.js +149 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/person-records.d.ts +69 -0
- package/src/lib/server/person-records.js +317 -0
- package/src/lib/server/person-records.js.map +1 -0
- package/src/lib/server/person-refund.d.ts +97 -0
- package/src/lib/server/person-refund.js +221 -0
- package/src/lib/server/person-refund.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +21 -0
- package/src/lib/server/picklist-values.js +308 -0
- package/src/lib/server/picklist-values.js.map +1 -0
- package/src/lib/server/read-picklist.d.ts +44 -0
- package/src/lib/server/read-picklist.js +65 -0
- package/src/lib/server/read-picklist.js.map +1 -0
- package/src/lib/server/recipe-routes.js +15 -7
- package/src/lib/server/recipe-routes.js.map +1 -1
- package/src/lib/server/record-email-state.js +7 -1
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.d.ts +3 -0
- package/src/lib/server/record-facts.js +233 -31
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-origin.d.ts +27 -0
- package/src/lib/server/record-origin.js +104 -0
- package/src/lib/server/record-origin.js.map +1 -0
- package/src/lib/server/record-timeline.js +15 -2
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/suite-gate.js +4 -3
- package/src/lib/server/suite-gate.js.map +1 -1
- package/src/lib/server/task-routes.js +30 -16
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/plan-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Types only: the manifest generator loads this module on its own to compile\n// the plan figures, so nothing here may reach a core module at runtime.\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { PluginPlanEntitlementsDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-plan-entitlements'\n\n/**\n * THE CRM'S PLAN BAND (AGL-3080): the one-to-one email pace each plan\n * includes. Compiled into core's `PLAN_ENTITLEMENTS` by the manifest\n * generator (`register.planEntitlements`); this is where the figure is\n * written, beside the reasoning that sized it.\n */\ndeclare module '@aglyn/aglyn/plugin-manager/plugin-entitlement-keys' {\n interface PluginEntitlementQuotas {\n /**\n * One-to-one emails a workspace may send from CRM records per UTC day —\n * the message a rep writes to one person from their contact page, as\n * against a campaign (`emailSendsPerMonth`) or transactional mail.\n *\n * A HARD cap on every tier and a daily one, because it is the only send\n * class a person can produce by hand at volume: a campaign is one act\n * over a metered audience, and a receipt follows an order somebody paid\n * for, but a rep with a template and a list can put a thousand messages\n * onto the platform's sending reputation in an afternoon. The day\n * boundary is what makes the cap a pace rather than a wall — tomorrow the\n * count is zero again — and the number is sized so that every tier holds\n * its margin with the whole day spent, which is the arithmetic the Drive\n * pricing decision of 2026-09-05 records. `checkCrmEmailQuota` reads it;\n * the counter it is enforced against is\n * `orgs/{orgId}/crmEmailUsage/{day}`.\n *\n * Every send still counts on the org's `emailSends` cost meter, like any\n * other message the provider charged for. 0 on Free, which has no CRM,\n * and finite on every paid tier, Enterprise's 2,000 included.\n *\n * ## This ladder and `emailSendsPerMonth` are NOT the same ladder\n *\n * They answer to different principles, deliberately, and a tier's two\n * numbers are not expected to look like each other. `emailSendsPerMonth`\n * is sized against SHARED-DOMAIN REPUTATION: campaign mail is bulk on a\n * pool every tier without its own verified sending domain rides under\n * `p=reject`, so what one workspace sends is a cost every other workspace\n * pays. This one is sized against COGS SHARE: one-to-one mail is a\n * per-tier margin question, which is why it is a daily pace rather than a\n * monthly band.\n *\n * The two are far apart at the entry tier — a band of 0 beside a real\n * daily allowance — and that reads as an inconsistency until the\n * principles are named. It is not one. One-to-one mail is addressed to a\n * single person, usually inside a relationship the recipient started, and\n * it reaches the provider through a per-user pace limit, both suppression\n * lists, a `declined` consent basis as a hard stop, and an atomic daily\n * reservation. Complaint rates on it are structurally unlike bulk.\n * Reconciling the two ladders would price a reply to one customer as\n * though it carried a campaign's reputation risk.\n */\n crmEmailsPerDay?: number\n }\n}\n\n/** The one-to-one emails each plan may send a UTC day — see the key's docblock. */\nexport const CRM_EMAILS_PER_DAY_BY_PLAN: Readonly<Record<OrgPlan, number>> = {\n // No one-to-one email on Free, which has no CRM: the send lives on a\n // record's page, and Free opens no record. Zero here is what\n // `checkCrmEmailQuota` refuses against, so a per-org grant of\n // `features.crm` alone still sends nothing until the band is raised with\n // it.\n free: 0,\n // One-to-one email from a CRM record, per UTC day (AGL-2611). A hard daily\n // pace rather than a monthly meter, sized so the whole day spent at $0.0009\n // a message leaves the tier's CRM axis inside the 20% cost share the Drive\n // pricing decision of 2026-09-05 sets.
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/plan-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Types only: the manifest generator loads this module on its own to compile\n// the plan figures, so nothing here may reach a core module at runtime.\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { PluginPlanEntitlementsDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-plan-entitlements'\n\n/**\n * THE CRM'S PLAN BAND (AGL-3080): the one-to-one email pace each plan\n * includes. Compiled into core's `PLAN_ENTITLEMENTS` by the manifest\n * generator (`register.planEntitlements`); this is where the figure is\n * written, beside the reasoning that sized it.\n */\ndeclare module '@aglyn/aglyn/plugin-manager/plugin-entitlement-keys' {\n interface PluginEntitlementQuotas {\n /**\n * One-to-one emails a workspace may send from CRM records per UTC day —\n * the message a rep writes to one person from their contact page, as\n * against a campaign (`emailSendsPerMonth`) or transactional mail.\n *\n * A HARD cap on every tier and a daily one, because it is the only send\n * class a person can produce by hand at volume: a campaign is one act\n * over a metered audience, and a receipt follows an order somebody paid\n * for, but a rep with a template and a list can put a thousand messages\n * onto the platform's sending reputation in an afternoon. The day\n * boundary is what makes the cap a pace rather than a wall — tomorrow the\n * count is zero again — and the number is sized so that every tier holds\n * its margin with the whole day spent, which is the arithmetic the Drive\n * pricing decision of 2026-09-05 records. `checkCrmEmailQuota` reads it;\n * the counter it is enforced against is\n * `orgs/{orgId}/crmEmailUsage/{day}`.\n *\n * Every send still counts on the org's `emailSends` cost meter, like any\n * other message the provider charged for. 0 on Free, which has no CRM,\n * and finite on every paid tier, Enterprise's 2,000 included.\n *\n * ## This ladder and `emailSendsPerMonth` are NOT the same ladder\n *\n * They answer to different principles, deliberately, and a tier's two\n * numbers are not expected to look like each other. `emailSendsPerMonth`\n * is sized against SHARED-DOMAIN REPUTATION: campaign mail is bulk on a\n * pool every tier without its own verified sending domain rides under\n * `p=reject`, so what one workspace sends is a cost every other workspace\n * pays. This one is sized against COGS SHARE: one-to-one mail is a\n * per-tier margin question, which is why it is a daily pace rather than a\n * monthly band.\n *\n * The two are far apart at the entry tier — a band of 0 beside a real\n * daily allowance — and that reads as an inconsistency until the\n * principles are named. It is not one. One-to-one mail is addressed to a\n * single person, usually inside a relationship the recipient started, and\n * it reaches the provider through a per-user pace limit, both suppression\n * lists, a `declined` consent basis as a hard stop, and an atomic daily\n * reservation. Complaint rates on it are structurally unlike bulk.\n * Reconciling the two ladders would price a reply to one customer as\n * though it carried a campaign's reputation risk.\n */\n crmEmailsPerDay?: number\n }\n}\n\n/** The one-to-one emails each plan may send a UTC day — see the key's docblock. */\nexport const CRM_EMAILS_PER_DAY_BY_PLAN: Readonly<Record<OrgPlan, number>> = {\n // No one-to-one email on Free, which has no CRM: the send lives on a\n // record's page, and Free opens no record. Zero here is what\n // `checkCrmEmailQuota` refuses against, so a per-org grant of\n // `features.crm` alone still sends nothing until the band is raised with\n // it.\n free: 0,\n // One-to-one email from a CRM record, per UTC day (AGL-2611). A hard daily\n // pace rather than a monthly meter, sized so the whole day spent at $0.0009\n // a message leaves the tier's CRM axis inside the 20% cost share the Drive\n // pricing decision of 2026-09-05 sets. 35 a day is $0.95 a month against a\n // $16 annual price — 50 was, until the whole Starter tier had to cover its\n // full-use cost plus 30% (AGL-3469). Every send still lands on the\n // `emailSends` cost meter beside the transactional mail this tier already\n // sends.\n starter: 35,\n pro: 150,\n business: 200,\n scale: 300,\n advanced: 500,\n agency: 1000,\n // Finite like the campaign allowance beside it, and for the same reason\n // one-to-one mail is capped on every other tier: a rep with a template and\n // a list can put thousands of messages onto the platform's sending\n // reputation in an afternoon. A negotiated agreement whose reps need more\n // than 2,000 a day says so, and the override is what carries it.\n enterprise: 2_000,\n}\n\n/** The CRM's plan figures, for the manifest generator. */\nexport function crmPlanEntitlements(): PluginPlanEntitlementsDeclaration {\n return {\n quotas: [\n {\n key: 'crmEmailsPerDay',\n label: 'One-to-one emails / day',\n byPlan: CRM_EMAILS_PER_DAY_BY_PLAN,\n },\n ],\n }\n}\n"],"names":["CRM_EMAILS_PER_DAY_BY_PLAN","free","starter","pro","business","scale","advanced","agency","enterprise","crmPlanEntitlements","quotas","key","label","byPlan"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,6EAA6E;AAC7E,wEAAwE;AA0DxE,iFAAiF,GACjF,OAAO,MAAMA,6BAAgE;IAC3E,qEAAqE;IACrE,6DAA6D;IAC7D,8DAA8D;IAC9D,yEAAyE;IACzE,MAAM;IACNC,MAAM;IACN,2EAA2E;IAC3E,4EAA4E;IAC5E,2EAA2E;IAC3E,2EAA2E;IAC3E,2EAA2E;IAC3E,mEAAmE;IACnE,0EAA0E;IAC1E,SAAS;IACTC,SAAS;IACTC,KAAK;IACLC,UAAU;IACVC,OAAO;IACPC,UAAU;IACVC,QAAQ;IACR,wEAAwE;IACxE,2EAA2E;IAC3E,mEAAmE;IACnE,0EAA0E;IAC1E,iEAAiE;IACjEC,YAAY;AACd,EAAC;AAED,wDAAwD,GACxD,OAAO,SAASC;IACd,OAAO;QACLC,QAAQ;YACN;gBACEC,KAAK;gBACLC,OAAO;gBACPC,QAAQb;YACV;SACD;IACH;AACF"}
|
package/src/lib/plugin.js
CHANGED
|
@@ -19,10 +19,14 @@ import { lazy } from "react";
|
|
|
19
19
|
import { CRM_CONSOLE_SECTIONS } from "./components/crm-console-sections.js";
|
|
20
20
|
import { CrmGlanceCard } from "./components/crm-glance-card.js";
|
|
21
21
|
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
22
|
+
import { registerCrmTransferResourceUis } from "./transfer/ui.js";
|
|
22
23
|
import { withCrmOrgMount } from "./hooks/use-crm-org-mount.js";
|
|
24
|
+
import { registerCrmRecordCounts } from "./model/crm-record-counts.js";
|
|
23
25
|
import { registerCrmRecordRoutes } from "./model/crm-record-routes.js";
|
|
26
|
+
import { registerCrmRecordLists } from "./model/crm-record-lists.js";
|
|
24
27
|
import { CRM_SEARCH_SOURCES } from "./model/crm-search-sources.js";
|
|
25
28
|
import { registerPluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
|
|
29
|
+
import { registerPluginTransferResourceUi } from "@aglyn/aglyn/plugin-manager/plugin-transfer-resources";
|
|
26
30
|
import { CRM_RECORD_ATTRIBUTION_ZONE } from "./components/crm-attribution-zone.js";
|
|
27
31
|
import { CRM_RECORD_BOOKING_ZONE } from "./components/crm-booking-zone.js";
|
|
28
32
|
/** Code-split: the CRM hub only loads when opened. */ const CrmConsolePage = lazy(()=>import("./components/crm-console-page.js"));
|
|
@@ -43,7 +47,20 @@ const CrmTasksDueCard = lazy(()=>import("./components/crm-tasks-due-card.js"));
|
|
|
43
47
|
* once that backfill reported nothing left (AGL-2614). The `/contacts`
|
|
44
48
|
* address is a URL, not a stored id, and the nav item keeps redirecting it.
|
|
45
49
|
*/ export function registerCrmConsole() {
|
|
50
|
+
// How the Import & export hub names email templates in a workspace package (AGL-3535).
|
|
51
|
+
registerPluginTransferResourceUi('crm.email-templates', {
|
|
52
|
+
label: 'Email templates'
|
|
53
|
+
}, {
|
|
54
|
+
pluginId: BUNDLE_ID
|
|
55
|
+
});
|
|
46
56
|
registerCrmRecordRoutes();
|
|
57
|
+
// How the import wizard and the export dialog name the CRM's records (AGL-3527).
|
|
58
|
+
registerCrmTransferResourceUis(BUNDLE_ID);
|
|
59
|
+
// The CRM's records as another plugin's picker lists them (AGL-3080).
|
|
60
|
+
registerCrmRecordLists();
|
|
61
|
+
// The leads a site captured and the organization's contacts, counted for
|
|
62
|
+
// another plugin's figure.
|
|
63
|
+
registerCrmRecordCounts();
|
|
47
64
|
registerPluginZone({
|
|
48
65
|
zone: CRM_RECORD_BOOKING_ZONE,
|
|
49
66
|
label: 'Book a meeting from a CRM record',
|
package/src/lib/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiCardAccountDetailsOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { CRM_CONSOLE_SECTIONS } from './components/crm-console-sections'\nimport { CrmGlanceCard } from './components/crm-glance-card'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { withCrmOrgMount } from './hooks/use-crm-org-mount'\nimport { registerCrmRecordRoutes } from './model/crm-record-routes'\nimport { CRM_SEARCH_SOURCES } from './model/crm-search-sources'\nimport { registerPluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { CRM_RECORD_ATTRIBUTION_ZONE } from './components/crm-attribution-zone'\nimport { CRM_RECORD_BOOKING_ZONE } from './components/crm-booking-zone'\n\n/** Code-split: the CRM hub only loads when opened. */\nconst CrmConsolePage = lazy(\n () => import('./components/crm-console-page'),\n)\n/** The dashboard glance, split the same way: it loads where the slot mounts it. */\n/** Where a form's fields save on a contact, drawn on the form's own page. */\nconst FormContactFieldsCard = lazy(\n () => import('./components/form-contact-fields-card'),\n)\n\nconst CrmTasksDueCard = lazy(\n () => import('./components/crm-tasks-due-card'),\n)\n\n/**\n * CRM feature plugin (AGL-395, the hub since AGL-2595). Console-only — its\n * records live in Firestore and have no canvas element, so there is no UI\n * bundle. The console half declares the CRM nav + hub through the\n * ConsoleExtension registry (release_crm gate via the nav tab); the\n * contacts section reads the `contactsPerHost` quota off the shell-passed\n * `org`.\n *\n * The plugin id is `crm` (AGL-2595). It was `contacts` while the surface was\n * one list; the id is persisted in every org's `enabledPlugins` and every\n * host's `disabledPlugins`, so the runtime read the old value as this one\n * until the stored documents had been rewritten, and the alias was retired\n * once that backfill reported nothing left (AGL-2614). The `/contacts`\n * address is a URL, not a stored id, and the nav item keeps redirecting it.\n */\nexport function registerCrmConsole(): void {\n registerCrmRecordRoutes()\n registerPluginZone(\n {\n zone: CRM_RECORD_BOOKING_ZONE,\n label: 'Book a meeting from a CRM record',\n surface: 'console',\n // One control in a record header's row of actions, and one beside the\n // composer's fields: the page lays them out, so the zone adds nothing.\n layout: 'bare',\n description:\n 'In a record’s header and beside the one-to-one composer. A widget here offers the site’s bookable services and the link a visitor books one at; it is handed the site, the record and, from the composer, a way to insert the chosen link. It writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: CRM_RECORD_ATTRIBUTION_ZONE,\n label: 'Where a CRM record came from',\n surface: 'console',\n description:\n 'On a contact’s page and in a lead’s history. A widget here says which campaign or link brought the person; it is handed the site, what the record is and its id, and it writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'CRM',\n /*\n * WHO may open the CRM, declared so the shell enforces it.\n *\n * `navTabId` below is a release flag, not authorization: it says whether\n * the surface has shipped, and `FeatureGate` reads it as\n * `released || isStaff`. Nothing above this page decided who among a\n * workspace's members may read it, and the page is a full editor over\n * org-shared people data — names, addresses, order history, notes,\n * consent, and a CSV export of all of it.\n *\n * `data.manage` rather than a key of this plugin's own, because it is\n * the catalog key whose subject is exactly this data. The Firestore\n * rules place contacts in the org-shared data block with datasets and\n * gate writes on `canWriteOrgData()` — owner, admin, editor — which is\n * the population `data.manage` defaults to, so the console gate and the\n * rules agree on the built-in roles by construction. A custom role or a\n * per-member override then narrows the console further, and the rules\n * stay the boundary for the data itself.\n *\n * Minting a `contacts.*` key instead would advertise a control that does\n * not exist. Console contact writes are client-direct against the rules,\n * so a new key would have no server enforcement point to sit on, and a\n * permission a customer can untick that changes nothing is worse than\n * its absence.\n *\n * A SITE COLLABORATOR holding it reaches this page deliberately. They\n * are a real org member document, so this gate is the first thing that\n * has ever asked about them here — and the answer is yes, scoped: the\n * listener filters on `visibleTo`, the rules prove the same predicate\n * per document, and every field the page shows is read through the\n * viewing group's own facet. Refusing them outright would delete a\n * shipped capability rather than close a hole.\n */\n permission: 'data.manage',\n /*\n * WHAT the workspace must have bought (AGL-2851). The CRM is included\n * from Starter and a Free workspace has none of it, so the flag is\n * declared once, here, and the shell refuses every surface this\n * registers on a plan without it: the hub body at both mounts, every\n * section and record beneath it, drawn locked beside the upgrade notice,\n * and the dashboard cards. The nav tab stays, as every plan-locked tab\n * does, because it is the way to that notice. The CRM's routes, `/v1`\n * and the Firestore rules ask the same question where the data is\n * (`server/suite-gate.ts`).\n */\n featureFlag: 'crm',\n // What the console's search finds here, under the two gates above.\n searchSources: CRM_SEARCH_SOURCES,\n /*\n * Dashboard cards (AGL-2599, AGL-2604): the site dashboard's `hostDashboard`\n * slot composes this extension's permission over each, so a card appears\n * only where the CRM is enabled and only for a reader who may open it —\n * registered here rather than imported by the dashboard so the shell's\n * enablement and entitlement gate decides whether a site that never turned\n * the CRM on sees them, the rule every card on that row follows. The tasks\n * card is a glance at the reader's own overdue and due-today work and\n * renders nothing on a workspace that has never made a task; the glance\n * card is four server-counted figures, each a link into the hub.\n *\n * Both name `features.crm`, as the extension does (AGL-2611, AGL-2851):\n * the slot composes the two, so a plan without the CRM gets neither card\n * — absent, not upsold, the treatment every gated card gets.\n *\n * The same two cards again on the organization's `orgDashboard` slot\n * (AGL-2636) — the org's sites page — under the same flag, with the same\n * ids: the id names the card, and it is one card totaling the whole\n * organization there instead of one site. The shell hands that slot the\n * org mount as a prop, and `withCrmOrgMount` is what puts the mount's\n * provider around each card, since the sites page cannot import one.\n */\n widgets: [\n // Which contact field each of a form's fields saves to, on the form's\n // own page. The forms plugin hosts the zone and writes the form\n // document; what a contact's fields ARE is this plugin's, so the card\n // is too.\n {\n slot: 'formContactFields',\n widgetId: 'crm-form-contact-fields',\n title: 'Saves to contact fields',\n // A contact's custom fields are part of the suite, so a workspace\n // without it has none to map a form's field onto.\n featureFlag: 'crm',\n Component: FormContactFieldsCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: CrmTasksDueCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: CrmGlanceCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmTasksDueCard),\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmGlanceCard),\n },\n ],\n navItems: [\n {\n label: 'CRM',\n href: '/crm',\n // The address the surface had while it was one list. A link kept\n // from then — a bookmark, a docs page, an email — still opens the\n // hub rather than the shell's \"not available\" notice.\n legacyHrefs: ['/contacts'],\n // Sections as ROUTES (AGL-2595): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read and a\n // bare `/crm` lands on the first. Every section inherits this\n // item's `release_crm` gate.\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n },\n Component: CrmConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's CRM hub (AGL-2630), at `/[orgSlug]/crm`: the same\n * sections over every site at once, mounted with no site and the org's\n * sites as `orgMount`. Its listeners carry no scope clause, so the\n * shell's org route refuses a site collaborator on REACH before it asks\n * the `data.manage` declared above; a permission alone, which a\n * collaborator's role can carry, never opens it.\n *\n * It carries the SITE tab's id on purpose: `release_contacts` names\n * `nav-tab-contacts`, so one flag holds both halves of the CRM, and the\n * org half cannot ship while the site half is switched off. First among\n * the organization's plugin tabs, beside Data, the org's other shared\n * collection of records. The help opens the docs heading about this\n * mount. Its old address, `/[orgSlug]/contacts`, is a console redirect\n * (`consoleRedirects`), so it needs no `legacyHrefs` here.\n */\n orgNavItems: [\n {\n label: 'CRM',\n href: '/crm',\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n tabOrder: -100,\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n docsAnchor: '#at-the-organization-level',\n },\n Component: CrmConsolePage,\n },\n ],\n })\n}\n"],"names":["Aglyn","mdiCardAccountDetailsOutline","lazy","CRM_CONSOLE_SECTIONS","CrmGlanceCard","BUNDLE_ID","withCrmOrgMount","registerCrmRecordRoutes","CRM_SEARCH_SOURCES","registerPluginZone","CRM_RECORD_ATTRIBUTION_ZONE","CRM_RECORD_BOOKING_ZONE","CrmConsolePage","FormContactFieldsCard","CrmTasksDueCard","registerCrmConsole","zone","label","surface","layout","description","pluginId","registerConsoleExtension","displayName","permission","featureFlag","searchSources","widgets","slot","widgetId","title","Component","CONSOLE_WIDGET_SLOTS","hostDashboard","orgDashboard","navItems","href","legacyHrefs","sections","navTabId","icon","path","header","docsTopic","orgNavItems","tabOrder","docsAnchor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiCardAccountDetailsOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { CRM_CONSOLE_SECTIONS } from './components/crm-console-sections'\nimport { CrmGlanceCard } from './components/crm-glance-card'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerCrmTransferResourceUis } from './transfer/ui'\nimport { withCrmOrgMount } from './hooks/use-crm-org-mount'\nimport { registerCrmRecordCounts } from './model/crm-record-counts'\nimport { registerCrmRecordRoutes } from './model/crm-record-routes'\nimport { registerCrmRecordLists } from './model/crm-record-lists'\nimport { CRM_SEARCH_SOURCES } from './model/crm-search-sources'\nimport { registerPluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { registerPluginTransferResourceUi } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { CRM_RECORD_ATTRIBUTION_ZONE } from './components/crm-attribution-zone'\nimport { CRM_RECORD_BOOKING_ZONE } from './components/crm-booking-zone'\n\n/** Code-split: the CRM hub only loads when opened. */\nconst CrmConsolePage = lazy(\n () => import('./components/crm-console-page'),\n)\n/** The dashboard glance, split the same way: it loads where the slot mounts it. */\n/** Where a form's fields save on a contact, drawn on the form's own page. */\nconst FormContactFieldsCard = lazy(\n () => import('./components/form-contact-fields-card'),\n)\n\nconst CrmTasksDueCard = lazy(\n () => import('./components/crm-tasks-due-card'),\n)\n\n/**\n * CRM feature plugin (AGL-395, the hub since AGL-2595). Console-only — its\n * records live in Firestore and have no canvas element, so there is no UI\n * bundle. The console half declares the CRM nav + hub through the\n * ConsoleExtension registry (release_crm gate via the nav tab); the\n * contacts section reads the `contactsPerHost` quota off the shell-passed\n * `org`.\n *\n * The plugin id is `crm` (AGL-2595). It was `contacts` while the surface was\n * one list; the id is persisted in every org's `enabledPlugins` and every\n * host's `disabledPlugins`, so the runtime read the old value as this one\n * until the stored documents had been rewritten, and the alias was retired\n * once that backfill reported nothing left (AGL-2614). The `/contacts`\n * address is a URL, not a stored id, and the nav item keeps redirecting it.\n */\nexport function registerCrmConsole(): void {\n // How the Import & export hub names email templates in a workspace package (AGL-3535).\n registerPluginTransferResourceUi('crm.email-templates', { label: 'Email templates' }, { pluginId: BUNDLE_ID })\n registerCrmRecordRoutes()\n // How the import wizard and the export dialog name the CRM's records (AGL-3527).\n registerCrmTransferResourceUis(BUNDLE_ID)\n // The CRM's records as another plugin's picker lists them (AGL-3080).\n registerCrmRecordLists()\n // The leads a site captured and the organization's contacts, counted for\n // another plugin's figure.\n registerCrmRecordCounts()\n registerPluginZone(\n {\n zone: CRM_RECORD_BOOKING_ZONE,\n label: 'Book a meeting from a CRM record',\n surface: 'console',\n // One control in a record header's row of actions, and one beside the\n // composer's fields: the page lays them out, so the zone adds nothing.\n layout: 'bare',\n description:\n 'In a record’s header and beside the one-to-one composer. A widget here offers the site’s bookable services and the link a visitor books one at; it is handed the site, the record and, from the composer, a way to insert the chosen link. It writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: CRM_RECORD_ATTRIBUTION_ZONE,\n label: 'Where a CRM record came from',\n surface: 'console',\n description:\n 'On a contact’s page and in a lead’s history. A widget here says which campaign or link brought the person; it is handed the site, what the record is and its id, and it writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'CRM',\n /*\n * WHO may open the CRM, declared so the shell enforces it.\n *\n * `navTabId` below is a release flag, not authorization: it says whether\n * the surface has shipped, and `FeatureGate` reads it as\n * `released || isStaff`. Nothing above this page decided who among a\n * workspace's members may read it, and the page is a full editor over\n * org-shared people data — names, addresses, order history, notes,\n * consent, and a CSV export of all of it.\n *\n * `data.manage` rather than a key of this plugin's own, because it is\n * the catalog key whose subject is exactly this data. The Firestore\n * rules place contacts in the org-shared data block with datasets and\n * gate writes on `canWriteOrgData()` — owner, admin, editor — which is\n * the population `data.manage` defaults to, so the console gate and the\n * rules agree on the built-in roles by construction. A custom role or a\n * per-member override then narrows the console further, and the rules\n * stay the boundary for the data itself.\n *\n * Minting a `contacts.*` key instead would advertise a control that does\n * not exist. Console contact writes are client-direct against the rules,\n * so a new key would have no server enforcement point to sit on, and a\n * permission a customer can untick that changes nothing is worse than\n * its absence.\n *\n * A SITE COLLABORATOR holding it reaches this page deliberately. They\n * are a real org member document, so this gate is the first thing that\n * has ever asked about them here — and the answer is yes, scoped: the\n * listener filters on `visibleTo`, the rules prove the same predicate\n * per document, and every field the page shows is read through the\n * viewing group's own facet. Refusing them outright would delete a\n * shipped capability rather than close a hole.\n */\n permission: 'data.manage',\n /*\n * WHAT the workspace must have bought (AGL-2851). The CRM is included\n * from Starter and a Free workspace has none of it, so the flag is\n * declared once, here, and the shell refuses every surface this\n * registers on a plan without it: the hub body at both mounts, every\n * section and record beneath it, drawn locked beside the upgrade notice,\n * and the dashboard cards. The nav tab stays, as every plan-locked tab\n * does, because it is the way to that notice. The CRM's routes, `/v1`\n * and the Firestore rules ask the same question where the data is\n * (`server/suite-gate.ts`).\n */\n featureFlag: 'crm',\n // What the console's search finds here, under the two gates above.\n searchSources: CRM_SEARCH_SOURCES,\n /*\n * Dashboard cards (AGL-2599, AGL-2604): the site dashboard's `hostDashboard`\n * slot composes this extension's permission over each, so a card appears\n * only where the CRM is enabled and only for a reader who may open it —\n * registered here rather than imported by the dashboard so the shell's\n * enablement and entitlement gate decides whether a site that never turned\n * the CRM on sees them, the rule every card on that row follows. The tasks\n * card is a glance at the reader's own overdue and due-today work and\n * renders nothing on a workspace that has never made a task; the glance\n * card is four server-counted figures, each a link into the hub.\n *\n * Both name `features.crm`, as the extension does (AGL-2611, AGL-2851):\n * the slot composes the two, so a plan without the CRM gets neither card\n * — absent, not upsold, the treatment every gated card gets.\n *\n * The same two cards again on the organization's `orgDashboard` slot\n * (AGL-2636) — the org's sites page — under the same flag, with the same\n * ids: the id names the card, and it is one card totaling the whole\n * organization there instead of one site. The shell hands that slot the\n * org mount as a prop, and `withCrmOrgMount` is what puts the mount's\n * provider around each card, since the sites page cannot import one.\n */\n widgets: [\n // Which contact field each of a form's fields saves to, on the form's\n // own page. The forms plugin hosts the zone and writes the form\n // document; what a contact's fields ARE is this plugin's, so the card\n // is too.\n {\n slot: 'formContactFields',\n widgetId: 'crm-form-contact-fields',\n title: 'Saves to contact fields',\n // A contact's custom fields are part of the suite, so a workspace\n // without it has none to map a form's field onto.\n featureFlag: 'crm',\n Component: FormContactFieldsCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: CrmTasksDueCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: CrmGlanceCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmTasksDueCard),\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmGlanceCard),\n },\n ],\n navItems: [\n {\n label: 'CRM',\n href: '/crm',\n // The address the surface had while it was one list. A link kept\n // from then — a bookmark, a docs page, an email — still opens the\n // hub rather than the shell's \"not available\" notice.\n legacyHrefs: ['/contacts'],\n // Sections as ROUTES (AGL-2595): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read and a\n // bare `/crm` lands on the first. Every section inherits this\n // item's `release_crm` gate.\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n },\n Component: CrmConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's CRM hub (AGL-2630), at `/[orgSlug]/crm`: the same\n * sections over every site at once, mounted with no site and the org's\n * sites as `orgMount`. Its listeners carry no scope clause, so the\n * shell's org route refuses a site collaborator on REACH before it asks\n * the `data.manage` declared above; a permission alone, which a\n * collaborator's role can carry, never opens it.\n *\n * It carries the SITE tab's id on purpose: `release_contacts` names\n * `nav-tab-contacts`, so one flag holds both halves of the CRM, and the\n * org half cannot ship while the site half is switched off. First among\n * the organization's plugin tabs, beside Data, the org's other shared\n * collection of records. The help opens the docs heading about this\n * mount. Its old address, `/[orgSlug]/contacts`, is a console redirect\n * (`consoleRedirects`), so it needs no `legacyHrefs` here.\n */\n orgNavItems: [\n {\n label: 'CRM',\n href: '/crm',\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n tabOrder: -100,\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n docsAnchor: '#at-the-organization-level',\n },\n Component: CrmConsolePage,\n },\n ],\n })\n}\n"],"names":["Aglyn","mdiCardAccountDetailsOutline","lazy","CRM_CONSOLE_SECTIONS","CrmGlanceCard","BUNDLE_ID","registerCrmTransferResourceUis","withCrmOrgMount","registerCrmRecordCounts","registerCrmRecordRoutes","registerCrmRecordLists","CRM_SEARCH_SOURCES","registerPluginZone","registerPluginTransferResourceUi","CRM_RECORD_ATTRIBUTION_ZONE","CRM_RECORD_BOOKING_ZONE","CrmConsolePage","FormContactFieldsCard","CrmTasksDueCard","registerCrmConsole","label","pluginId","zone","surface","layout","description","registerConsoleExtension","displayName","permission","featureFlag","searchSources","widgets","slot","widgetId","title","Component","CONSOLE_WIDGET_SLOTS","hostDashboard","orgDashboard","navItems","href","legacyHrefs","sections","navTabId","icon","path","header","docsTopic","orgNavItems","tabOrder","docsAnchor"],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { nameSearchFields } from "@aglyn/aglyn/app-utils/name-search";
|
|
18
|
-
import { CAPTURED_BY_HOST_FIELD, CONTACT_COMPANY_IDS_FIELD, CONTACT_FACETS_FIELD, CONTACT_FIELDS_MAX_PER_ORG, CONTACT_LIFECYCLE_STAGES, consentGroupForHost, contactFacetPath, createResourceUid, CRM_COLLECTIONS, CRM_MEDIA_IDS_MAX, isContactLifecycleStage, MARKETING_CONSENT_BY_HOST_FIELD, marketingConsentFieldsForGroup, marketingConsentHostIds, normalizeAddress, normalizeContactEmail, normalizeCrmMediaIds, normalizePhone, ORG_SCOPE_TOKEN, planContactCompanyLink, readContactCompanyLink, readContactCustomInput, readContactFacet, soloConsentGroup } from "@aglyn/aglyn/server";
|
|
18
|
+
import { CAPTURED_BY_HOST_FIELD, composeContactName, CONTACT_BIRTHDATE_REFUSAL, CONTACT_COMPANY_IDS_FIELD, CONTACT_EXTRA_PHONE_FIELDS, CONTACT_FACETS_FIELD, CONTACT_FIELDS_MAX_PER_ORG, CONTACT_LIFECYCLE_STAGES, CONTACT_NAME_PART_MAX, consentGroupForHost, contactFacetPath, createResourceUid, CRM_COLLECTIONS, CRM_LEAD_SOURCE_PICKLIST, CRM_MEDIA_IDS_MAX, CRM_SALUTATION_PICKLIST, isContactLifecycleStage, judgeCrmPicklistValue, MARKETING_CONSENT_BY_HOST_FIELD, marketingConsentFieldsForGroup, marketingConsentHostIds, normalizeAddress, normalizeContactBirthdate, normalizeContactEmail, normalizeCrmMediaIds, normalizePhone, ORG_SCOPE_TOKEN, planContactCompanyLink, readContactCompanyLink, readContactCustomInput, readContactFacet, soloConsentGroup } from "@aglyn/aglyn/server";
|
|
19
19
|
import { ApiErrors, apiJson, contactCompanyMirrorValue, crmRecordsQuotaForOrg, listResponse, restampCrmListFieldsAt, settleCompanyContactsCounts } from "@aglyn/tenant-data-admin";
|
|
20
20
|
import { claimWrite, orgOwnsHost, paginate, readJsonBody, requireScope, serialize } from "@aglyn/tenant-data-admin/server/api-v1-kit";
|
|
21
21
|
// The leaf, so a spec that stands a partial barrel in still reaches it.
|
|
@@ -28,7 +28,11 @@ import { findContactByEmail } from "@aglyn/tenant-data-admin/server/contact-emai
|
|
|
28
28
|
import { deleteWholeContact, removeContactKeepingRefusals } from "@aglyn/tenant-data-admin/server/retained-refusals";
|
|
29
29
|
import { FieldPath, Timestamp } from "firebase-admin/firestore";
|
|
30
30
|
import { mergeContactRoute } from "./contacts-merge.js";
|
|
31
|
-
import {
|
|
31
|
+
import { sweepDealContactRoles } from "../deal-contact-roles.js";
|
|
32
|
+
import { cachedContactReader, clearReportsToOf, CONTACT_REPORTS_TO_LOOP_REFUSAL, CONTACT_REPORTS_TO_SELF_REFUSAL, contactReportsToLoops } from "../contact-reports-to.js";
|
|
33
|
+
import { readCrmPicklist } from "../read-picklist.js";
|
|
34
|
+
import { CRM_ID_MAX, CRM_LABEL_MAX, createPayload, crmRefErrors, memberError, readChoice, readOptionalText, readOrgLeadSourcePicklist, readRefId, updatePayload } from "./crm-shared.js";
|
|
35
|
+
import { resolveCrmPicklistWrite } from "../read-picklist.js";
|
|
32
36
|
/**
|
|
33
37
|
* `/v1/contacts` (AGL-618, AGL-2276, AGL-2606): the organization's people —
|
|
34
38
|
* list, read, create, update, delete, and `POST /v1/contacts/{id}/merge` —
|
|
@@ -76,10 +80,27 @@ import { CRM_ID_MAX, CRM_LABEL_MAX, createPayload, crmRefErrors, memberError, re
|
|
|
76
80
|
'address',
|
|
77
81
|
'ownerUid',
|
|
78
82
|
'lifecycleStage',
|
|
83
|
+
// The holder's Lead source, one of the org's picklist values (AGL-3511).
|
|
84
|
+
'leadSource',
|
|
79
85
|
// Files are a facet field like the rest (AGL-2662): an agency running two
|
|
80
86
|
// client brands has one contact document between them, and a contract one
|
|
81
87
|
// client filed is not the other client's to read.
|
|
82
|
-
'mediaIds'
|
|
88
|
+
'mediaIds',
|
|
89
|
+
// Salesforce's standard contact fields (AGL-3515), per holder like the rest.
|
|
90
|
+
'salutation',
|
|
91
|
+
'firstName',
|
|
92
|
+
'lastName',
|
|
93
|
+
'department',
|
|
94
|
+
'mobilePhone',
|
|
95
|
+
'homePhone',
|
|
96
|
+
'otherPhone',
|
|
97
|
+
'fax',
|
|
98
|
+
'birthdate',
|
|
99
|
+
'assistantName',
|
|
100
|
+
'assistantPhone',
|
|
101
|
+
'reportsToContactId',
|
|
102
|
+
'otherAddress',
|
|
103
|
+
'doNotCall'
|
|
83
104
|
];
|
|
84
105
|
/** The groups holding a facet on this row, in stable order. */ function contactFacetHolders(data) {
|
|
85
106
|
const facets = data[CONTACT_FACETS_FIELD];
|
|
@@ -93,9 +114,25 @@ function contactCrmProfile(data, groupId) {
|
|
|
93
114
|
address: null,
|
|
94
115
|
ownerUid: null,
|
|
95
116
|
lifecycleStage: null,
|
|
117
|
+
leadSource: null,
|
|
96
118
|
// An empty ARRAY rather than null, so a client can index it without a
|
|
97
119
|
// guard — the same shape `custom` publishes for the same reason.
|
|
98
|
-
mediaIds: []
|
|
120
|
+
mediaIds: [],
|
|
121
|
+
salutation: null,
|
|
122
|
+
firstName: null,
|
|
123
|
+
lastName: null,
|
|
124
|
+
department: null,
|
|
125
|
+
mobilePhone: null,
|
|
126
|
+
homePhone: null,
|
|
127
|
+
otherPhone: null,
|
|
128
|
+
fax: null,
|
|
129
|
+
birthdate: null,
|
|
130
|
+
assistantName: null,
|
|
131
|
+
assistantPhone: null,
|
|
132
|
+
reportsToContactId: null,
|
|
133
|
+
otherAddress: null,
|
|
134
|
+
// A flag reads `false` rather than null: unset IS "may be called".
|
|
135
|
+
doNotCall: false
|
|
99
136
|
};
|
|
100
137
|
for (const holder of groupId ? [
|
|
101
138
|
groupId
|
|
@@ -111,6 +148,10 @@ function contactCrmProfile(data, groupId) {
|
|
|
111
148
|
if (!held.length) profile[field] = normalizeCrmMediaIds(value);
|
|
112
149
|
continue;
|
|
113
150
|
}
|
|
151
|
+
if (field === 'doNotCall') {
|
|
152
|
+
if (value === true) profile[field] = true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
114
155
|
if (profile[field] === null && value !== undefined && value !== null) {
|
|
115
156
|
profile[field] = value;
|
|
116
157
|
}
|
|
@@ -303,6 +344,9 @@ function readContactInput(body, { partial }) {
|
|
|
303
344
|
}
|
|
304
345
|
const ownerUid = readOptionalText(body, 'ownerUid', CONTACT_NAME_MAX, errors);
|
|
305
346
|
if (ownerUid !== undefined) crm.ownerUid = ownerUid;
|
|
347
|
+
// Shape only here; the org's list judges the value in `contactCrmRefErrors`.
|
|
348
|
+
const leadSource = readOptionalText(body, 'leadSource', CRM_LABEL_MAX, errors);
|
|
349
|
+
if (leadSource !== undefined) crm.leadSource = leadSource;
|
|
306
350
|
if (body.mediaIds !== undefined) {
|
|
307
351
|
if (!Array.isArray(body.mediaIds)) {
|
|
308
352
|
errors.mediaIds = 'Must be an array of media ids';
|
|
@@ -320,6 +364,60 @@ function readContactInput(body, { partial }) {
|
|
|
320
364
|
const stage = readChoice(body, 'lifecycleStage', CONTACT_LIFECYCLE_STAGES, errors);
|
|
321
365
|
if (stage) crm.lifecycleStage = stage;
|
|
322
366
|
}
|
|
367
|
+
/*
|
|
368
|
+
* Salesforce's standard contact fields (AGL-3515), through the normalizers
|
|
369
|
+
* the console's profile route uses. The salutation and the reports-to are
|
|
370
|
+
* judged after this pass, against the org's list and its contacts.
|
|
371
|
+
*/ const salutation = readOptionalText(body, 'salutation', CRM_LABEL_MAX, errors);
|
|
372
|
+
if (salutation !== undefined) crm.salutation = salutation;
|
|
373
|
+
for (const key of [
|
|
374
|
+
'firstName',
|
|
375
|
+
'lastName'
|
|
376
|
+
]){
|
|
377
|
+
const part = readOptionalText(body, key, CONTACT_NAME_PART_MAX, errors);
|
|
378
|
+
if (part !== undefined) crm[key] = part;
|
|
379
|
+
}
|
|
380
|
+
for (const key of [
|
|
381
|
+
'department',
|
|
382
|
+
'assistantName'
|
|
383
|
+
]){
|
|
384
|
+
const text = readOptionalText(body, key, CONTACT_NAME_MAX, errors);
|
|
385
|
+
if (text !== undefined) crm[key] = text;
|
|
386
|
+
}
|
|
387
|
+
for (const key of CONTACT_EXTRA_PHONE_FIELDS){
|
|
388
|
+
const text = readOptionalText(body, key, CRM_LABEL_MAX, errors);
|
|
389
|
+
if (text === null) {
|
|
390
|
+
crm[key] = null;
|
|
391
|
+
} else if (text !== undefined) {
|
|
392
|
+
const normalized = normalizePhone(text);
|
|
393
|
+
if (normalized) crm[key] = normalized;
|
|
394
|
+
else errors[key] = 'Must be a phone number with a country code, like +15125550123';
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const birthdate = readOptionalText(body, 'birthdate', 32, errors);
|
|
398
|
+
if (birthdate === null) {
|
|
399
|
+
crm.birthdate = null;
|
|
400
|
+
} else if (birthdate !== undefined) {
|
|
401
|
+
const normalized = normalizeContactBirthdate(birthdate);
|
|
402
|
+
if (normalized) crm.birthdate = normalized;
|
|
403
|
+
else errors.birthdate = CONTACT_BIRTHDATE_REFUSAL;
|
|
404
|
+
}
|
|
405
|
+
const reportsTo = readRefId(body, 'reportsToContactId', errors);
|
|
406
|
+
if (reportsTo !== undefined) crm.reportsToContactId = reportsTo;
|
|
407
|
+
if (body.otherAddress !== undefined) {
|
|
408
|
+
if (body.otherAddress === null) {
|
|
409
|
+
crm.otherAddress = null;
|
|
410
|
+
} else if (typeof body.otherAddress !== 'object' || Array.isArray(body.otherAddress)) {
|
|
411
|
+
errors.otherAddress = 'Must be an address object';
|
|
412
|
+
} else {
|
|
413
|
+
crm.otherAddress = normalizeAddress(body.otherAddress);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (body.doNotCall !== undefined) {
|
|
417
|
+
if (body.doNotCall === null || body.doNotCall === false) crm.doNotCall = null;
|
|
418
|
+
else if (body.doNotCall === true) crm.doNotCall = true;
|
|
419
|
+
else errors.doNotCall = 'Must be true or false';
|
|
420
|
+
}
|
|
323
421
|
values.crm = crm;
|
|
324
422
|
// Whether the body MEANT to write a profile — judged by the keys it sent,
|
|
325
423
|
// not by which of them parsed, so a body whose every profile value was
|
|
@@ -380,15 +478,92 @@ function readContactInput(body, { partial }) {
|
|
|
380
478
|
* and `companyId`, so a contact cannot point at a company `/v1/deals` would
|
|
381
479
|
* refuse. Run after the synchronous grammar, so a body already refused never
|
|
382
480
|
* spends the reads.
|
|
383
|
-
*/ async function contactCrmRefErrors(ctx, crm
|
|
481
|
+
*/ async function contactCrmRefErrors(ctx, crm, at = {
|
|
482
|
+
contactId: null
|
|
483
|
+
}) {
|
|
384
484
|
var _crm_companyId;
|
|
385
|
-
|
|
485
|
+
var _facetAt;
|
|
486
|
+
const [owner, refs, leadSource, salutation, reportsTo] = await Promise.all([
|
|
386
487
|
memberError(ctx, 'ownerUid', crm.ownerUid),
|
|
387
488
|
crmRefErrors(ctx, {
|
|
388
489
|
companyId: (_crm_companyId = crm.companyId) != null ? _crm_companyId : undefined
|
|
389
|
-
})
|
|
490
|
+
}),
|
|
491
|
+
// The holder's current lead source is kept even when the list no longer offers it.
|
|
492
|
+
contactLeadSourceErrors(ctx, crm, (_facetAt = facetAt(ctx, at)) == null ? void 0 : _facetAt.facet.leadSource),
|
|
493
|
+
contactSalutationError(ctx, crm, at),
|
|
494
|
+
contactReportsToError(ctx, crm, at)
|
|
390
495
|
]);
|
|
391
|
-
return _extends({}, owner, refs);
|
|
496
|
+
return _extends({}, owner, refs, leadSource, salutation, reportsTo);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* The lead source a contact write stores (AGL-3511), judged against the
|
|
500
|
+
* org's Lead source list exactly as a lead's is: an active value stores the
|
|
501
|
+
* list's spelling, the holder's current value is always kept, and anything
|
|
502
|
+
* else is refused naming what the list allows. Rewrites `crm.leadSource` to
|
|
503
|
+
* the label the list spells. A contact takes no default — the list's default
|
|
504
|
+
* is for a new LEAD.
|
|
505
|
+
*/ async function contactLeadSourceErrors(ctx, crm, current) {
|
|
506
|
+
var _resolved_write;
|
|
507
|
+
if (typeof crm.leadSource !== 'string') return {};
|
|
508
|
+
const resolved = resolveCrmPicklistWrite(CRM_LEAD_SOURCE_PICKLIST, await readOrgLeadSourcePicklist(ctx), crm.leadSource, {
|
|
509
|
+
current,
|
|
510
|
+
created: false
|
|
511
|
+
});
|
|
512
|
+
if (resolved.ok === false) return {
|
|
513
|
+
leadSource: resolved.error
|
|
514
|
+
};
|
|
515
|
+
crm.leadSource = (_resolved_write = resolved.write) != null ? _resolved_write : null;
|
|
516
|
+
return {};
|
|
517
|
+
}
|
|
518
|
+
/** The facet a write at `at` reads its current values from. */ function facetAt(ctx, at) {
|
|
519
|
+
if (!at.data || !at.siteId) return null;
|
|
520
|
+
const group = consentGroupForHost(ctx.org, at.siteId);
|
|
521
|
+
return {
|
|
522
|
+
groupId: group.groupId,
|
|
523
|
+
facet: readContactFacet(at.data, group.groupId)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* The salutation, judged against the org's list (AGL-3515) the way the
|
|
528
|
+
* console's route judges it: an active value stores the list's spelling,
|
|
529
|
+
* the value the contact already holds is kept, anything else is named with
|
|
530
|
+
* the values allowed. Rewrites `crm.salutation` to the stored spelling.
|
|
531
|
+
*/ async function contactSalutationError(ctx, crm, at) {
|
|
532
|
+
var _judged_value;
|
|
533
|
+
var _facetAt;
|
|
534
|
+
if (!crm.salutation) return {};
|
|
535
|
+
const judged = judgeCrmPicklistValue(CRM_SALUTATION_PICKLIST, await readCrmPicklist(ctx.firestore, ctx.orgId, CRM_SALUTATION_PICKLIST), crm.salutation, (_facetAt = facetAt(ctx, at)) == null ? void 0 : _facetAt.facet.salutation);
|
|
536
|
+
if (judged.ok === false) return {
|
|
537
|
+
salutation: judged.error
|
|
538
|
+
};
|
|
539
|
+
crm.salutation = (_judged_value = judged.value) != null ? _judged_value : null;
|
|
540
|
+
return {};
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* The reports-to (AGL-3515): a contact of this organization, never the
|
|
544
|
+
* contact itself, and never one whose own chain — through the named site's
|
|
545
|
+
* facets — leads back to it.
|
|
546
|
+
*/ async function contactReportsToError(ctx, crm, at) {
|
|
547
|
+
const managerId = crm.reportsToContactId;
|
|
548
|
+
if (!managerId) return {};
|
|
549
|
+
if (managerId === at.contactId) {
|
|
550
|
+
return {
|
|
551
|
+
reportsToContactId: CONTACT_REPORTS_TO_SELF_REFUSAL
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const read = cachedContactReader(contactsCollection(ctx));
|
|
555
|
+
if (!await read(managerId)) {
|
|
556
|
+
return {
|
|
557
|
+
reportsToContactId: 'No such contact in this organization'
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const facet = facetAt(ctx, at);
|
|
561
|
+
if (at.contactId && facet && await contactReportsToLoops(read, at.contactId, managerId, facet.groupId)) {
|
|
562
|
+
return {
|
|
563
|
+
reportsToContactId: CONTACT_REPORTS_TO_LOOP_REFUSAL
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return {};
|
|
392
567
|
}
|
|
393
568
|
/**
|
|
394
569
|
* The facet a create writes when the body carried a CRM profile field, under
|
|
@@ -402,6 +577,9 @@ function readContactInput(body, { partial }) {
|
|
|
402
577
|
*/ function contactCrmCreateFields(ctx, crm, siteId) {
|
|
403
578
|
const stored = createPayload(crm);
|
|
404
579
|
if (Object.keys(stored).length === 0) return {};
|
|
580
|
+
// The holder's name is the first and last names' when either was given.
|
|
581
|
+
const composed = composeContactName(crm.firstName, crm.lastName);
|
|
582
|
+
if (composed) stored['name'] = composed;
|
|
405
583
|
const group = consentGroupForHost(ctx.org, siteId);
|
|
406
584
|
return _extends({
|
|
407
585
|
[CONTACT_FACETS_FIELD]: {
|
|
@@ -439,6 +617,15 @@ function readContactInput(body, { partial }) {
|
|
|
439
617
|
for (const [field, value] of Object.entries(updatePayload(crm))){
|
|
440
618
|
update[contactFacetPath(group.groupId, field)] = value;
|
|
441
619
|
}
|
|
620
|
+
/*
|
|
621
|
+
* The holder's name follows the first and last names (AGL-3515): either
|
|
622
|
+
* sent recomposes it from what the facet will hold, and both cleared
|
|
623
|
+
* leave it as it stands.
|
|
624
|
+
*/ if (crm.firstName !== undefined || crm.lastName !== undefined) {
|
|
625
|
+
const facet = readContactFacet(data, group.groupId);
|
|
626
|
+
const composed = composeContactName(crm.firstName === undefined ? facet.firstName : crm.firstName, crm.lastName === undefined ? facet.lastName : crm.lastName);
|
|
627
|
+
if (composed) update[contactFacetPath(group.groupId, 'name')] = composed;
|
|
628
|
+
}
|
|
442
629
|
let link = null;
|
|
443
630
|
if (crm.companyId !== undefined) {
|
|
444
631
|
link = planContactCompanyLink(readContactCompanyLink(data, group.groupId), crm.companyId);
|
|
@@ -586,6 +773,8 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
586
773
|
headers: ctx.headers
|
|
587
774
|
});
|
|
588
775
|
}
|
|
776
|
+
// The canonical name, or the first and last names' for a body with only those.
|
|
777
|
+
const canonicalName = name || composeContactName(crm.firstName, crm.lastName);
|
|
589
778
|
const collection = contactsCollection(ctx);
|
|
590
779
|
const claimed = await claimWrite(ctx, '*', request.headers.get('Idempotency-Key'), 'contacts');
|
|
591
780
|
if ('replay' in claimed) return claimed.replay;
|
|
@@ -625,7 +814,7 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
625
814
|
const id = createResourceUid();
|
|
626
815
|
await collection.doc(id).create(_extends({
|
|
627
816
|
email
|
|
628
|
-
},
|
|
817
|
+
}, canonicalName ? nameSearchFields(canonicalName) : {}, {
|
|
629
818
|
tags: tags != null ? tags : []
|
|
630
819
|
}, notes ? {
|
|
631
820
|
notes
|
|
@@ -695,6 +884,7 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
695
884
|
* does not grow the audience, and charging a plan refusal for renaming
|
|
696
885
|
* somebody would make a downgraded org unable to correct its own data.
|
|
697
886
|
*/ async function updateContact(request, ctx, contactRef) {
|
|
887
|
+
var _snap_data;
|
|
698
888
|
const parsed = readContactInput(await readJsonBody(request), {
|
|
699
889
|
partial: true
|
|
700
890
|
});
|
|
@@ -724,7 +914,11 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
724
914
|
headers: ctx.headers
|
|
725
915
|
});
|
|
726
916
|
}
|
|
727
|
-
const crmErrors = await contactCrmRefErrors(ctx, crm
|
|
917
|
+
const crmErrors = await contactCrmRefErrors(ctx, crm, {
|
|
918
|
+
contactId: contactRef.id,
|
|
919
|
+
siteId: consentSiteId,
|
|
920
|
+
data: (_snap_data = snap.data()) != null ? _snap_data : {}
|
|
921
|
+
});
|
|
728
922
|
if (Object.keys(crmErrors).length) {
|
|
729
923
|
return ApiErrors.badRequest({
|
|
730
924
|
message: 'Contact failed validation',
|
|
@@ -788,8 +982,8 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
788
982
|
}
|
|
789
983
|
let link = null;
|
|
790
984
|
if (consentSiteId) {
|
|
791
|
-
var
|
|
792
|
-
const crmFields = contactCrmUpdateFields(ctx, (
|
|
985
|
+
var _snap_data1;
|
|
986
|
+
const crmFields = contactCrmUpdateFields(ctx, (_snap_data1 = snap.data()) != null ? _snap_data1 : {}, crm, consentSiteId);
|
|
793
987
|
Object.assign(update, crmFields.update);
|
|
794
988
|
link = crmFields.link;
|
|
795
989
|
}
|
|
@@ -820,7 +1014,10 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
820
1014
|
* Deletes the document whoever else holds it, and keeps every refusal it
|
|
821
1015
|
* held — each site's and the unscoped one — in the organization's retained
|
|
822
1016
|
* store in the same transaction (AGL-3338), so deleting a person over the
|
|
823
|
-
* API is not a way to add somebody who said no to a list.
|
|
1017
|
+
* API is not a way to add somebody who said no to a list. Then what pointed
|
|
1018
|
+
* at the person goes: every other contact's reports-to naming them, in
|
|
1019
|
+
* every holder's facet (AGL-3537), and their contact roles on deals
|
|
1020
|
+
* (AGL-3521).
|
|
824
1021
|
*
|
|
825
1022
|
* Takes an `Idempotency-Key` with `deleteRecord`'s exact semantics, and for a
|
|
826
1023
|
* sharper reason: an erasure request is the operation most likely to be run
|
|
@@ -831,9 +1028,14 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
831
1028
|
if ('replay' in claimed) return claimed.replay;
|
|
832
1029
|
const { claim } = claimed;
|
|
833
1030
|
try {
|
|
1031
|
+
// The holders whose facets may name the person as a reports-to (AGL-3537).
|
|
1032
|
+
let holders = [];
|
|
834
1033
|
const removed = await removeContactKeepingRefusals({
|
|
835
1034
|
contactRef,
|
|
836
|
-
decide:
|
|
1035
|
+
decide: (contact)=>{
|
|
1036
|
+
holders = contactFacetHolders(contact);
|
|
1037
|
+
return deleteWholeContact(contact);
|
|
1038
|
+
},
|
|
837
1039
|
nowMs: Date.now()
|
|
838
1040
|
});
|
|
839
1041
|
if (removed.outcome === 'missing') {
|
|
@@ -843,6 +1045,10 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
|
|
|
843
1045
|
headers: ctx.headers
|
|
844
1046
|
});
|
|
845
1047
|
}
|
|
1048
|
+
// Nobody reports to a deleted person (AGL-3537)…
|
|
1049
|
+
await clearReportsToOf(ctx.firestore, ctx.firestore.collection('orgs').doc(ctx.orgId).collection('contacts'), holders, contactRef.id);
|
|
1050
|
+
// …and they leave every deal's contact roles (AGL-3521).
|
|
1051
|
+
await sweepDealContactRoles(ctx.firestore, ctx.firestore.collection('orgs').doc(ctx.orgId).collection(CRM_COLLECTIONS.deals), contactRef.id, null, '[api-v1] contacts delete');
|
|
846
1052
|
const view = {
|
|
847
1053
|
id: contactRef.id,
|
|
848
1054
|
object: 'contact',
|