@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.223
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-next-activity-column.js +1 -1
- package/src/lib/components/crm-next-activity-column.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/pipeline-card.js +2 -1
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- 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 +97 -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 +17 -2
- 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-next-activity.d.ts +107 -0
- package/src/lib/model/crm-next-activity.js +140 -0
- package/src/lib/model/crm-next-activity.js.map +1 -0
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +259 -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/crm-task-reminders.d.ts +152 -0
- package/src/lib/model/crm-task-reminders.js +161 -0
- package/src/lib/model/crm-task-reminders.js.map +1 -0
- 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/next-activity.d.ts +1 -1
- package/src/lib/model/next-activity.js.map +1 -1
- 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 +78 -34
- 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 +188 -0
- package/src/lib/server/assign-contact-owner.js +446 -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 +140 -0
- package/src/lib/server/automation-steps.js +669 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.d.ts +17 -0
- package/src/lib/server/capture-contact.js +95 -26
- 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-email-activity.d.ts +118 -0
- package/src/lib/server/crm-email-activity.js +148 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.js +1 -1
- package/src/lib/server/crm-inbound-email.js.map +1 -1
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- 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/email-send.js +2 -1
- package/src/lib/server/email-send.js.map +1 -1
- 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 +23 -5
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/next-activity-routes.js +2 -1
- package/src/lib/server/next-activity-routes.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 +22 -8
- 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 +40 -3
- 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-reminders-route.js +2 -1
- package/src/lib/server/task-reminders-route.js.map +1 -1
- package/src/lib/server/task-routes.js +33 -17
- 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/model/crm-sharing.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/**\n * CRM SHARING FOR MULTI-BRAND ORGS (AGL-3336).\n *\n * An org manager shares a lead, a contact, a company or a deal with other\n * sites — by hand, from the record or a list's bulk bar, or by a SHARING\n * RULE that applies to every record it matches, now and later. The model is\n * Salesforce's three layers:\n *\n * 1. the org-wide default — the consent-group scope a record is captured\n * with (`consentGroupScope`), untouched by anything here;\n * 2. sharing rules — `crm.sharingRules` on the org document;\n * 3. manual shares — per record, per target.\n *\n * ## One field is enforced, so one field is widened\n *\n * Both enforcement layers read `visibleTo`: the rules with `hasAny`, every\n * list with `array-contains-any`. A share therefore ADDS its tokens to\n * `visibleTo`, which is what puts the record on the target site's lists as a\n * real query — no second field, no second index, no loaded-window match.\n * `'org'` is the token for \"all sites\", and it covers a site created later\n * with nothing to recompute, because every site's read set carries it.\n *\n * ## …and the sources stay distinguishable\n *\n * The record keeps its grants under `sharing` (`SCOPE_GRANTS_FIELD` in\n * core): one grant per rule and one per hand-shared target, and `added`,\n * the tokens the grants put on `visibleTo` that were not there already.\n * The record's own scope — its HELD tokens — is `visibleTo` without\n * `added`, so taking a grant away removes only what grants alone\n * contributed, and never a consent-group token. A token a site later\n * captures the person on is held from then on: the capturing sites\n * (`hostId`, `capturedByHostIds`) are never counted as added.\n *\n * ## Visibility is not consent, and not write access\n *\n * Nothing here reads or writes a consent entry. A site a record is shared\n * with sees it; it holds no basis to market to the person, and the\n * audience paths read `heldByHost`, not `visibleTo`. Write access is its\n * own field, `writeTo`, present only on a shared record: the held tokens\n * and the tokens of grants whose access is `edit`. The Firestore rules and\n * the CRM's routes check it; a read-only grant is the default.\n */\n\nimport type { ScopeToken } from '@aglyn/aglyn'\nimport {\n heldScopeTokens,\n hostIdsFromScope,\n hostScopeToken,\n isScopeToken,\n MAX_SCOPE_HOSTS,\n ORG_SCOPE_TOKEN,\n parseScopeToken,\n SCOPE_GRANTS_FIELD,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\n\n/*==========================================\n * WHAT IS SHARED.\n *=========================================*/\n\n/** The four record types a share or a rule applies to, by their collection. */\nexport const CRM_SHARING_OBJECTS = ['leads', 'contacts', 'companies', 'deals'] as const\nexport type CrmSharingObject = (typeof CRM_SHARING_OBJECTS)[number]\n\nexport function isCrmSharingObject(value: unknown): value is CrmSharingObject {\n return (\n typeof value === 'string' && (CRM_SHARING_OBJECTS as readonly string[]).includes(value)\n )\n}\n\n/** How each object reads in a sentence. */\nexport const CRM_SHARING_OBJECT_LABELS: Readonly<\n Record<CrmSharingObject, { one: string; many: string }>\n> = {\n leads: { one: 'lead', many: 'Leads' },\n contacts: { one: 'contact', many: 'Contacts' },\n companies: { one: 'company', many: 'Companies' },\n deals: { one: 'deal', many: 'Deals' },\n}\n\n/** What a target site may do with a shared record. Read-only unless a grant says edit. */\nexport type CrmShareAccess = 'read' | 'edit'\n\nexport function isCrmShareAccess(value: unknown): value is CrmShareAccess {\n return value === 'read' || value === 'edit'\n}\n\n/** Where a grant came from. */\nexport type CrmShareSource = 'manual' | 'rule'\n\n/** One grant on one record. */\nexport interface CrmShareGrant {\n source: CrmShareSource\n /** The sites it shares with: `['org']` for all sites, else host tokens. */\n tokens: ScopeToken[]\n access: CrmShareAccess\n /** The rule behind a `rule` grant. */\n ruleId?: string\n /** Who shared it by hand, as they were named at the time. */\n byUid?: string\n byName?: string\n /** When the grant was made. */\n atMs: number\n}\n\n/** `sharing` on a record — see the module note. */\nexport interface CrmRecordSharing {\n /** Keyed by {@link manualGrantKey} or {@link ruleGrantKey}. */\n grants: Record<string, CrmShareGrant>\n /** Every token any grant names — the record's shares, however held. */\n tokens: ScopeToken[]\n /** The tokens the grants ADDED to `visibleTo` — see `heldScopeTokens`. */\n added: ScopeToken[]\n /** The rules holding a grant here, so a rule's removal can find its records. */\n ruleIds: string[]\n /**\n * The held tokens of a record shared with all sites, and nothing\n * otherwise. `array-contains` on it answers \"is this record shared with\n * everyone AND held by site X\" — the one question `added` alone cannot,\n * since a query takes a single array clause.\n */\n orgWideHeldBy: ScopeToken[]\n}\n\n/** The record field its grants live under — the core's name for it. */\nexport const CRM_SHARING_FIELD = SCOPE_GRANTS_FIELD\n\n/** The record field naming who may WRITE it, present only while it is shared. */\nexport const CRM_WRITE_SCOPE_FIELD = 'writeTo'\n\n/** The array field a rule's removal queries. */\nexport const CRM_SHARING_RULE_IDS_PATH = `${CRM_SHARING_FIELD}.ruleIds`\n\n/** A hand share is one grant per target, so each is unshared on its own. */\nexport function manualGrantKey(token: string): string {\n const parsed = parseScopeToken(token)\n if (!parsed) throw new Error(`not a scope token: ${token}`)\n return parsed.kind === 'org' ? 'manual_org' : `manual_${parsed.hostId}`\n}\n\nexport function ruleGrantKey(ruleId: string): string {\n return `rule_${ruleId}`\n}\n\nfunction tokenList(raw: unknown): ScopeToken[] {\n if (!Array.isArray(raw)) return []\n return [...new Set(raw.filter(isScopeToken))]\n}\n\nfunction readGrant(raw: unknown): CrmShareGrant | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const value = raw as Record<string, unknown>\n const tokens = tokenList(value['tokens'])\n if (!tokens.length) return null\n const source = value['source'] === 'rule' ? 'rule' : value['source'] === 'manual' ? 'manual' : null\n if (!source) return null\n const ruleId = typeof value['ruleId'] === 'string' ? value['ruleId'] : ''\n if (source === 'rule' && !ruleId) return null\n const byUid = typeof value['byUid'] === 'string' ? value['byUid'] : ''\n const byName = typeof value['byName'] === 'string' ? value['byName'] : ''\n const atMs = typeof value['atMs'] === 'number' && Number.isFinite(value['atMs']) ? value['atMs'] : 0\n return {\n source,\n tokens,\n access: value['access'] === 'edit' ? 'edit' : 'read',\n ...(ruleId ? { ruleId } : {}),\n ...(byUid ? { byUid } : {}),\n ...(byName ? { byName } : {}),\n atMs,\n }\n}\n\n/** A record's grants, cleaned; `{}` for a record never shared. */\nexport function readRecordGrants(\n record: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, CrmShareGrant> {\n const sharing = (record ?? {})[CRM_SHARING_FIELD]\n if (!sharing || typeof sharing !== 'object' || Array.isArray(sharing)) return {}\n const grants = (sharing as Record<string, unknown>)['grants']\n if (!grants || typeof grants !== 'object' || Array.isArray(grants)) return {}\n const out: Record<string, CrmShareGrant> = {}\n for (const [key, raw] of Object.entries(grants as Record<string, unknown>)) {\n const grant = readGrant(raw)\n if (grant) out[key] = grant\n }\n return out\n}\n\n/*==========================================\n * THE PLAN: grants in, the three fields out.\n *=========================================*/\n\n/**\n * The sites that captured or created the record. Their tokens are HELD,\n * whatever `added` says: a person shared to site B and later captured on B\n * is B's own from then on, and unsharing must not hide them from B.\n */\nexport function recordHolderHostIds(record: Readonly<Record<string, unknown>>): string[] {\n const ids = new Set<string>()\n const hostId = record['hostId']\n if (typeof hostId === 'string' && hostId) ids.add(hostId)\n const captured = record['capturedByHostIds']\n if (Array.isArray(captured)) {\n for (const id of captured) if (typeof id === 'string' && id) ids.add(id)\n }\n return [...ids]\n}\n\n/**\n * The record's own scope: `visibleTo` less what grants added, its capturing\n * sites kept — the core's `heldScopeTokens`, as scope tokens.\n */\nexport function recordHeldScope(record: Readonly<Record<string, unknown>>): ScopeToken[] {\n return tokenList(heldScopeTokens(record))\n}\n\nexport interface CrmSharingPlan {\n visibleTo: ScopeToken[]\n /** `null` removes the field: an unshared record writes by `visibleTo`. */\n writeTo: ScopeToken[] | null\n /** `null` removes the field. */\n sharing: CrmRecordSharing | null\n}\n\n/**\n * The fields a record carries under `grants`.\n *\n * A grant naming a token the record already holds adds nothing, and a\n * record held org-wide adds nothing at all — every site sees it already, so\n * a share there would only make a later unshare look like it hid something.\n * The token order is the held scope first and the additions after it, so an\n * unchanged record plans the `visibleTo` it has.\n */\nexport function planRecordSharing(\n record: Readonly<Record<string, unknown>>,\n grants: Readonly<Record<string, CrmShareGrant>>,\n): CrmSharingPlan {\n const held = recordHeldScope(record)\n const entries = Object.entries(grants)\n if (!entries.length) return { visibleTo: held, writeTo: null, sharing: null }\n const heldSet = new Set<string>(held)\n const heldOrgWide = heldSet.has(ORG_SCOPE_TOKEN)\n const tokens: ScopeToken[] = []\n const editTokens: ScopeToken[] = []\n const ruleIds: string[] = []\n for (const [, grant] of entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {\n for (const token of grant.tokens) {\n if (!tokens.includes(token)) tokens.push(token)\n if (grant.access === 'edit' && !editTokens.includes(token)) editTokens.push(token)\n }\n if (grant.source === 'rule' && grant.ruleId && !ruleIds.includes(grant.ruleId)) {\n ruleIds.push(grant.ruleId)\n }\n }\n const added = heldOrgWide ? [] : tokens.filter((token) => !heldSet.has(token))\n const writeTo = [...held]\n for (const token of editTokens) if (!writeTo.includes(token)) writeTo.push(token)\n return {\n visibleTo: [...held, ...added],\n writeTo,\n sharing: {\n grants: Object.fromEntries(entries),\n tokens,\n added,\n ruleIds,\n orgWideHeldBy: added.includes(ORG_SCOPE_TOKEN) ? [...held] : [],\n },\n }\n}\n\nconst sameList = (a: readonly unknown[] | null | undefined, b: readonly unknown[] | null | undefined) =>\n (a ?? null) === null\n ? (b ?? null) === null\n : (b ?? null) !== null && a!.length === b!.length && a!.every((entry, at) => entry === b![at])\n\nconst sameGrant = (a: CrmShareGrant | undefined, b: CrmShareGrant | undefined) =>\n !!a &&\n !!b &&\n a.source === b.source &&\n a.access === b.access &&\n (a.ruleId ?? '') === (b.ruleId ?? '') &&\n (a.byUid ?? '') === (b.byUid ?? '') &&\n (a.byName ?? '') === (b.byName ?? '') &&\n a.atMs === b.atMs &&\n sameList(a.tokens, b.tokens)\n\n/** Whether writing `plan` would change what the record stores. */\nexport function sharingPlanChanges(\n record: Readonly<Record<string, unknown>>,\n plan: CrmSharingPlan,\n): boolean {\n if (!sameList(tokenList(record['visibleTo']), plan.visibleTo)) return true\n const storedWrite = record[CRM_WRITE_SCOPE_FIELD]\n if (!sameList(Array.isArray(storedWrite) ? tokenList(storedWrite) : null, plan.writeTo)) return true\n const stored = record[CRM_SHARING_FIELD]\n if (!plan.sharing) return stored !== undefined && stored !== null\n const grants = readRecordGrants(record)\n const keys = Object.keys(plan.sharing.grants)\n if (keys.length !== Object.keys(grants).length) return true\n if (!keys.every((key) => sameGrant(grants[key], plan.sharing!.grants[key]))) return true\n const sharing = (stored ?? {}) as Record<string, unknown>\n return (\n !sameList(tokenList(sharing['tokens']), plan.sharing.tokens) ||\n !sameList(tokenList(sharing['added']), plan.sharing.added) ||\n !sameList(\n Array.isArray(sharing['ruleIds']) ? (sharing['ruleIds'] as unknown[]) : [],\n plan.sharing.ruleIds,\n ) ||\n !sameList(tokenList(sharing['orgWideHeldBy']), plan.sharing.orgWideHeldBy)\n )\n}\n\n/*==========================================\n * SHARING RULES: `crm.sharingRules` on the org document.\n *=========================================*/\n\n/** The org document path the rules are stored under. */\nexport const CRM_SHARING_RULES_PATH = 'crm.sharingRules'\n\n/** A workspace keeps at most this many. */\nexport const CRM_SHARING_RULES_MAX = 20\n\n/** At most this many values per criterion. */\nexport const CRM_SHARING_CRITERION_MAX = 20\n\n/**\n * Which records a rule matches, beyond the object and the capturing site.\n * AND across the keys, OR within one; an absent or empty key matches\n * everything.\n */\nexport interface CrmSharingCriteria {\n /** A lead's or a contact's lead source, by label. */\n leadSources?: string[]\n /** Any of these tags (lead, contact, company). */\n tags?: string[]\n /** Filed under any of these campaigns (lead, contact). */\n campaignIds?: string[]\n /** A lead's status, a contact's lifecycle stage, or a deal's stage id. */\n stages?: string[]\n /** Owned by any of these members. */\n ownerUids?: string[]\n}\n\nexport const CRM_SHARING_CRITERIA_KEYS = [\n 'leadSources',\n 'tags',\n 'campaignIds',\n 'stages',\n 'ownerUids',\n] as const satisfies readonly (keyof CrmSharingCriteria)[]\n\n/** Which criteria an object's records can answer. */\nexport const CRM_SHARING_CRITERIA_FOR: Readonly<\n Record<CrmSharingObject, readonly (keyof CrmSharingCriteria)[]>\n> = {\n leads: ['leadSources', 'tags', 'campaignIds', 'stages', 'ownerUids'],\n contacts: ['leadSources', 'tags', 'campaignIds', 'stages', 'ownerUids'],\n companies: ['tags', 'ownerUids'],\n deals: ['stages', 'ownerUids'],\n}\n\n/** Where a rule's recompute stands. */\nexport interface CrmSharingRuleRun {\n status: 'running' | 'done' | 'failed'\n /** What the run does: apply the rule everywhere, or take it away. */\n mode: 'apply' | 'remove'\n /** Records examined. */\n processed: number\n /** Records whose sharing changed. */\n changed: number\n /** The last record id an `apply` run examined, to continue after. */\n cursor?: string | null\n startedAtMs: number\n finishedAtMs?: number\n error?: string\n}\n\nexport interface CrmSharingRule {\n id: string\n name: string\n object: CrmSharingObject\n /** Off keeps the rule and takes away what it granted. */\n enabled: boolean\n /** Records captured or created on any of these sites; empty is every site. */\n sourceHostIds: string[]\n criteria: CrmSharingCriteria\n /** `['org']` for all sites, including sites added later; else host tokens. */\n targets: ScopeToken[]\n access: CrmShareAccess\n /** Being deleted: its grants are being removed, and it matches nothing. */\n deleting?: boolean\n createdByUid?: string\n createdAtMs: number\n updatedAtMs: number\n run?: CrmSharingRuleRun\n}\n\nconst RULE_ID = /^[A-Za-z0-9_-]{1,64}$/\n\nfunction stringList(raw: unknown, max = CRM_SHARING_CRITERION_MAX): string[] {\n if (!Array.isArray(raw)) return []\n const out: string[] = []\n for (const entry of raw) {\n const value = String(entry ?? '').trim().slice(0, 200)\n if (value && !out.includes(value)) out.push(value)\n if (out.length >= max) break\n }\n return out\n}\n\n/** One rule as stored, cleaned, or null when it cannot be applied. */\nexport function readCrmSharingRule(raw: unknown): CrmSharingRule | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const value = raw as Record<string, unknown>\n const id = String(value['id'] ?? '')\n if (!RULE_ID.test(id)) return null\n if (!isCrmSharingObject(value['object'])) return null\n const targets = normalizeShareTargets(tokenList(value['targets']))\n if (!targets) return null\n const rawCriteria = (value['criteria'] ?? {}) as Record<string, unknown>\n const criteria: CrmSharingCriteria = {}\n for (const key of CRM_SHARING_CRITERIA_FOR[value['object']]) {\n const list = stringList(rawCriteria[key])\n if (list.length) criteria[key] = key === 'tags' ? list.map((tag) => tag.toLowerCase()) : list\n }\n const run = value['run'] as Record<string, unknown> | undefined\n return {\n id,\n name: String(value['name'] ?? '').trim().slice(0, 120) || 'Sharing rule',\n object: value['object'],\n enabled: value['enabled'] !== false,\n sourceHostIds: stringList(value['sourceHostIds'], MAX_SCOPE_HOSTS),\n criteria,\n targets,\n access: value['access'] === 'edit' ? 'edit' : 'read',\n ...(value['deleting'] === true ? { deleting: true } : {}),\n ...(typeof value['createdByUid'] === 'string' ? { createdByUid: value['createdByUid'] } : {}),\n createdAtMs: Number(value['createdAtMs']) || 0,\n updatedAtMs: Number(value['updatedAtMs']) || 0,\n ...(run && typeof run === 'object'\n ? {\n run: {\n status:\n run['status'] === 'done' || run['status'] === 'failed' ? run['status'] : 'running',\n mode: run['mode'] === 'remove' ? 'remove' : 'apply',\n processed: Number(run['processed']) || 0,\n changed: Number(run['changed']) || 0,\n cursor: typeof run['cursor'] === 'string' ? run['cursor'] : null,\n startedAtMs: Number(run['startedAtMs']) || 0,\n ...(typeof run['finishedAtMs'] === 'number' ? { finishedAtMs: run['finishedAtMs'] } : {}),\n ...(typeof run['error'] === 'string' ? { error: run['error'] } : {}),\n },\n }\n : {}),\n }\n}\n\n/** The org's sharing rules, in their stored order. */\nexport function readCrmSharingRules(\n org: Readonly<Record<string, unknown>> | null | undefined,\n): CrmSharingRule[] {\n const crm = (org ?? {})['crm']\n const raw =\n crm && typeof crm === 'object' && !Array.isArray(crm)\n ? (crm as Record<string, unknown>)['sharingRules']\n : undefined\n if (!Array.isArray(raw)) return []\n const out: CrmSharingRule[] = []\n for (const entry of raw) {\n const rule = readCrmSharingRule(entry)\n if (rule && !out.some((existing) => existing.id === rule.id)) out.push(rule)\n }\n return out\n}\n\n/** The rules that grant anything now: enabled, and not being deleted. */\nexport function activeSharingRules(\n rules: readonly CrmSharingRule[],\n object?: CrmSharingObject,\n): CrmSharingRule[] {\n return rules.filter(\n (rule) => rule.enabled && !rule.deleting && (!object || rule.object === object),\n )\n}\n\n/**\n * A share's targets, cleaned for storage: `['org']` when all sites are\n * named, else the host tokens, capped. `null` when nothing is left.\n */\nexport function normalizeShareTargets(tokens: readonly string[]): ScopeToken[] | null {\n const clean = tokenList(tokens)\n if (!clean.length) return null\n if (clean.includes(ORG_SCOPE_TOKEN)) return [ORG_SCOPE_TOKEN]\n if (clean.length > MAX_SCOPE_HOSTS) return null\n return clean\n}\n\n/** Targets from a request: `'all'`, or a list of host ids. */\nexport function shareTargetsFrom(input: unknown): ScopeToken[] | null {\n if (input === 'all') return [ORG_SCOPE_TOKEN]\n if (!Array.isArray(input)) return null\n return normalizeShareTargets(\n input\n .map((id) => String(id ?? '').trim())\n .filter((id) => id && !id.includes('/') && !id.includes(':'))\n .map(hostScopeToken),\n )\n}\n\n/*==========================================\n * WHAT A RULE READS OFF A RECORD.\n *=========================================*/\n\nexport interface CrmSharingFacts {\n hostIds: string[]\n leadSources: string[]\n tags: string[]\n campaignIds: string[]\n stages: string[]\n ownerUids: string[]\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\nconst strs = (value: unknown): string[] =>\n Array.isArray(value) ? value.map(str).filter(Boolean) : []\n\n/**\n * The facts a rule's criteria are matched against. A contact keeps each\n * holder's working fields in its own facet, so a contact answers with the\n * union of its facets: a rule about \"contacts tagged vip\" matches a person\n * any holder tagged.\n */\nexport function crmSharingFacts(\n object: CrmSharingObject,\n record: Readonly<Record<string, unknown>>,\n): CrmSharingFacts {\n const facts: CrmSharingFacts = {\n hostIds: recordHolderHostIds(record),\n leadSources: [],\n tags: [],\n campaignIds: [],\n stages: [],\n ownerUids: [],\n }\n const take = (source: Readonly<Record<string, unknown>>, stageField: string | null) => {\n const leadSource = str(source['leadSource'])\n if (leadSource) facts.leadSources.push(leadSource)\n facts.tags.push(...strs(source['tags']).map((tag) => tag.toLowerCase()))\n facts.campaignIds.push(...strs(source['campaignIds']))\n const stage = stageField ? str(source[stageField]) : ''\n if (stage) facts.stages.push(stage)\n const owner = str(source['ownerUid'])\n if (owner) facts.ownerUids.push(owner)\n }\n if (object === 'leads') take(record, 'status')\n else if (object === 'companies') take(record, null)\n else if (object === 'deals') take(record, 'stageId')\n else {\n const facets = record['facets']\n if (facets && typeof facets === 'object' && !Array.isArray(facets)) {\n for (const facet of Object.values(facets as Record<string, unknown>)) {\n if (facet && typeof facet === 'object' && !Array.isArray(facet)) {\n take(facet as Record<string, unknown>, 'lifecycleStage')\n }\n }\n }\n }\n // A lead with no status is New, as every lead reader answers it.\n if (object === 'leads' && !facts.stages.length) facts.stages.push('new')\n return facts\n}\n\nconst anyOf = (wanted: readonly string[] | undefined, have: readonly string[], fold = false) => {\n if (!wanted?.length) return true\n const set = new Set(fold ? have.map((value) => value.toLowerCase()) : have)\n return wanted.some((value) => set.has(fold ? value.toLowerCase() : value))\n}\n\n/** Whether a rule shares this record. */\nexport function crmSharingRuleMatches(\n rule: CrmSharingRule,\n object: CrmSharingObject,\n record: Readonly<Record<string, unknown>>,\n): boolean {\n if (!rule.enabled || rule.deleting || rule.object !== object) return false\n const facts = crmSharingFacts(object, record)\n if (rule.sourceHostIds.length && !facts.hostIds.some((id) => rule.sourceHostIds.includes(id))) {\n return false\n }\n const { criteria } = rule\n return (\n anyOf(criteria.leadSources, facts.leadSources, true) &&\n anyOf(criteria.tags, facts.tags, true) &&\n anyOf(criteria.campaignIds, facts.campaignIds) &&\n anyOf(criteria.stages, facts.stages) &&\n anyOf(criteria.ownerUids, facts.ownerUids)\n )\n}\n\n/**\n * The grants a record should carry: its hand shares as they are, and one\n * grant per active rule that matches it now. A rule grant already present\n * keeps its date, so re-evaluating an unchanged record writes nothing.\n *\n * A rule grant whose rule is not among `rules` is dropped — the caller\n * passes every active rule of the org, so a rule absent from them has been\n * switched off or deleted.\n */\nexport function evaluateRecordGrants(\n object: CrmSharingObject,\n record: Readonly<Record<string, unknown>>,\n rules: readonly CrmSharingRule[],\n atMs: number,\n): Record<string, CrmShareGrant> {\n const current = readRecordGrants(record)\n const next: Record<string, CrmShareGrant> = {}\n for (const [key, grant] of Object.entries(current)) {\n if (grant.source === 'manual') next[key] = grant\n }\n for (const rule of activeSharingRules(rules, object)) {\n if (!crmSharingRuleMatches(rule, object, record)) continue\n const key = ruleGrantKey(rule.id)\n const before = current[key]\n next[key] = {\n source: 'rule',\n ruleId: rule.id,\n tokens: [...rule.targets],\n access: rule.access,\n atMs: before && sameList(before.tokens, rule.targets) && before.access === rule.access\n ? before.atMs\n : atMs,\n }\n }\n return next\n}\n\n/** The hand shares after sharing with `targets`, by `by`. */\nexport function withManualShares(\n grants: Readonly<Record<string, CrmShareGrant>>,\n targets: readonly ScopeToken[],\n access: CrmShareAccess,\n by: { uid: string; name?: string | null },\n atMs: number,\n): Record<string, CrmShareGrant> {\n const next = { ...grants }\n for (const token of targets) {\n const name = String(by.name ?? '').trim()\n next[manualGrantKey(token)] = {\n source: 'manual',\n tokens: [token],\n access,\n byUid: by.uid,\n ...(name ? { byName: name } : {}),\n atMs,\n }\n }\n return next\n}\n\n/** The hand shares after unsharing `targets`. Rule grants are the rule's to take away. */\nexport function withoutManualShares(\n grants: Readonly<Record<string, CrmShareGrant>>,\n targets: readonly string[],\n): Record<string, CrmShareGrant> {\n const drop = new Set(targets.filter(isScopeToken).map(manualGrantKey))\n return Object.fromEntries(Object.entries(grants).filter(([key]) => !drop.has(key)))\n}\n\n/*==========================================\n * WHAT A READER SEES.\n *=========================================*/\n\n/**\n * Whether a caller holding `tokens` may write the record: `writeTo` on a\n * shared record, else `visibleTo` — the Firestore rules' own predicate.\n */\nexport function crmRecordWritableBy(\n record: Readonly<Record<string, unknown>>,\n tokens: readonly string[],\n): boolean {\n const writeTo = record[CRM_WRITE_SCOPE_FIELD]\n const scope = Array.isArray(writeTo) ? writeTo : record['visibleTo']\n if (!Array.isArray(scope) || !tokens.length) return false\n return scope.some((token) => tokens.includes(token as string))\n}\n\n/** How a record reached a site that sees it only through a share. */\nexport interface CrmShareChip {\n source: CrmShareSource\n access: CrmShareAccess\n /** The member who shared it by hand. */\n byName?: string\n /** The rule that shared it. */\n ruleId?: string\n /** Shared with every site. */\n allSites: boolean\n}\n\n/**\n * Why a site seeing this record sees it, when the answer is a share — or\n * `null` when the site holds it (or cannot see it at all). `hostIds` are\n * the viewing group's sites; at the organization level there is none.\n */\nexport function crmShareChipFor(\n record: Readonly<Record<string, unknown>> | null | undefined,\n hostIds: readonly string[],\n): CrmShareChip | null {\n if (!record || !hostIds.length) return null\n const held = recordHeldScope(record)\n if (held.includes(ORG_SCOPE_TOKEN)) return null\n const viewing = new Set(hostIds.map(hostScopeToken))\n if (held.some((token) => viewing.has(token))) return null\n const grants = Object.values(readRecordGrants(record))\n const reaching = grants.filter((grant) =>\n grant.tokens.some((token) => token === ORG_SCOPE_TOKEN || viewing.has(token)),\n )\n if (!reaching.length) return null\n // A hand share names a person, so it leads; an edit grant outranks read.\n const pick =\n reaching.find((grant) => grant.source === 'manual' && grant.access === 'edit') ??\n reaching.find((grant) => grant.source === 'manual') ??\n reaching.find((grant) => grant.access === 'edit') ??\n reaching[0]\n return {\n source: pick.source,\n access: reaching.some((grant) => grant.access === 'edit') ? 'edit' : 'read',\n ...(pick.byName ? { byName: pick.byName } : {}),\n ...(pick.ruleId ? { ruleId: pick.ruleId } : {}),\n allSites: pick.tokens.includes(ORG_SCOPE_TOKEN),\n }\n}\n\n/** The chip's words: \"Shared by Dana\", \"Shared by rule Brand A leads\". */\nexport function crmShareChipLabel(\n chip: CrmShareChip,\n rules: readonly Pick<CrmSharingRule, 'id' | 'name'>[] = [],\n): string {\n if (chip.source === 'manual') return chip.byName ? `Shared by ${chip.byName}` : 'Shared by hand'\n const rule = rules.find((entry) => entry.id === chip.ruleId)\n return rule ? `Shared by rule ${rule.name}` : 'Shared by a rule'\n}\n\n/** One line of a record's Sharing card. */\nexport type CrmSharingLine =\n | { kind: 'held'; hostIds: string[]; allSites: boolean }\n | {\n kind: 'manual'\n key: string\n token: ScopeToken\n access: CrmShareAccess\n byName?: string\n atMs: number\n }\n | { kind: 'rule'; key: string; ruleId: string; tokens: ScopeToken[]; access: CrmShareAccess; atMs: number }\n\n/**\n * Where a record is visible and why: the sites that hold it (its consent\n * group or its creating site), then one line per hand share, then one per\n * rule. The held line is never unsharable; a hand share is, in one click.\n */\nexport function describeRecordSharing(\n record: Readonly<Record<string, unknown>> | null | undefined,\n): CrmSharingLine[] {\n if (!record) return []\n const held = recordHeldScope(record)\n const lines: CrmSharingLine[] = [\n { kind: 'held', hostIds: hostIdsFromScope(held), allSites: held.includes(ORG_SCOPE_TOKEN) },\n ]\n const grants = Object.entries(readRecordGrants(record))\n for (const [key, grant] of grants) {\n if (grant.source !== 'manual') continue\n lines.push({\n kind: 'manual',\n key,\n token: grant.tokens[0],\n access: grant.access,\n ...(grant.byName ? { byName: grant.byName } : {}),\n atMs: grant.atMs,\n })\n }\n for (const [key, grant] of grants) {\n if (grant.source !== 'rule' || !grant.ruleId) continue\n lines.push({\n kind: 'rule',\n key,\n ruleId: grant.ruleId,\n tokens: grant.tokens,\n access: grant.access,\n atMs: grant.atMs,\n })\n }\n return lines\n}\n\n/** \"All sites\", a site's name, or \"N sites\". */\nexport function describeShareTargets(\n tokens: readonly string[],\n siteName: (hostId: string) => string,\n): string {\n if (tokens.includes(ORG_SCOPE_TOKEN)) return 'All sites'\n const ids = hostIdsFromScope(tokens)\n if (ids.length === 1) return siteName(ids[0])\n if (ids.length <= 3) return ids.map(siteName).join(', ')\n return `${ids.length} sites`\n}\n\n/** A rule as one sentence of its \"which records\" half. */\nexport function describeSharingRuleScope(\n rule: Pick<CrmSharingRule, 'object' | 'sourceHostIds' | 'criteria'>,\n siteName: (hostId: string) => string,\n labels: { owner?: (uid: string) => string; stage?: (id: string) => string } = {},\n): string {\n const parts: string[] = [CRM_SHARING_OBJECT_LABELS[rule.object].many]\n if (rule.sourceHostIds.length) {\n parts.push(`captured on ${rule.sourceHostIds.map(siteName).join(' or ')}`)\n }\n const { criteria } = rule\n if (criteria.leadSources?.length) parts.push(`from ${criteria.leadSources.join(' or ')}`)\n if (criteria.tags?.length) parts.push(`tagged ${criteria.tags.join(' or ')}`)\n if (criteria.campaignIds?.length) {\n parts.push(`in ${criteria.campaignIds.length === 1 ? 'a campaign' : `${criteria.campaignIds.length} campaigns`}`)\n }\n if (criteria.stages?.length) {\n parts.push(`at ${criteria.stages.map((id) => labels.stage?.(id) ?? id).join(' or ')}`)\n }\n if (criteria.ownerUids?.length) {\n parts.push(`owned by ${criteria.ownerUids.map((uid) => labels.owner?.(uid) ?? uid).join(' or ')}`)\n }\n return parts.join(', ')\n}\n\n/*==========================================\n * THE ROUTE.\n *=========================================*/\n\n/** `POST /api/crm/sharing` — every sharing act, by `action`. */\nexport const CRM_SHARING_ROUTE = 'crm/sharing' as const\n\nexport const crmSharingRouteUrl = (): string => `/api/${CRM_SHARING_ROUTE}`\n\n/** Records one share or unshare may name. */\nexport const CRM_SHARING_IDS_MAX = 200\n\nexport type CrmSharingAction =\n | 'sites'\n | 'share'\n | 'unshare'\n | 'evaluate'\n | 'rule-save'\n | 'rule-delete'\n | 'rule-continue'\n\nexport interface CrmSharingSite {\n id: string\n name: string\n}\n\nexport interface CrmSharingResponse {\n ok: true\n /** `sites`: the org's sites, by name. */\n sites?: CrmSharingSite[]\n /** `share`/`unshare`/`evaluate`: records whose sharing changed. */\n changed?: number\n /** Records named that could not be found. */\n missing?: number\n /** The rule a rule action saved or ran. */\n rule?: CrmSharingRule | null\n /** Whether a rule run has more to do (`rule-continue` again). */\n more?: boolean\n}\n"],"names":["heldScopeTokens","hostIdsFromScope","hostScopeToken","isScopeToken","MAX_SCOPE_HOSTS","ORG_SCOPE_TOKEN","parseScopeToken","SCOPE_GRANTS_FIELD","CRM_SHARING_OBJECTS","isCrmSharingObject","value","includes","CRM_SHARING_OBJECT_LABELS","leads","one","many","contacts","companies","deals","isCrmShareAccess","CRM_SHARING_FIELD","CRM_WRITE_SCOPE_FIELD","CRM_SHARING_RULE_IDS_PATH","manualGrantKey","token","parsed","Error","kind","hostId","ruleGrantKey","ruleId","tokenList","raw","Array","isArray","Set","filter","readGrant","tokens","length","source","byUid","byName","atMs","Number","isFinite","access","readRecordGrants","record","sharing","grants","out","key","Object","entries","grant","recordHolderHostIds","ids","add","captured","id","recordHeldScope","planRecordSharing","held","visibleTo","writeTo","heldSet","heldOrgWide","has","editTokens","ruleIds","sort","a","b","push","added","fromEntries","orgWideHeldBy","sameList","every","entry","at","sameGrant","sharingPlanChanges","plan","storedWrite","stored","undefined","keys","CRM_SHARING_RULES_PATH","CRM_SHARING_RULES_MAX","CRM_SHARING_CRITERION_MAX","CRM_SHARING_CRITERIA_KEYS","CRM_SHARING_CRITERIA_FOR","RULE_ID","stringList","max","String","trim","slice","readCrmSharingRule","test","targets","normalizeShareTargets","rawCriteria","criteria","list","map","tag","toLowerCase","run","name","object","enabled","sourceHostIds","deleting","createdByUid","createdAtMs","updatedAtMs","status","mode","processed","changed","cursor","startedAtMs","finishedAtMs","error","readCrmSharingRules","org","crm","rule","some","existing","activeSharingRules","rules","clean","shareTargetsFrom","input","str","strs","Boolean","crmSharingFacts","facts","hostIds","leadSources","tags","campaignIds","stages","ownerUids","take","stageField","leadSource","stage","owner","facets","facet","values","anyOf","wanted","have","fold","set","crmSharingRuleMatches","evaluateRecordGrants","current","next","before","withManualShares","by","uid","withoutManualShares","drop","crmRecordWritableBy","scope","crmShareChipFor","reaching","viewing","pick","find","allSites","crmShareChipLabel","chip","describeRecordSharing","lines","describeShareTargets","siteName","join","describeSharingRuleScope","labels","parts","CRM_SHARING_ROUTE","crmSharingRouteUrl","CRM_SHARING_IDS_MAX"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/crm-sharing.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/**\n * CRM SHARING FOR MULTI-BRAND ORGS (AGL-3336).\n *\n * An org manager shares a lead, a contact, a company or a deal with other\n * sites — by hand, from the record or a list's bulk bar, or by a SHARING\n * RULE that applies to every record it matches, now and later. The model is\n * Salesforce's three layers:\n *\n * 1. the org-wide default — the consent-group scope a record is captured\n * with (`consentGroupScope`), untouched by anything here;\n * 2. sharing rules — `crm.sharingRules` on the org document;\n * 3. manual shares — per record, per target.\n *\n * ## One field is enforced, so one field is widened\n *\n * Both enforcement layers read `visibleTo`: the rules with `hasAny`, every\n * list with `array-contains-any`. A share therefore ADDS its tokens to\n * `visibleTo`, which is what puts the record on the target site's lists as a\n * real query — no second field, no second index, no loaded-window match.\n * `'org'` is the token for \"all sites\", and it covers a site created later\n * with nothing to recompute, because every site's read set carries it.\n *\n * ## …and the sources stay distinguishable\n *\n * The record keeps its grants under `sharing` (`SCOPE_GRANTS_FIELD` in\n * core): one grant per rule and one per hand-shared target, and `added`,\n * the tokens the grants put on `visibleTo` that were not there already.\n * The record's own scope — its HELD tokens — is `visibleTo` without\n * `added`, so taking a grant away removes only what grants alone\n * contributed, and never a consent-group token. A token a site later\n * captures the person on is held from then on: the capturing sites\n * (`hostId`, `capturedByHostIds`) are never counted as added.\n *\n * ## Visibility is not consent, and not write access\n *\n * Nothing here reads or writes a consent entry. A site a record is shared\n * with sees it; it holds no basis to market to the person, and the\n * audience paths read `heldByHost`, not `visibleTo`. Write access is its\n * own field, `writeTo`, present only on a shared record: the held tokens\n * and the tokens of grants whose access is `edit`. The Firestore rules and\n * the CRM's routes check it; a read-only grant is the default.\n */\n\nimport type { ScopeToken } from '@aglyn/aglyn'\nimport {\n heldScopeTokens,\n hostIdsFromScope,\n hostScopeToken,\n isScopeToken,\n MAX_SCOPE_HOSTS,\n ORG_SCOPE_TOKEN,\n parseScopeToken,\n SCOPE_GRANTS_FIELD,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type { CrmPicklist } from '@aglyn/aglyn/app-utils/crm'\nimport {\n isLeadSourceDirection,\n LEAD_SOURCE_DIRECTION_LABELS,\n leadSourceDirectionOf,\n STANDARD_LEAD_SOURCES,\n} from './lead-source-direction'\n\n/*==========================================\n * WHAT IS SHARED.\n *=========================================*/\n\n/** The four record types a share or a rule applies to, by their collection. */\nexport const CRM_SHARING_OBJECTS = ['leads', 'contacts', 'companies', 'deals'] as const\nexport type CrmSharingObject = (typeof CRM_SHARING_OBJECTS)[number]\n\nexport function isCrmSharingObject(value: unknown): value is CrmSharingObject {\n return (\n typeof value === 'string' && (CRM_SHARING_OBJECTS as readonly string[]).includes(value)\n )\n}\n\n/** How each object reads in a sentence. */\nexport const CRM_SHARING_OBJECT_LABELS: Readonly<\n Record<CrmSharingObject, { one: string; many: string }>\n> = {\n leads: { one: 'lead', many: 'Leads' },\n contacts: { one: 'contact', many: 'Contacts' },\n companies: { one: 'company', many: 'Companies' },\n deals: { one: 'deal', many: 'Deals' },\n}\n\n/** What a target site may do with a shared record. Read-only unless a grant says edit. */\nexport type CrmShareAccess = 'read' | 'edit'\n\nexport function isCrmShareAccess(value: unknown): value is CrmShareAccess {\n return value === 'read' || value === 'edit'\n}\n\n/** Where a grant came from. */\nexport type CrmShareSource = 'manual' | 'rule'\n\n/** One grant on one record. */\nexport interface CrmShareGrant {\n source: CrmShareSource\n /** The sites it shares with: `['org']` for all sites, else host tokens. */\n tokens: ScopeToken[]\n access: CrmShareAccess\n /** The rule behind a `rule` grant. */\n ruleId?: string\n /** Who shared it by hand, as they were named at the time. */\n byUid?: string\n byName?: string\n /** When the grant was made. */\n atMs: number\n}\n\n/** `sharing` on a record — see the module note. */\nexport interface CrmRecordSharing {\n /** Keyed by {@link manualGrantKey} or {@link ruleGrantKey}. */\n grants: Record<string, CrmShareGrant>\n /** Every token any grant names — the record's shares, however held. */\n tokens: ScopeToken[]\n /** The tokens the grants ADDED to `visibleTo` — see `heldScopeTokens`. */\n added: ScopeToken[]\n /** The rules holding a grant here, so a rule's removal can find its records. */\n ruleIds: string[]\n /**\n * The held tokens of a record shared with all sites, and nothing\n * otherwise. `array-contains` on it answers \"is this record shared with\n * everyone AND held by site X\" — the one question `added` alone cannot,\n * since a query takes a single array clause.\n */\n orgWideHeldBy: ScopeToken[]\n}\n\n/** The record field its grants live under — the core's name for it. */\nexport const CRM_SHARING_FIELD = SCOPE_GRANTS_FIELD\n\n/** The record field naming who may WRITE it, present only while it is shared. */\nexport const CRM_WRITE_SCOPE_FIELD = 'writeTo'\n\n/** The array field a rule's removal queries. */\nexport const CRM_SHARING_RULE_IDS_PATH = `${CRM_SHARING_FIELD}.ruleIds`\n\n/** A hand share is one grant per target, so each is unshared on its own. */\nexport function manualGrantKey(token: string): string {\n const parsed = parseScopeToken(token)\n if (!parsed) throw new Error(`not a scope token: ${token}`)\n return parsed.kind === 'org' ? 'manual_org' : `manual_${parsed.hostId}`\n}\n\nexport function ruleGrantKey(ruleId: string): string {\n return `rule_${ruleId}`\n}\n\nfunction tokenList(raw: unknown): ScopeToken[] {\n if (!Array.isArray(raw)) return []\n return [...new Set(raw.filter(isScopeToken))]\n}\n\nfunction readGrant(raw: unknown): CrmShareGrant | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const value = raw as Record<string, unknown>\n const tokens = tokenList(value['tokens'])\n if (!tokens.length) return null\n const source = value['source'] === 'rule' ? 'rule' : value['source'] === 'manual' ? 'manual' : null\n if (!source) return null\n const ruleId = typeof value['ruleId'] === 'string' ? value['ruleId'] : ''\n if (source === 'rule' && !ruleId) return null\n const byUid = typeof value['byUid'] === 'string' ? value['byUid'] : ''\n const byName = typeof value['byName'] === 'string' ? value['byName'] : ''\n const atMs = typeof value['atMs'] === 'number' && Number.isFinite(value['atMs']) ? value['atMs'] : 0\n return {\n source,\n tokens,\n access: value['access'] === 'edit' ? 'edit' : 'read',\n ...(ruleId ? { ruleId } : {}),\n ...(byUid ? { byUid } : {}),\n ...(byName ? { byName } : {}),\n atMs,\n }\n}\n\n/** A record's grants, cleaned; `{}` for a record never shared. */\nexport function readRecordGrants(\n record: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, CrmShareGrant> {\n const sharing = (record ?? {})[CRM_SHARING_FIELD]\n if (!sharing || typeof sharing !== 'object' || Array.isArray(sharing)) return {}\n const grants = (sharing as Record<string, unknown>)['grants']\n if (!grants || typeof grants !== 'object' || Array.isArray(grants)) return {}\n const out: Record<string, CrmShareGrant> = {}\n for (const [key, raw] of Object.entries(grants as Record<string, unknown>)) {\n const grant = readGrant(raw)\n if (grant) out[key] = grant\n }\n return out\n}\n\n/*==========================================\n * THE PLAN: grants in, the three fields out.\n *=========================================*/\n\n/**\n * The sites that captured or created the record. Their tokens are HELD,\n * whatever `added` says: a person shared to site B and later captured on B\n * is B's own from then on, and unsharing must not hide them from B.\n */\nexport function recordHolderHostIds(record: Readonly<Record<string, unknown>>): string[] {\n const ids = new Set<string>()\n const hostId = record['hostId']\n if (typeof hostId === 'string' && hostId) ids.add(hostId)\n const captured = record['capturedByHostIds']\n if (Array.isArray(captured)) {\n for (const id of captured) if (typeof id === 'string' && id) ids.add(id)\n }\n return [...ids]\n}\n\n/**\n * The record's own scope: `visibleTo` less what grants added, its capturing\n * sites kept — the core's `heldScopeTokens`, as scope tokens.\n */\nexport function recordHeldScope(record: Readonly<Record<string, unknown>>): ScopeToken[] {\n return tokenList(heldScopeTokens(record))\n}\n\nexport interface CrmSharingPlan {\n visibleTo: ScopeToken[]\n /** `null` removes the field: an unshared record writes by `visibleTo`. */\n writeTo: ScopeToken[] | null\n /** `null` removes the field. */\n sharing: CrmRecordSharing | null\n}\n\n/**\n * The fields a record carries under `grants`.\n *\n * A grant naming a token the record already holds adds nothing, and a\n * record held org-wide adds nothing at all — every site sees it already, so\n * a share there would only make a later unshare look like it hid something.\n * The token order is the held scope first and the additions after it, so an\n * unchanged record plans the `visibleTo` it has.\n */\nexport function planRecordSharing(\n record: Readonly<Record<string, unknown>>,\n grants: Readonly<Record<string, CrmShareGrant>>,\n): CrmSharingPlan {\n const held = recordHeldScope(record)\n const entries = Object.entries(grants)\n if (!entries.length) return { visibleTo: held, writeTo: null, sharing: null }\n const heldSet = new Set<string>(held)\n const heldOrgWide = heldSet.has(ORG_SCOPE_TOKEN)\n const tokens: ScopeToken[] = []\n const editTokens: ScopeToken[] = []\n const ruleIds: string[] = []\n for (const [, grant] of entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {\n for (const token of grant.tokens) {\n if (!tokens.includes(token)) tokens.push(token)\n if (grant.access === 'edit' && !editTokens.includes(token)) editTokens.push(token)\n }\n if (grant.source === 'rule' && grant.ruleId && !ruleIds.includes(grant.ruleId)) {\n ruleIds.push(grant.ruleId)\n }\n }\n const added = heldOrgWide ? [] : tokens.filter((token) => !heldSet.has(token))\n const writeTo = [...held]\n for (const token of editTokens) if (!writeTo.includes(token)) writeTo.push(token)\n return {\n visibleTo: [...held, ...added],\n writeTo,\n sharing: {\n grants: Object.fromEntries(entries),\n tokens,\n added,\n ruleIds,\n orgWideHeldBy: added.includes(ORG_SCOPE_TOKEN) ? [...held] : [],\n },\n }\n}\n\nconst sameList = (a: readonly unknown[] | null | undefined, b: readonly unknown[] | null | undefined) =>\n (a ?? null) === null\n ? (b ?? null) === null\n : (b ?? null) !== null && a!.length === b!.length && a!.every((entry, at) => entry === b![at])\n\nconst sameGrant = (a: CrmShareGrant | undefined, b: CrmShareGrant | undefined) =>\n !!a &&\n !!b &&\n a.source === b.source &&\n a.access === b.access &&\n (a.ruleId ?? '') === (b.ruleId ?? '') &&\n (a.byUid ?? '') === (b.byUid ?? '') &&\n (a.byName ?? '') === (b.byName ?? '') &&\n a.atMs === b.atMs &&\n sameList(a.tokens, b.tokens)\n\n/** Whether writing `plan` would change what the record stores. */\nexport function sharingPlanChanges(\n record: Readonly<Record<string, unknown>>,\n plan: CrmSharingPlan,\n): boolean {\n if (!sameList(tokenList(record['visibleTo']), plan.visibleTo)) return true\n const storedWrite = record[CRM_WRITE_SCOPE_FIELD]\n if (!sameList(Array.isArray(storedWrite) ? tokenList(storedWrite) : null, plan.writeTo)) return true\n const stored = record[CRM_SHARING_FIELD]\n if (!plan.sharing) return stored !== undefined && stored !== null\n const grants = readRecordGrants(record)\n const keys = Object.keys(plan.sharing.grants)\n if (keys.length !== Object.keys(grants).length) return true\n if (!keys.every((key) => sameGrant(grants[key], plan.sharing!.grants[key]))) return true\n const sharing = (stored ?? {}) as Record<string, unknown>\n return (\n !sameList(tokenList(sharing['tokens']), plan.sharing.tokens) ||\n !sameList(tokenList(sharing['added']), plan.sharing.added) ||\n !sameList(\n Array.isArray(sharing['ruleIds']) ? (sharing['ruleIds'] as unknown[]) : [],\n plan.sharing.ruleIds,\n ) ||\n !sameList(tokenList(sharing['orgWideHeldBy']), plan.sharing.orgWideHeldBy)\n )\n}\n\n/*==========================================\n * SHARING RULES: `crm.sharingRules` on the org document.\n *=========================================*/\n\n/** The org document path the rules are stored under. */\nexport const CRM_SHARING_RULES_PATH = 'crm.sharingRules'\n\n/** A workspace keeps at most this many. */\nexport const CRM_SHARING_RULES_MAX = 20\n\n/** At most this many values per criterion. */\nexport const CRM_SHARING_CRITERION_MAX = 20\n\n/**\n * Which records a rule matches, beyond the object and the capturing site.\n * AND across the keys, OR within one; an absent or empty key matches\n * everything.\n */\nexport interface CrmSharingCriteria {\n /** A lead's or a contact's lead source, by label. */\n leadSources?: string[]\n /**\n * A lead's or a contact's lead source DIRECTION — `inbound` or `outbound`,\n * the Lead source picklist's groups (AGL-3511). Expanded through the org's\n * list when the rule is evaluated, so a value moved between groups moves\n * the records that hold it.\n */\n leadSourceGroups?: string[]\n /** Any of these tags (lead, contact, company). */\n tags?: string[]\n /** Filed under any of these campaigns (lead, contact). */\n campaignIds?: string[]\n /** A lead's status, a contact's lifecycle stage, or a deal's stage id. */\n stages?: string[]\n /** Owned by any of these members. */\n ownerUids?: string[]\n}\n\nexport const CRM_SHARING_CRITERIA_KEYS = [\n 'leadSources',\n 'leadSourceGroups',\n 'tags',\n 'campaignIds',\n 'stages',\n 'ownerUids',\n] as const satisfies readonly (keyof CrmSharingCriteria)[]\n\n/** Which criteria an object's records can answer. */\nexport const CRM_SHARING_CRITERIA_FOR: Readonly<\n Record<CrmSharingObject, readonly (keyof CrmSharingCriteria)[]>\n> = {\n leads: ['leadSources', 'leadSourceGroups', 'tags', 'campaignIds', 'stages', 'ownerUids'],\n contacts: ['leadSources', 'leadSourceGroups', 'tags', 'campaignIds', 'stages', 'ownerUids'],\n companies: ['tags', 'ownerUids'],\n deals: ['stages', 'ownerUids'],\n}\n\n/** Where a rule's recompute stands. */\nexport interface CrmSharingRuleRun {\n status: 'running' | 'done' | 'failed'\n /** What the run does: apply the rule everywhere, or take it away. */\n mode: 'apply' | 'remove'\n /** Records examined. */\n processed: number\n /** Records whose sharing changed. */\n changed: number\n /** The last record id an `apply` run examined, to continue after. */\n cursor?: string | null\n startedAtMs: number\n finishedAtMs?: number\n error?: string\n}\n\nexport interface CrmSharingRule {\n id: string\n name: string\n object: CrmSharingObject\n /** Off keeps the rule and takes away what it granted. */\n enabled: boolean\n /** Records captured or created on any of these sites; empty is every site. */\n sourceHostIds: string[]\n criteria: CrmSharingCriteria\n /** `['org']` for all sites, including sites added later; else host tokens. */\n targets: ScopeToken[]\n access: CrmShareAccess\n /** Being deleted: its grants are being removed, and it matches nothing. */\n deleting?: boolean\n createdByUid?: string\n createdAtMs: number\n updatedAtMs: number\n run?: CrmSharingRuleRun\n}\n\nconst RULE_ID = /^[A-Za-z0-9_-]{1,64}$/\n\nfunction stringList(raw: unknown, max = CRM_SHARING_CRITERION_MAX): string[] {\n if (!Array.isArray(raw)) return []\n const out: string[] = []\n for (const entry of raw) {\n const value = String(entry ?? '').trim().slice(0, 200)\n if (value && !out.includes(value)) out.push(value)\n if (out.length >= max) break\n }\n return out\n}\n\n/** One rule as stored, cleaned, or null when it cannot be applied. */\nexport function readCrmSharingRule(raw: unknown): CrmSharingRule | null {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const value = raw as Record<string, unknown>\n const id = String(value['id'] ?? '')\n if (!RULE_ID.test(id)) return null\n if (!isCrmSharingObject(value['object'])) return null\n const targets = normalizeShareTargets(tokenList(value['targets']))\n if (!targets) return null\n const rawCriteria = (value['criteria'] ?? {}) as Record<string, unknown>\n const criteria: CrmSharingCriteria = {}\n for (const key of CRM_SHARING_CRITERIA_FOR[value['object']]) {\n let list = stringList(rawCriteria[key])\n // A direction is one of the picklist's groups, never free text.\n if (key === 'leadSourceGroups') list = list.filter(isLeadSourceDirection)\n if (list.length) criteria[key] = key === 'tags' ? list.map((tag) => tag.toLowerCase()) : list\n }\n const run = value['run'] as Record<string, unknown> | undefined\n return {\n id,\n name: String(value['name'] ?? '').trim().slice(0, 120) || 'Sharing rule',\n object: value['object'],\n enabled: value['enabled'] !== false,\n sourceHostIds: stringList(value['sourceHostIds'], MAX_SCOPE_HOSTS),\n criteria,\n targets,\n access: value['access'] === 'edit' ? 'edit' : 'read',\n ...(value['deleting'] === true ? { deleting: true } : {}),\n ...(typeof value['createdByUid'] === 'string' ? { createdByUid: value['createdByUid'] } : {}),\n createdAtMs: Number(value['createdAtMs']) || 0,\n updatedAtMs: Number(value['updatedAtMs']) || 0,\n ...(run && typeof run === 'object'\n ? {\n run: {\n status:\n run['status'] === 'done' || run['status'] === 'failed' ? run['status'] : 'running',\n mode: run['mode'] === 'remove' ? 'remove' : 'apply',\n processed: Number(run['processed']) || 0,\n changed: Number(run['changed']) || 0,\n cursor: typeof run['cursor'] === 'string' ? run['cursor'] : null,\n startedAtMs: Number(run['startedAtMs']) || 0,\n ...(typeof run['finishedAtMs'] === 'number' ? { finishedAtMs: run['finishedAtMs'] } : {}),\n ...(typeof run['error'] === 'string' ? { error: run['error'] } : {}),\n },\n }\n : {}),\n }\n}\n\n/** The org's sharing rules, in their stored order. */\nexport function readCrmSharingRules(\n org: Readonly<Record<string, unknown>> | null | undefined,\n): CrmSharingRule[] {\n const crm = (org ?? {})['crm']\n const raw =\n crm && typeof crm === 'object' && !Array.isArray(crm)\n ? (crm as Record<string, unknown>)['sharingRules']\n : undefined\n if (!Array.isArray(raw)) return []\n const out: CrmSharingRule[] = []\n for (const entry of raw) {\n const rule = readCrmSharingRule(entry)\n if (rule && !out.some((existing) => existing.id === rule.id)) out.push(rule)\n }\n return out\n}\n\n/** The rules that grant anything now: enabled, and not being deleted. */\nexport function activeSharingRules(\n rules: readonly CrmSharingRule[],\n object?: CrmSharingObject,\n): CrmSharingRule[] {\n return rules.filter(\n (rule) => rule.enabled && !rule.deleting && (!object || rule.object === object),\n )\n}\n\n/**\n * A share's targets, cleaned for storage: `['org']` when all sites are\n * named, else the host tokens, capped. `null` when nothing is left.\n */\nexport function normalizeShareTargets(tokens: readonly string[]): ScopeToken[] | null {\n const clean = tokenList(tokens)\n if (!clean.length) return null\n if (clean.includes(ORG_SCOPE_TOKEN)) return [ORG_SCOPE_TOKEN]\n if (clean.length > MAX_SCOPE_HOSTS) return null\n return clean\n}\n\n/** Targets from a request: `'all'`, or a list of host ids. */\nexport function shareTargetsFrom(input: unknown): ScopeToken[] | null {\n if (input === 'all') return [ORG_SCOPE_TOKEN]\n if (!Array.isArray(input)) return null\n return normalizeShareTargets(\n input\n .map((id) => String(id ?? '').trim())\n .filter((id) => id && !id.includes('/') && !id.includes(':'))\n .map(hostScopeToken),\n )\n}\n\n/*==========================================\n * WHAT A RULE READS OFF A RECORD.\n *=========================================*/\n\nexport interface CrmSharingFacts {\n hostIds: string[]\n leadSources: string[]\n tags: string[]\n campaignIds: string[]\n stages: string[]\n ownerUids: string[]\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\nconst strs = (value: unknown): string[] =>\n Array.isArray(value) ? value.map(str).filter(Boolean) : []\n\n/**\n * The facts a rule's criteria are matched against. A contact keeps each\n * holder's working fields in its own facet, so a contact answers with the\n * union of its facets: a rule about \"contacts tagged vip\" matches a person\n * any holder tagged.\n */\nexport function crmSharingFacts(\n object: CrmSharingObject,\n record: Readonly<Record<string, unknown>>,\n): CrmSharingFacts {\n const facts: CrmSharingFacts = {\n hostIds: recordHolderHostIds(record),\n leadSources: [],\n tags: [],\n campaignIds: [],\n stages: [],\n ownerUids: [],\n }\n const take = (source: Readonly<Record<string, unknown>>, stageField: string | null) => {\n const leadSource = str(source['leadSource'])\n if (leadSource) facts.leadSources.push(leadSource)\n facts.tags.push(...strs(source['tags']).map((tag) => tag.toLowerCase()))\n facts.campaignIds.push(...strs(source['campaignIds']))\n const stage = stageField ? str(source[stageField]) : ''\n if (stage) facts.stages.push(stage)\n const owner = str(source['ownerUid'])\n if (owner) facts.ownerUids.push(owner)\n }\n if (object === 'leads') take(record, 'status')\n else if (object === 'companies') take(record, null)\n else if (object === 'deals') take(record, 'stageId')\n else {\n const facets = record['facets']\n if (facets && typeof facets === 'object' && !Array.isArray(facets)) {\n for (const facet of Object.values(facets as Record<string, unknown>)) {\n if (facet && typeof facet === 'object' && !Array.isArray(facet)) {\n take(facet as Record<string, unknown>, 'lifecycleStage')\n }\n }\n }\n }\n // A lead with no status is New, as every lead reader answers it.\n if (object === 'leads' && !facts.stages.length) facts.stages.push('new')\n return facts\n}\n\nconst anyOf = (wanted: readonly string[] | undefined, have: readonly string[], fold = false) => {\n if (!wanted?.length) return true\n const set = new Set(fold ? have.map((value) => value.toLowerCase()) : have)\n return wanted.some((value) => set.has(fold ? value.toLowerCase() : value))\n}\n\n/**\n * What a rule is evaluated against beyond the record: the org's Lead source\n * list, which a direction criterion is expanded through (AGL-3511). Absent,\n * the standard values alone answer.\n */\nexport interface CrmSharingContext {\n leadSources?: CrmPicklist\n}\n\n/** Whether any active rule for `object` asks a lead source direction — whether the list must be read. */\nexport function sharingRulesAskLeadSourceDirection(\n rules: readonly CrmSharingRule[],\n object: CrmSharingObject,\n): boolean {\n return activeSharingRules(rules, object).some((rule) => Boolean(rule.criteria.leadSourceGroups?.length))\n}\n\n/** Whether a rule shares this record. */\nexport function crmSharingRuleMatches(\n rule: CrmSharingRule,\n object: CrmSharingObject,\n record: Readonly<Record<string, unknown>>,\n context: CrmSharingContext = {},\n): boolean {\n if (!rule.enabled || rule.deleting || rule.object !== object) return false\n const facts = crmSharingFacts(object, record)\n if (rule.sourceHostIds.length && !facts.hostIds.some((id) => rule.sourceHostIds.includes(id))) {\n return false\n }\n const { criteria } = rule\n const directions = criteria.leadSourceGroups?.length\n ? facts.leadSources\n .map((label) => leadSourceDirectionOf(context.leadSources ?? STANDARD_LEAD_SOURCES, label))\n .filter((direction): direction is NonNullable<typeof direction> => direction !== null)\n : []\n return (\n anyOf(criteria.leadSources, facts.leadSources, true) &&\n anyOf(criteria.leadSourceGroups, directions) &&\n anyOf(criteria.tags, facts.tags, true) &&\n anyOf(criteria.campaignIds, facts.campaignIds) &&\n anyOf(criteria.stages, facts.stages) &&\n anyOf(criteria.ownerUids, facts.ownerUids)\n )\n}\n\n/**\n * The grants a record should carry: its hand shares as they are, and one\n * grant per active rule that matches it now. A rule grant already present\n * keeps its date, so re-evaluating an unchanged record writes nothing.\n *\n * A rule grant whose rule is not among `rules` is dropped — the caller\n * passes every active rule of the org, so a rule absent from them has been\n * switched off or deleted.\n */\nexport function evaluateRecordGrants(\n object: CrmSharingObject,\n record: Readonly<Record<string, unknown>>,\n rules: readonly CrmSharingRule[],\n atMs: number,\n context: CrmSharingContext = {},\n): Record<string, CrmShareGrant> {\n const current = readRecordGrants(record)\n const next: Record<string, CrmShareGrant> = {}\n for (const [key, grant] of Object.entries(current)) {\n if (grant.source === 'manual') next[key] = grant\n }\n for (const rule of activeSharingRules(rules, object)) {\n if (!crmSharingRuleMatches(rule, object, record, context)) continue\n const key = ruleGrantKey(rule.id)\n const before = current[key]\n next[key] = {\n source: 'rule',\n ruleId: rule.id,\n tokens: [...rule.targets],\n access: rule.access,\n atMs: before && sameList(before.tokens, rule.targets) && before.access === rule.access\n ? before.atMs\n : atMs,\n }\n }\n return next\n}\n\n/** The hand shares after sharing with `targets`, by `by`. */\nexport function withManualShares(\n grants: Readonly<Record<string, CrmShareGrant>>,\n targets: readonly ScopeToken[],\n access: CrmShareAccess,\n by: { uid: string; name?: string | null },\n atMs: number,\n): Record<string, CrmShareGrant> {\n const next = { ...grants }\n for (const token of targets) {\n const name = String(by.name ?? '').trim()\n next[manualGrantKey(token)] = {\n source: 'manual',\n tokens: [token],\n access,\n byUid: by.uid,\n ...(name ? { byName: name } : {}),\n atMs,\n }\n }\n return next\n}\n\n/** The hand shares after unsharing `targets`. Rule grants are the rule's to take away. */\nexport function withoutManualShares(\n grants: Readonly<Record<string, CrmShareGrant>>,\n targets: readonly string[],\n): Record<string, CrmShareGrant> {\n const drop = new Set(targets.filter(isScopeToken).map(manualGrantKey))\n return Object.fromEntries(Object.entries(grants).filter(([key]) => !drop.has(key)))\n}\n\n/*==========================================\n * WHAT A READER SEES.\n *=========================================*/\n\n/**\n * Whether a caller holding `tokens` may write the record: `writeTo` on a\n * shared record, else `visibleTo` — the Firestore rules' own predicate.\n */\nexport function crmRecordWritableBy(\n record: Readonly<Record<string, unknown>>,\n tokens: readonly string[],\n): boolean {\n const writeTo = record[CRM_WRITE_SCOPE_FIELD]\n const scope = Array.isArray(writeTo) ? writeTo : record['visibleTo']\n if (!Array.isArray(scope) || !tokens.length) return false\n return scope.some((token) => tokens.includes(token as string))\n}\n\n/** How a record reached a site that sees it only through a share. */\nexport interface CrmShareChip {\n source: CrmShareSource\n access: CrmShareAccess\n /** The member who shared it by hand. */\n byName?: string\n /** The rule that shared it. */\n ruleId?: string\n /** Shared with every site. */\n allSites: boolean\n}\n\n/**\n * Why a site seeing this record sees it, when the answer is a share — or\n * `null` when the site holds it (or cannot see it at all). `hostIds` are\n * the viewing group's sites; at the organization level there is none.\n */\nexport function crmShareChipFor(\n record: Readonly<Record<string, unknown>> | null | undefined,\n hostIds: readonly string[],\n): CrmShareChip | null {\n if (!record || !hostIds.length) return null\n const held = recordHeldScope(record)\n if (held.includes(ORG_SCOPE_TOKEN)) return null\n const viewing = new Set(hostIds.map(hostScopeToken))\n if (held.some((token) => viewing.has(token))) return null\n const grants = Object.values(readRecordGrants(record))\n const reaching = grants.filter((grant) =>\n grant.tokens.some((token) => token === ORG_SCOPE_TOKEN || viewing.has(token)),\n )\n if (!reaching.length) return null\n // A hand share names a person, so it leads; an edit grant outranks read.\n const pick =\n reaching.find((grant) => grant.source === 'manual' && grant.access === 'edit') ??\n reaching.find((grant) => grant.source === 'manual') ??\n reaching.find((grant) => grant.access === 'edit') ??\n reaching[0]\n return {\n source: pick.source,\n access: reaching.some((grant) => grant.access === 'edit') ? 'edit' : 'read',\n ...(pick.byName ? { byName: pick.byName } : {}),\n ...(pick.ruleId ? { ruleId: pick.ruleId } : {}),\n allSites: pick.tokens.includes(ORG_SCOPE_TOKEN),\n }\n}\n\n/** The chip's words: \"Shared by Dana\", \"Shared by rule Brand A leads\". */\nexport function crmShareChipLabel(\n chip: CrmShareChip,\n rules: readonly Pick<CrmSharingRule, 'id' | 'name'>[] = [],\n): string {\n if (chip.source === 'manual') return chip.byName ? `Shared by ${chip.byName}` : 'Shared by hand'\n const rule = rules.find((entry) => entry.id === chip.ruleId)\n return rule ? `Shared by rule ${rule.name}` : 'Shared by a rule'\n}\n\n/** One line of a record's Sharing card. */\nexport type CrmSharingLine =\n | { kind: 'held'; hostIds: string[]; allSites: boolean }\n | {\n kind: 'manual'\n key: string\n token: ScopeToken\n access: CrmShareAccess\n byName?: string\n atMs: number\n }\n | { kind: 'rule'; key: string; ruleId: string; tokens: ScopeToken[]; access: CrmShareAccess; atMs: number }\n\n/**\n * Where a record is visible and why: the sites that hold it (its consent\n * group or its creating site), then one line per hand share, then one per\n * rule. The held line is never unsharable; a hand share is, in one click.\n */\nexport function describeRecordSharing(\n record: Readonly<Record<string, unknown>> | null | undefined,\n): CrmSharingLine[] {\n if (!record) return []\n const held = recordHeldScope(record)\n const lines: CrmSharingLine[] = [\n { kind: 'held', hostIds: hostIdsFromScope(held), allSites: held.includes(ORG_SCOPE_TOKEN) },\n ]\n const grants = Object.entries(readRecordGrants(record))\n for (const [key, grant] of grants) {\n if (grant.source !== 'manual') continue\n lines.push({\n kind: 'manual',\n key,\n token: grant.tokens[0],\n access: grant.access,\n ...(grant.byName ? { byName: grant.byName } : {}),\n atMs: grant.atMs,\n })\n }\n for (const [key, grant] of grants) {\n if (grant.source !== 'rule' || !grant.ruleId) continue\n lines.push({\n kind: 'rule',\n key,\n ruleId: grant.ruleId,\n tokens: grant.tokens,\n access: grant.access,\n atMs: grant.atMs,\n })\n }\n return lines\n}\n\n/** \"All sites\", a site's name, or \"N sites\". */\nexport function describeShareTargets(\n tokens: readonly string[],\n siteName: (hostId: string) => string,\n): string {\n if (tokens.includes(ORG_SCOPE_TOKEN)) return 'All sites'\n const ids = hostIdsFromScope(tokens)\n if (ids.length === 1) return siteName(ids[0])\n if (ids.length <= 3) return ids.map(siteName).join(', ')\n return `${ids.length} sites`\n}\n\n/** A rule as one sentence of its \"which records\" half. */\nexport function describeSharingRuleScope(\n rule: Pick<CrmSharingRule, 'object' | 'sourceHostIds' | 'criteria'>,\n siteName: (hostId: string) => string,\n labels: { owner?: (uid: string) => string; stage?: (id: string) => string } = {},\n): string {\n const parts: string[] = [CRM_SHARING_OBJECT_LABELS[rule.object].many]\n if (rule.sourceHostIds.length) {\n parts.push(`captured on ${rule.sourceHostIds.map(siteName).join(' or ')}`)\n }\n const { criteria } = rule\n if (criteria.leadSources?.length) parts.push(`from ${criteria.leadSources.join(' or ')}`)\n if (criteria.leadSourceGroups?.length) {\n const directions = criteria.leadSourceGroups.map((group) =>\n isLeadSourceDirection(group) ? LEAD_SOURCE_DIRECTION_LABELS[group] : group,\n )\n parts.push(`${directions.join(' or ')} by lead source`)\n }\n if (criteria.tags?.length) parts.push(`tagged ${criteria.tags.join(' or ')}`)\n if (criteria.campaignIds?.length) {\n parts.push(`in ${criteria.campaignIds.length === 1 ? 'a campaign' : `${criteria.campaignIds.length} campaigns`}`)\n }\n if (criteria.stages?.length) {\n parts.push(`at ${criteria.stages.map((id) => labels.stage?.(id) ?? id).join(' or ')}`)\n }\n if (criteria.ownerUids?.length) {\n parts.push(`owned by ${criteria.ownerUids.map((uid) => labels.owner?.(uid) ?? uid).join(' or ')}`)\n }\n return parts.join(', ')\n}\n\n/*==========================================\n * THE ROUTE.\n *=========================================*/\n\n/** `POST /api/crm/sharing` — every sharing act, by `action`. */\nexport const CRM_SHARING_ROUTE = 'crm/sharing' as const\n\nexport const crmSharingRouteUrl = (): string => `/api/${CRM_SHARING_ROUTE}`\n\n/** Records one share or unshare may name. */\nexport const CRM_SHARING_IDS_MAX = 200\n\nexport type CrmSharingAction =\n | 'sites'\n | 'share'\n | 'unshare'\n | 'evaluate'\n | 'rule-save'\n | 'rule-delete'\n | 'rule-continue'\n\nexport interface CrmSharingSite {\n id: string\n name: string\n}\n\nexport interface CrmSharingResponse {\n ok: true\n /** `sites`: the org's sites, by name. */\n sites?: CrmSharingSite[]\n /** `share`/`unshare`/`evaluate`: records whose sharing changed. */\n changed?: number\n /** Records named that could not be found. */\n missing?: number\n /** The rule a rule action saved or ran. */\n rule?: CrmSharingRule | null\n /** Whether a rule run has more to do (`rule-continue` again). */\n more?: boolean\n}\n"],"names":["heldScopeTokens","hostIdsFromScope","hostScopeToken","isScopeToken","MAX_SCOPE_HOSTS","ORG_SCOPE_TOKEN","parseScopeToken","SCOPE_GRANTS_FIELD","isLeadSourceDirection","LEAD_SOURCE_DIRECTION_LABELS","leadSourceDirectionOf","STANDARD_LEAD_SOURCES","CRM_SHARING_OBJECTS","isCrmSharingObject","value","includes","CRM_SHARING_OBJECT_LABELS","leads","one","many","contacts","companies","deals","isCrmShareAccess","CRM_SHARING_FIELD","CRM_WRITE_SCOPE_FIELD","CRM_SHARING_RULE_IDS_PATH","manualGrantKey","token","parsed","Error","kind","hostId","ruleGrantKey","ruleId","tokenList","raw","Array","isArray","Set","filter","readGrant","tokens","length","source","byUid","byName","atMs","Number","isFinite","access","readRecordGrants","record","sharing","grants","out","key","Object","entries","grant","recordHolderHostIds","ids","add","captured","id","recordHeldScope","planRecordSharing","held","visibleTo","writeTo","heldSet","heldOrgWide","has","editTokens","ruleIds","sort","a","b","push","added","fromEntries","orgWideHeldBy","sameList","every","entry","at","sameGrant","sharingPlanChanges","plan","storedWrite","stored","undefined","keys","CRM_SHARING_RULES_PATH","CRM_SHARING_RULES_MAX","CRM_SHARING_CRITERION_MAX","CRM_SHARING_CRITERIA_KEYS","CRM_SHARING_CRITERIA_FOR","RULE_ID","stringList","max","String","trim","slice","readCrmSharingRule","test","targets","normalizeShareTargets","rawCriteria","criteria","list","map","tag","toLowerCase","run","name","object","enabled","sourceHostIds","deleting","createdByUid","createdAtMs","updatedAtMs","status","mode","processed","changed","cursor","startedAtMs","finishedAtMs","error","readCrmSharingRules","org","crm","rule","some","existing","activeSharingRules","rules","clean","shareTargetsFrom","input","str","strs","Boolean","crmSharingFacts","facts","hostIds","leadSources","tags","campaignIds","stages","ownerUids","take","stageField","leadSource","stage","owner","facets","facet","values","anyOf","wanted","have","fold","set","sharingRulesAskLeadSourceDirection","leadSourceGroups","crmSharingRuleMatches","context","directions","label","direction","evaluateRecordGrants","current","next","before","withManualShares","by","uid","withoutManualShares","drop","crmRecordWritableBy","scope","crmShareChipFor","reaching","viewing","pick","find","allSites","crmShareChipLabel","chip","describeRecordSharing","lines","describeShareTargets","siteName","join","describeSharingRuleScope","labels","parts","group","CRM_SHARING_ROUTE","crmSharingRouteUrl","CRM_SHARING_IDS_MAX"],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A task's reminder at its own time (AGL-2659).
|
|
19
|
+
*
|
|
20
|
+
* The daily digest says once a morning what is late and what is due today;
|
|
21
|
+
* nothing said "the 3:00 call is at 3:00". A task now carries `remindAtMs`
|
|
22
|
+
* — the due time unless a person moved it, `null` for none — and an hourly
|
|
23
|
+
* route reads every open task whose reminder has come due and tells the
|
|
24
|
+
* assignee once, in the console and by mail. Every judgment here is pure so
|
|
25
|
+
* the three writers of the field (the task routes, the REST resource, the
|
|
26
|
+
* drawer) and the route that reads it agree on what a reminder is.
|
|
27
|
+
*
|
|
28
|
+
* ## Which reminder a save leaves
|
|
29
|
+
*
|
|
30
|
+
* A caller that SAYS what the reminder is gets what it said. A caller that
|
|
31
|
+
* says nothing gets the rule: a new task's reminder is its due time, and an
|
|
32
|
+
* edit that moves the due date moves a reminder that still sat on the old
|
|
33
|
+
* due time — the reminder was never a choice of its own, so it follows —
|
|
34
|
+
* while a reminder somebody set to a time of their own stays where they put
|
|
35
|
+
* it. The one thing an edit never does is invent a reminder for a task that
|
|
36
|
+
* has none.
|
|
37
|
+
*/
|
|
38
|
+
import type { CrmTask } from '@aglyn/aglyn/app-utils/crm';
|
|
39
|
+
/**
|
|
40
|
+
* The most due reminders one org's sweep reads per run.
|
|
41
|
+
*
|
|
42
|
+
* NEWEST FIRST, which is the whole reason a ceiling is safe here. A sent
|
|
43
|
+
* reminder keeps its `remindAtMs` — the drawer shows when it fired — so the
|
|
44
|
+
* range "reminders up to now" holds every reminder an open task ever had,
|
|
45
|
+
* and a window of the OLDEST five hundred would fill with ones already sent
|
|
46
|
+
* and never reach a new one. The newest five hundred always begin with what
|
|
47
|
+
* fell due since the last run; the sent ones sit behind them.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CRM_TASK_REMINDER_CEILING = 500;
|
|
50
|
+
/** The two fields the rule reads off a stored task, or off nothing on create. */
|
|
51
|
+
export interface CrmTaskReminderState {
|
|
52
|
+
dueAtMs: number | null;
|
|
53
|
+
remindAtMs: number | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The reminder a save leaves on the task — see the module comment.
|
|
57
|
+
*
|
|
58
|
+
* `remindAtMs` is what the caller SAID: a time, `null` for "no reminder",
|
|
59
|
+
* `undefined` for nothing said. `previous` is the stored task, or `null`
|
|
60
|
+
* when the task is being created. A stored task from before reminders
|
|
61
|
+
* existed carries no `remindAtMs` at all; it reads as `null` here — no
|
|
62
|
+
* reminder — rather than as one that follows the due date, because a task
|
|
63
|
+
* nobody ever set a reminder on should not start reminding the day its
|
|
64
|
+
* title is corrected.
|
|
65
|
+
*/
|
|
66
|
+
export declare function crmTaskReminderAfterEdit(input: {
|
|
67
|
+
dueAtMs: number | null;
|
|
68
|
+
remindAtMs?: number | null;
|
|
69
|
+
previous: CrmTaskReminderState | null;
|
|
70
|
+
}): number | null;
|
|
71
|
+
/** Whether a task's reminder is still owed: set, and not yet handled. */
|
|
72
|
+
export declare function crmTaskReminderPending(task: Pick<CrmTask, 'remindAtMs' | 'reminderSentAtMs'>): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Whether the runner owes this task a reminder now: open, pending, and the
|
|
75
|
+
* reminder's time has come. The query reads the first and third off the
|
|
76
|
+
* index; the second is a fact about a field's absence, which a query cannot
|
|
77
|
+
* select on, so the runner asks it here over the page it read.
|
|
78
|
+
*/
|
|
79
|
+
export declare function crmTaskReminderDue(task: Pick<CrmTask, 'status' | 'remindAtMs' | 'reminderSentAtMs'>, nowMs: number): boolean;
|
|
80
|
+
/** A due reminder as the runner carries it: the fields a line and a link need. */
|
|
81
|
+
export interface CrmReminderTask extends Pick<CrmTask, 'title' | 'kind' | 'contactId' | 'companyId' | 'dealId'> {
|
|
82
|
+
id: string;
|
|
83
|
+
dueAtMs: number | null;
|
|
84
|
+
remindAtMs: number;
|
|
85
|
+
assigneeUid: string;
|
|
86
|
+
/** The site the task was created on, or `''` for the organization's own. */
|
|
87
|
+
hostId: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The console path a reminder opens: the record the task is for, in the
|
|
91
|
+
* hub, or the tasks list when it is for nobody in particular — the same
|
|
92
|
+
* page the assignment notification opens, in the `/{hostDocId}/rest` shape
|
|
93
|
+
* every host notification uses and the `/org/rest` shape the console
|
|
94
|
+
* rewrites onto the organization's own hub for a task with no site.
|
|
95
|
+
*/
|
|
96
|
+
export declare function crmTaskReminderLink(hostId: string | null, task: Pick<CrmTask, 'contactId' | 'companyId' | 'dealId'>): string;
|
|
97
|
+
/**
|
|
98
|
+
* `Wed, Sep 9, 3:00 PM CDT` — the words a reminder says a time with. The zone
|
|
99
|
+
* is the server's choice, not the reader's, so it is printed: a reader an
|
|
100
|
+
* hour away would otherwise take it for their own clock.
|
|
101
|
+
*/
|
|
102
|
+
export declare function crmTaskReminderWhen(ms: number, timeZone: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* One task as the reminder email lists it: its title and when it is due, or
|
|
105
|
+
* that it has no due date — a reminder can sit on a task with none, at a time
|
|
106
|
+
* the person chose.
|
|
107
|
+
*/
|
|
108
|
+
export declare function crmTaskReminderLine(task: Pick<CrmReminderTask, 'title' | 'dueAtMs'>, timeZone: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* The console notification's body, one per task, which is also what a
|
|
111
|
+
* preview of it shows. It says it is a reminder and names the workspace, so
|
|
112
|
+
* it reads whole without the "Task reminder" title above it.
|
|
113
|
+
*/
|
|
114
|
+
export declare function composeCrmTaskReminderBody(task: Pick<CrmReminderTask, 'title' | 'dueAtMs'>, timeZone: string, workspaceName?: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* One mail per member per run, so five tasks due at nine o'clock are one
|
|
117
|
+
* message and not five. The subject names the task when there is one, and
|
|
118
|
+
* never says a task is due: a reminder can be set ahead of the due time, or
|
|
119
|
+
* on a task with none.
|
|
120
|
+
*/
|
|
121
|
+
export declare function composeCrmTaskReminderSubject(tasks: ReadonlyArray<Pick<CrmReminderTask, 'title'>>): string;
|
|
122
|
+
/** What the reminder email needs from the route besides the tasks. */
|
|
123
|
+
export interface CrmTaskReminderEmailInput {
|
|
124
|
+
tasks: readonly CrmReminderTask[];
|
|
125
|
+
timeZone: string;
|
|
126
|
+
/** The product name the mail reads as — the org's brand when white-labeled. */
|
|
127
|
+
productName: string;
|
|
128
|
+
/**
|
|
129
|
+
* The workspace the tasks are in. The system email shows only the body, so
|
|
130
|
+
* the body names it: a member of two workspaces gets reminders from each.
|
|
131
|
+
*/
|
|
132
|
+
workspaceName?: string;
|
|
133
|
+
/** The console page a task opens on, absolute. */
|
|
134
|
+
taskUrl: (task: CrmReminderTask) => string;
|
|
135
|
+
/** Account settings → Notifications, where the category can be muted. */
|
|
136
|
+
settingsUrl: string;
|
|
137
|
+
/** The brand's support line, already prefixed, or empty. */
|
|
138
|
+
supportLine?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Leave out the closing paragraph and the support line (AGL-3367), for the
|
|
141
|
+
* copy that goes inside a system email: its footer says why the mail came
|
|
142
|
+
* and where to get help, and its body links the settings page.
|
|
143
|
+
*/
|
|
144
|
+
omitClosing?: boolean;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The plain-text email. Text rather than a designed template for the
|
|
148
|
+
* reason the digest gives: the body is a list, and `sendEmail` synthesizes
|
|
149
|
+
* the HTML part that makes its links live. Soonest due first, each task on
|
|
150
|
+
* its own line with its link under it, and the way out last.
|
|
151
|
+
*/
|
|
152
|
+
export declare function composeCrmTaskReminderEmailText(input: CrmTaskReminderEmailInput): string;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* A task's reminder at its own time (AGL-2659).
|
|
18
|
+
*
|
|
19
|
+
* The daily digest says once a morning what is late and what is due today;
|
|
20
|
+
* nothing said "the 3:00 call is at 3:00". A task now carries `remindAtMs`
|
|
21
|
+
* — the due time unless a person moved it, `null` for none — and an hourly
|
|
22
|
+
* route reads every open task whose reminder has come due and tells the
|
|
23
|
+
* assignee once, in the console and by mail. Every judgment here is pure so
|
|
24
|
+
* the three writers of the field (the task routes, the REST resource, the
|
|
25
|
+
* drawer) and the route that reads it agree on what a reminder is.
|
|
26
|
+
*
|
|
27
|
+
* ## Which reminder a save leaves
|
|
28
|
+
*
|
|
29
|
+
* A caller that SAYS what the reminder is gets what it said. A caller that
|
|
30
|
+
* says nothing gets the rule: a new task's reminder is its due time, and an
|
|
31
|
+
* edit that moves the due date moves a reminder that still sat on the old
|
|
32
|
+
* due time — the reminder was never a choice of its own, so it follows —
|
|
33
|
+
* while a reminder somebody set to a time of their own stays where they put
|
|
34
|
+
* it. The one thing an edit never does is invent a reminder for a task that
|
|
35
|
+
* has none.
|
|
36
|
+
*/ /**
|
|
37
|
+
* The most due reminders one org's sweep reads per run.
|
|
38
|
+
*
|
|
39
|
+
* NEWEST FIRST, which is the whole reason a ceiling is safe here. A sent
|
|
40
|
+
* reminder keeps its `remindAtMs` — the drawer shows when it fired — so the
|
|
41
|
+
* range "reminders up to now" holds every reminder an open task ever had,
|
|
42
|
+
* and a window of the OLDEST five hundred would fill with ones already sent
|
|
43
|
+
* and never reach a new one. The newest five hundred always begin with what
|
|
44
|
+
* fell due since the last run; the sent ones sit behind them.
|
|
45
|
+
*/ export const CRM_TASK_REMINDER_CEILING = 500;
|
|
46
|
+
/**
|
|
47
|
+
* The reminder a save leaves on the task — see the module comment.
|
|
48
|
+
*
|
|
49
|
+
* `remindAtMs` is what the caller SAID: a time, `null` for "no reminder",
|
|
50
|
+
* `undefined` for nothing said. `previous` is the stored task, or `null`
|
|
51
|
+
* when the task is being created. A stored task from before reminders
|
|
52
|
+
* existed carries no `remindAtMs` at all; it reads as `null` here — no
|
|
53
|
+
* reminder — rather than as one that follows the due date, because a task
|
|
54
|
+
* nobody ever set a reminder on should not start reminding the day its
|
|
55
|
+
* title is corrected.
|
|
56
|
+
*/ export function crmTaskReminderAfterEdit(input) {
|
|
57
|
+
var _previous_remindAtMs;
|
|
58
|
+
const { dueAtMs, remindAtMs, previous } = input;
|
|
59
|
+
if (remindAtMs !== undefined) return remindAtMs;
|
|
60
|
+
if (!previous) return dueAtMs;
|
|
61
|
+
const followed = typeof previous.remindAtMs === 'number' && previous.remindAtMs === previous.dueAtMs;
|
|
62
|
+
if (followed && dueAtMs !== previous.dueAtMs) return dueAtMs;
|
|
63
|
+
return (_previous_remindAtMs = previous.remindAtMs) != null ? _previous_remindAtMs : null;
|
|
64
|
+
}
|
|
65
|
+
/** Whether a task's reminder is still owed: set, and not yet handled. */ export function crmTaskReminderPending(task) {
|
|
66
|
+
return typeof task.remindAtMs === 'number' && task.reminderSentAtMs === undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether the runner owes this task a reminder now: open, pending, and the
|
|
70
|
+
* reminder's time has come. The query reads the first and third off the
|
|
71
|
+
* index; the second is a fact about a field's absence, which a query cannot
|
|
72
|
+
* select on, so the runner asks it here over the page it read.
|
|
73
|
+
*/ export function crmTaskReminderDue(task, nowMs) {
|
|
74
|
+
return task.status === 'open' && crmTaskReminderPending(task) && task.remindAtMs <= nowMs;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The console path a reminder opens: the record the task is for, in the
|
|
78
|
+
* hub, or the tasks list when it is for nobody in particular — the same
|
|
79
|
+
* page the assignment notification opens, in the `/{hostDocId}/rest` shape
|
|
80
|
+
* every host notification uses and the `/org/rest` shape the console
|
|
81
|
+
* rewrites onto the organization's own hub for a task with no site.
|
|
82
|
+
*/ export function crmTaskReminderLink(hostId, task) {
|
|
83
|
+
const base = hostId ? `/${hostId}/crm` : '/org/crm';
|
|
84
|
+
if (task.contactId) return `${base}/contacts/${encodeURIComponent(task.contactId)}`;
|
|
85
|
+
if (task.dealId) return `${base}/deals/${encodeURIComponent(task.dealId)}`;
|
|
86
|
+
if (task.companyId) return `${base}/companies/${encodeURIComponent(task.companyId)}`;
|
|
87
|
+
return `${base}/tasks`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* `Wed, Sep 9, 3:00 PM CDT` — the words a reminder says a time with. The zone
|
|
91
|
+
* is the server's choice, not the reader's, so it is printed: a reader an
|
|
92
|
+
* hour away would otherwise take it for their own clock.
|
|
93
|
+
*/ export function crmTaskReminderWhen(ms, timeZone) {
|
|
94
|
+
return new Date(ms).toLocaleString('en-US', {
|
|
95
|
+
timeZone,
|
|
96
|
+
weekday: 'short',
|
|
97
|
+
month: 'short',
|
|
98
|
+
day: 'numeric',
|
|
99
|
+
hour: 'numeric',
|
|
100
|
+
minute: '2-digit',
|
|
101
|
+
timeZoneName: 'short'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* One task as the reminder email lists it: its title and when it is due, or
|
|
106
|
+
* that it has no due date — a reminder can sit on a task with none, at a time
|
|
107
|
+
* the person chose.
|
|
108
|
+
*/ export function crmTaskReminderLine(task, timeZone) {
|
|
109
|
+
return typeof task.dueAtMs === 'number' ? `${task.title} · due ${crmTaskReminderWhen(task.dueAtMs, timeZone)}` : `${task.title} · no due date`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The console notification's body, one per task, which is also what a
|
|
113
|
+
* preview of it shows. It says it is a reminder and names the workspace, so
|
|
114
|
+
* it reads whole without the "Task reminder" title above it.
|
|
115
|
+
*/ export function composeCrmTaskReminderBody(task, timeZone, workspaceName) {
|
|
116
|
+
const workspace = workspaceName == null ? void 0 : workspaceName.trim();
|
|
117
|
+
const when = typeof task.dueAtMs === 'number' ? `, due ${crmTaskReminderWhen(task.dueAtMs, timeZone)}.` : ' (no due date).';
|
|
118
|
+
return `Reminder for your task${workspace ? ` in ${workspace}` : ''}: ${task.title}${when}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* One mail per member per run, so five tasks due at nine o'clock are one
|
|
122
|
+
* message and not five. The subject names the task when there is one, and
|
|
123
|
+
* never says a task is due: a reminder can be set ahead of the due time, or
|
|
124
|
+
* on a task with none.
|
|
125
|
+
*/ export function composeCrmTaskReminderSubject(tasks) {
|
|
126
|
+
if (tasks.length === 1) return `Reminder: ${tasks[0].title}`;
|
|
127
|
+
return `Reminder: ${tasks.length} tasks`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The plain-text email. Text rather than a designed template for the
|
|
131
|
+
* reason the digest gives: the body is a list, and `sendEmail` synthesizes
|
|
132
|
+
* the HTML part that makes its links live. Soonest due first, each task on
|
|
133
|
+
* its own line with its link under it, and the way out last.
|
|
134
|
+
*/ export function composeCrmTaskReminderEmailText(input) {
|
|
135
|
+
var _input_supportLine;
|
|
136
|
+
var _input_workspaceName;
|
|
137
|
+
const { tasks, timeZone, productName } = input;
|
|
138
|
+
const ordered = [
|
|
139
|
+
...tasks
|
|
140
|
+
].sort((a, b)=>{
|
|
141
|
+
var _a_dueAtMs, _b_dueAtMs;
|
|
142
|
+
return ((_a_dueAtMs = a.dueAtMs) != null ? _a_dueAtMs : a.remindAtMs) - ((_b_dueAtMs = b.dueAtMs) != null ? _b_dueAtMs : b.remindAtMs);
|
|
143
|
+
});
|
|
144
|
+
// "About", never "is due": a reminder fires at the time somebody chose,
|
|
145
|
+
// which may be a day ahead of the due date or on a task with none, and
|
|
146
|
+
// each line below says when its task is actually due.
|
|
147
|
+
const where = ((_input_workspaceName = input.workspaceName) == null ? void 0 : _input_workspaceName.trim()) ? ` in ${input.workspaceName.trim()}` : '';
|
|
148
|
+
const lines = [
|
|
149
|
+
ordered.length === 1 ? `A reminder from ${productName} about your task${where}:` : `A reminder from ${productName} about ${ordered.length} of your tasks${where}:`,
|
|
150
|
+
''
|
|
151
|
+
];
|
|
152
|
+
for (const task of ordered){
|
|
153
|
+
lines.push(`- ${crmTaskReminderLine(task, timeZone)}`);
|
|
154
|
+
lines.push(` ${input.taskUrl(task)}`);
|
|
155
|
+
}
|
|
156
|
+
if (input.omitClosing) return lines.join('\n').trimEnd();
|
|
157
|
+
lines.push('', 'You get task reminders because the Forms & bookings category is on in ' + `your notification settings: ${input.settingsUrl}`);
|
|
158
|
+
return lines.join('\n') + ((_input_supportLine = input.supportLine) != null ? _input_supportLine : '');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//# sourceMappingURL=crm-task-reminders.js.map
|