@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
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
* The `nextTaskAtMs` a record carries, as the Admin SDK writes it (AGL-2661).
|
|
18
|
+
*
|
|
19
|
+
* A contact, a company and a deal each denormalize when their earliest OPEN
|
|
20
|
+
* task is due, so a list can draw a "Next activity" column and answer
|
|
21
|
+
* "which deals have nothing scheduled" without a task query per row. This
|
|
22
|
+
* is the writer: given the records a task names, read that record's tasks
|
|
23
|
+
* back and store the answer `nextTaskAtMsOf` gives. Every server-side task
|
|
24
|
+
* writer — the console's task routes, the REST resource, the workflow step
|
|
25
|
+
* that files a task, the merge that repoints one — calls it after its own
|
|
26
|
+
* write; a client-direct task write asks the console's `crm/next-activity`
|
|
27
|
+
* route, which calls it too.
|
|
28
|
+
*
|
|
29
|
+
* Imported by module path rather than from the `@aglyn/aglyn/server`
|
|
30
|
+
* barrel, for the reason `contact-company-link.ts` gives.
|
|
31
|
+
*
|
|
32
|
+
* The task query is ONE equality on the link field, the query the record
|
|
33
|
+
* cards already run, and the status is judged in memory — no composite
|
|
34
|
+
* index, and a record with a hundred done tasks reads a hundred small
|
|
35
|
+
* documents once per write against it, which is the price of never being
|
|
36
|
+
* wrong. The record write is a bare `update()` of the one field, so
|
|
37
|
+
* `updatedAt` stays where the person's last edit left it: a task moving is
|
|
38
|
+
* not the record being edited, and the lists order by `updatedAt`.
|
|
39
|
+
*/ import { CRM_COLLECTIONS } from "@aglyn/aglyn/app-utils/crm";
|
|
40
|
+
import { CRM_NEXT_ACTIVITY_FIELD, mergeNextActivityLinks, nextActivityLinksOfTask, nextTaskAtMsOf, readNextTaskAtMs } from "../model/crm-next-activity.js";
|
|
41
|
+
/** The collection each link field's record lives in, under `orgs/{orgId}/`. */ export const CRM_NEXT_ACTIVITY_COLLECTIONS = {
|
|
42
|
+
contactId: 'contacts',
|
|
43
|
+
companyId: CRM_COLLECTIONS.companies,
|
|
44
|
+
dealId: CRM_COLLECTIONS.deals
|
|
45
|
+
};
|
|
46
|
+
const isNotFound = (error)=>{
|
|
47
|
+
const { code, message } = error != null ? error : {};
|
|
48
|
+
return code === 5 || code === 'not-found' || code === 'NOT_FOUND' || typeof message === 'string' && message.includes('NOT_FOUND');
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Store one record's `nextTaskAtMs` from its tasks. `null` for a record
|
|
52
|
+
* that no longer exists, else the value written.
|
|
53
|
+
*/ async function recomputeOne(orgRef, field, id) {
|
|
54
|
+
const tasks = await orgRef.collection(CRM_COLLECTIONS.tasks).where(field, '==', id).get();
|
|
55
|
+
const value = nextTaskAtMsOf(tasks.docs.map((doc)=>({
|
|
56
|
+
status: doc.get('status'),
|
|
57
|
+
dueAtMs: doc.get('dueAtMs')
|
|
58
|
+
})));
|
|
59
|
+
try {
|
|
60
|
+
await orgRef.collection(CRM_NEXT_ACTIVITY_COLLECTIONS[field]).doc(id).update({
|
|
61
|
+
[CRM_NEXT_ACTIVITY_FIELD]: value
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (isNotFound(error)) return {
|
|
65
|
+
written: false
|
|
66
|
+
};
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
written: true,
|
|
71
|
+
value
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Recompute `nextTaskAtMs` for every record the links name, once each.
|
|
76
|
+
*
|
|
77
|
+
* Hand in BOTH sides of a change — the links a task carried before an
|
|
78
|
+
* update and the ones it carries after — so a task moved from one deal to
|
|
79
|
+
* another leaves neither stale. A record the task named that has since been
|
|
80
|
+
* deleted is counted in `missing` and skipped; nothing here creates one.
|
|
81
|
+
*/ export async function recomputeCrmNextTaskAt(firestore, orgId, links) {
|
|
82
|
+
const orgRef = firestore.collection('orgs').doc(orgId);
|
|
83
|
+
const result = {
|
|
84
|
+
records: 0,
|
|
85
|
+
missing: 0
|
|
86
|
+
};
|
|
87
|
+
for (const { field, id } of mergeNextActivityLinks(links)){
|
|
88
|
+
const outcome = await recomputeOne(orgRef, field, id);
|
|
89
|
+
if (outcome.written) result.records += 1;
|
|
90
|
+
else result.missing += 1;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The same, from a task document rather than a link — what a writer holds
|
|
96
|
+
* after it has read the task it is about to change or has just changed.
|
|
97
|
+
*/ export function crmNextActivityLinksOf(task) {
|
|
98
|
+
return nextActivityLinksOfTask(task);
|
|
99
|
+
}
|
|
100
|
+
/** The most open tasks one org-wide recompute walks. */ export const CRM_NEXT_ACTIVITY_SWEEP_MAX = 20000;
|
|
101
|
+
/**
|
|
102
|
+
* The one-off recompute for a whole organization — the Fields section's
|
|
103
|
+
* "Recompute next activity" (AGL-2661), for the records written before the
|
|
104
|
+
* field existed and for whatever a client-direct write left behind.
|
|
105
|
+
*
|
|
106
|
+
* Two passes and no per-record task query: every OPEN task once, grouped
|
|
107
|
+
* by the records it names, so each record with something scheduled is
|
|
108
|
+
* written its earliest due time; then every record in the three
|
|
109
|
+
* collections that CARRIES a time and was not in that group, cleared to
|
|
110
|
+
* `null`. A record that was never written is left alone — every reader
|
|
111
|
+
* already treats absent as `null`, and writing a null onto every contact
|
|
112
|
+
* an org holds would cost a write per person to say nothing.
|
|
113
|
+
*
|
|
114
|
+
* The clearing pass runs only when the first pass read EVERY open task.
|
|
115
|
+
* "No open task names this record" is a statement about all of them, and
|
|
116
|
+
* an organization holding more than the cap has open tasks the sweep never
|
|
117
|
+
* saw — clearing on a partial read would blank records that are perfectly
|
|
118
|
+
* scheduled. Past the cap the sweep is the writing half alone, and
|
|
119
|
+
* `truncated` says so.
|
|
120
|
+
*/ export async function sweepCrmNextTaskAt(firestore, orgId, options = {}) {
|
|
121
|
+
var _options_max;
|
|
122
|
+
const max = (_options_max = options.max) != null ? _options_max : CRM_NEXT_ACTIVITY_SWEEP_MAX;
|
|
123
|
+
const orgRef = firestore.collection('orgs').doc(orgId);
|
|
124
|
+
const open = await orgRef.collection(CRM_COLLECTIONS.tasks).where('status', '==', 'open').limit(max + 1).get();
|
|
125
|
+
const truncated = open.docs.length > max;
|
|
126
|
+
const docs = open.docs.slice(0, max);
|
|
127
|
+
const earliest = new Map();
|
|
128
|
+
for (const field of Object.keys(CRM_NEXT_ACTIVITY_COLLECTIONS)){
|
|
129
|
+
earliest.set(field, new Map());
|
|
130
|
+
}
|
|
131
|
+
for (const doc of docs){
|
|
132
|
+
const task = {
|
|
133
|
+
status: doc.get('status'),
|
|
134
|
+
dueAtMs: doc.get('dueAtMs')
|
|
135
|
+
};
|
|
136
|
+
const links = nextActivityLinksOfTask({
|
|
137
|
+
contactId: doc.get('contactId'),
|
|
138
|
+
companyId: doc.get('companyId'),
|
|
139
|
+
dealId: doc.get('dealId')
|
|
140
|
+
});
|
|
141
|
+
for (const { field, id } of mergeNextActivityLinks([
|
|
142
|
+
links
|
|
143
|
+
])){
|
|
144
|
+
var _byId_get;
|
|
145
|
+
const byId = earliest.get(field);
|
|
146
|
+
byId.set(id, nextTaskAtMsOf([
|
|
147
|
+
task,
|
|
148
|
+
{
|
|
149
|
+
status: 'open',
|
|
150
|
+
dueAtMs: (_byId_get = byId.get(id)) != null ? _byId_get : null
|
|
151
|
+
}
|
|
152
|
+
]));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const sweep = {
|
|
156
|
+
tasks: docs.length,
|
|
157
|
+
scheduled: 0,
|
|
158
|
+
cleared: 0,
|
|
159
|
+
truncated
|
|
160
|
+
};
|
|
161
|
+
for (const [field, byId] of earliest){
|
|
162
|
+
const records = orgRef.collection(CRM_NEXT_ACTIVITY_COLLECTIONS[field]);
|
|
163
|
+
for (const [id, value] of byId){
|
|
164
|
+
try {
|
|
165
|
+
await records.doc(id).update({
|
|
166
|
+
[CRM_NEXT_ACTIVITY_FIELD]: value
|
|
167
|
+
});
|
|
168
|
+
sweep.scheduled += 1;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (!isNotFound(error)) throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/*
|
|
174
|
+
* The stale half: a record that says something is scheduled when no
|
|
175
|
+
* open task names it any more. `> 0` is the one query a stored time
|
|
176
|
+
* answers without an index of its own, and matches what
|
|
177
|
+
* `readNextTaskAtMs` calls a usable value.
|
|
178
|
+
*/ if (truncated) continue;
|
|
179
|
+
const carrying = await records.where(CRM_NEXT_ACTIVITY_FIELD, '>', 0).get();
|
|
180
|
+
for (const doc of carrying.docs){
|
|
181
|
+
if (byId.has(doc.id)) continue;
|
|
182
|
+
if (readNextTaskAtMs({
|
|
183
|
+
nextTaskAtMs: doc.get(CRM_NEXT_ACTIVITY_FIELD)
|
|
184
|
+
}) === null) continue;
|
|
185
|
+
await doc.ref.update({
|
|
186
|
+
[CRM_NEXT_ACTIVITY_FIELD]: null
|
|
187
|
+
});
|
|
188
|
+
sweep.cleared += 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return sweep;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=crm-next-activity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/crm-next-activity.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 * The `nextTaskAtMs` a record carries, as the Admin SDK writes it (AGL-2661).\n *\n * A contact, a company and a deal each denormalize when their earliest OPEN\n * task is due, so a list can draw a \"Next activity\" column and answer\n * \"which deals have nothing scheduled\" without a task query per row. This\n * is the writer: given the records a task names, read that record's tasks\n * back and store the answer `nextTaskAtMsOf` gives. Every server-side task\n * writer — the console's task routes, the REST resource, the workflow step\n * that files a task, the merge that repoints one — calls it after its own\n * write; a client-direct task write asks the console's `crm/next-activity`\n * route, which calls it too.\n *\n * Imported by module path rather than from the `@aglyn/aglyn/server`\n * barrel, for the reason `contact-company-link.ts` gives.\n *\n * The task query is ONE equality on the link field, the query the record\n * cards already run, and the status is judged in memory — no composite\n * index, and a record with a hundred done tasks reads a hundred small\n * documents once per write against it, which is the price of never being\n * wrong. The record write is a bare `update()` of the one field, so\n * `updatedAt` stays where the person's last edit left it: a task moving is\n * not the record being edited, and the lists order by `updatedAt`.\n */\n\nimport { CRM_COLLECTIONS } from '@aglyn/aglyn/app-utils/crm'\nimport {\n CRM_NEXT_ACTIVITY_FIELD,\n type CrmNextActivityLink,\n type CrmNextActivityLinkField,\n mergeNextActivityLinks,\n nextActivityLinksOfTask,\n nextTaskAtMsOf,\n readNextTaskAtMs,\n} from '../model/crm-next-activity'\n\n/** The collection each link field's record lives in, under `orgs/{orgId}/`. */\nexport const CRM_NEXT_ACTIVITY_COLLECTIONS: Readonly<Record<CrmNextActivityLinkField, string>> = {\n contactId: 'contacts',\n companyId: CRM_COLLECTIONS.companies,\n dealId: CRM_COLLECTIONS.deals,\n}\n\nexport interface CrmNextActivityRecompute {\n /** Records whose stored value was written (or rewritten to the same figure). */\n records: number\n /** Records that no longer exist — a task can outlive what it was filed against. */\n missing: number\n}\n\nconst isNotFound = (error: unknown): boolean => {\n const { code, message } = (error ?? {}) as { code?: unknown; message?: unknown }\n return (\n code === 5 ||\n code === 'not-found' ||\n code === 'NOT_FOUND' ||\n (typeof message === 'string' && message.includes('NOT_FOUND'))\n )\n}\n\n/**\n * Store one record's `nextTaskAtMs` from its tasks. `null` for a record\n * that no longer exists, else the value written.\n */\nasync function recomputeOne(\n orgRef: FirebaseFirestore.DocumentReference,\n field: CrmNextActivityLinkField,\n id: string,\n): Promise<{ written: true; value: number | null } | { written: false }> {\n const tasks = await orgRef\n .collection(CRM_COLLECTIONS.tasks)\n .where(field, '==', id)\n .get()\n const value = nextTaskAtMsOf(\n tasks.docs.map((doc) => ({\n status: doc.get('status'),\n dueAtMs: doc.get('dueAtMs'),\n })),\n )\n try {\n await orgRef\n .collection(CRM_NEXT_ACTIVITY_COLLECTIONS[field])\n .doc(id)\n .update({ [CRM_NEXT_ACTIVITY_FIELD]: value })\n } catch (error) {\n if (isNotFound(error)) return { written: false }\n throw error\n }\n return { written: true, value }\n}\n\n/**\n * Recompute `nextTaskAtMs` for every record the links name, once each.\n *\n * Hand in BOTH sides of a change — the links a task carried before an\n * update and the ones it carries after — so a task moved from one deal to\n * another leaves neither stale. A record the task named that has since been\n * deleted is counted in `missing` and skipped; nothing here creates one.\n */\nexport async function recomputeCrmNextTaskAt(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n links: readonly (CrmNextActivityLink | null | undefined)[],\n): Promise<CrmNextActivityRecompute> {\n const orgRef = firestore.collection('orgs').doc(orgId)\n const result: CrmNextActivityRecompute = { records: 0, missing: 0 }\n for (const { field, id } of mergeNextActivityLinks(links)) {\n const outcome = await recomputeOne(orgRef, field, id)\n if (outcome.written) result.records += 1\n else result.missing += 1\n }\n return result\n}\n\n/**\n * The same, from a task document rather than a link — what a writer holds\n * after it has read the task it is about to change or has just changed.\n */\nexport function crmNextActivityLinksOf(\n task: Record<string, unknown> | null | undefined,\n): CrmNextActivityLink {\n return nextActivityLinksOfTask(task as Parameters<typeof nextActivityLinksOfTask>[0])\n}\n\n/** The most open tasks one org-wide recompute walks. */\nexport const CRM_NEXT_ACTIVITY_SWEEP_MAX = 20_000\n\nexport interface CrmNextActivitySweep {\n /** Open tasks read. */\n tasks: number\n /** Records that now carry a time. */\n scheduled: number\n /** Records that carried a time and now carry `null`. */\n cleared: number\n /** Whether the org held more open tasks than the sweep reads. */\n truncated: boolean\n}\n\n/**\n * The one-off recompute for a whole organization — the Fields section's\n * \"Recompute next activity\" (AGL-2661), for the records written before the\n * field existed and for whatever a client-direct write left behind.\n *\n * Two passes and no per-record task query: every OPEN task once, grouped\n * by the records it names, so each record with something scheduled is\n * written its earliest due time; then every record in the three\n * collections that CARRIES a time and was not in that group, cleared to\n * `null`. A record that was never written is left alone — every reader\n * already treats absent as `null`, and writing a null onto every contact\n * an org holds would cost a write per person to say nothing.\n *\n * The clearing pass runs only when the first pass read EVERY open task.\n * \"No open task names this record\" is a statement about all of them, and\n * an organization holding more than the cap has open tasks the sweep never\n * saw — clearing on a partial read would blank records that are perfectly\n * scheduled. Past the cap the sweep is the writing half alone, and\n * `truncated` says so.\n */\nexport async function sweepCrmNextTaskAt(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n options: { max?: number } = {},\n): Promise<CrmNextActivitySweep> {\n const max = options.max ?? CRM_NEXT_ACTIVITY_SWEEP_MAX\n const orgRef = firestore.collection('orgs').doc(orgId)\n const open = await orgRef\n .collection(CRM_COLLECTIONS.tasks)\n .where('status', '==', 'open')\n .limit(max + 1)\n .get()\n const truncated = open.docs.length > max\n const docs = open.docs.slice(0, max)\n\n const earliest = new Map<CrmNextActivityLinkField, Map<string, number | null>>()\n for (const field of Object.keys(CRM_NEXT_ACTIVITY_COLLECTIONS) as CrmNextActivityLinkField[]) {\n earliest.set(field, new Map())\n }\n for (const doc of docs) {\n const task = { status: doc.get('status'), dueAtMs: doc.get('dueAtMs') }\n const links = nextActivityLinksOfTask({\n contactId: doc.get('contactId'),\n companyId: doc.get('companyId'),\n dealId: doc.get('dealId'),\n })\n for (const { field, id } of mergeNextActivityLinks([links])) {\n const byId = earliest.get(field) as Map<string, number | null>\n byId.set(id, nextTaskAtMsOf([task, { status: 'open', dueAtMs: byId.get(id) ?? null }]))\n }\n }\n\n const sweep: CrmNextActivitySweep = { tasks: docs.length, scheduled: 0, cleared: 0, truncated }\n for (const [field, byId] of earliest) {\n const records = orgRef.collection(CRM_NEXT_ACTIVITY_COLLECTIONS[field])\n for (const [id, value] of byId) {\n try {\n await records.doc(id).update({ [CRM_NEXT_ACTIVITY_FIELD]: value })\n sweep.scheduled += 1\n } catch (error) {\n if (!isNotFound(error)) throw error\n }\n }\n /*\n * The stale half: a record that says something is scheduled when no\n * open task names it any more. `> 0` is the one query a stored time\n * answers without an index of its own, and matches what\n * `readNextTaskAtMs` calls a usable value.\n */\n if (truncated) continue\n const carrying = await records.where(CRM_NEXT_ACTIVITY_FIELD, '>', 0).get()\n for (const doc of carrying.docs) {\n if (byId.has(doc.id)) continue\n if (readNextTaskAtMs({ nextTaskAtMs: doc.get(CRM_NEXT_ACTIVITY_FIELD) }) === null) continue\n await doc.ref.update({ [CRM_NEXT_ACTIVITY_FIELD]: null })\n sweep.cleared += 1\n }\n }\n return sweep\n}\n"],"names":["CRM_COLLECTIONS","CRM_NEXT_ACTIVITY_FIELD","mergeNextActivityLinks","nextActivityLinksOfTask","nextTaskAtMsOf","readNextTaskAtMs","CRM_NEXT_ACTIVITY_COLLECTIONS","contactId","companyId","companies","dealId","deals","isNotFound","error","code","message","includes","recomputeOne","orgRef","field","id","tasks","collection","where","get","value","docs","map","doc","status","dueAtMs","update","written","recomputeCrmNextTaskAt","firestore","orgId","links","result","records","missing","outcome","crmNextActivityLinksOf","task","CRM_NEXT_ACTIVITY_SWEEP_MAX","sweepCrmNextTaskAt","options","max","open","limit","truncated","length","slice","earliest","Map","Object","keys","set","byId","sweep","scheduled","cleared","carrying","has","nextTaskAtMs","ref"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
import type { PluginIndexedRecord, PluginRecordIndex } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
|
|
18
|
+
/**
|
|
19
|
+
* Two more of the CRM's record kinds, as another plugin reads them on the
|
|
20
|
+
* server (AGL-3080) — a sales sequence judging the company a person works
|
|
21
|
+
* for, and sending the body of a template a rep wrote — without knowing where
|
|
22
|
+
* either is stored.
|
|
23
|
+
*
|
|
24
|
+
* Both org-scoped (`orgId` required; a site-only scope answers nothing). With
|
|
25
|
+
* a `hostId`, only what that site may see.
|
|
26
|
+
*
|
|
27
|
+
* `company` — `name` is the company's, else its domain, else its id. Its
|
|
28
|
+
* `facts` are the company as the CRM stores it: `name`, `domain`, `address`
|
|
29
|
+
* (`{ line1, city, region, postalCode, country }`), `phone`, `website`,
|
|
30
|
+
* `industry` and the rest of the record's fields.
|
|
31
|
+
*
|
|
32
|
+
* `messageTemplate` — the CRM's email templates and snippets, by the name a
|
|
33
|
+
* rep gave it; one saved without a name is left out. Its `facts`:
|
|
34
|
+
* `{ kind: 'template' | 'snippet', subject: string, body: string }`, the body
|
|
35
|
+
* in the CRM's merge-field grammar.
|
|
36
|
+
*/
|
|
37
|
+
type Firestore = FirebaseFirestore.Firestore;
|
|
38
|
+
export declare function companyIndexedRecord(id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null;
|
|
39
|
+
export declare function messageTemplateIndexedRecord(id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null;
|
|
40
|
+
/** Builds the two indexes over a Firestore; the CRM registers them over the admin app's. */
|
|
41
|
+
export declare function crmRecordIndexes(firestore?: () => Firestore): {
|
|
42
|
+
company: PluginRecordIndex;
|
|
43
|
+
messageTemplate: PluginRecordIndex;
|
|
44
|
+
};
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { CRM_COLLECTIONS } from "@aglyn/aglyn/app-utils/crm";
|
|
18
|
+
import { normalizeCrmEmailTemplate } from "@aglyn/aglyn/app-utils/crm-email-templates";
|
|
19
|
+
import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
20
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
|
|
21
|
+
const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
22
|
+
export function companyIndexedRecord(id, data) {
|
|
23
|
+
if (!data) return null;
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
name: str(data['name']) || str(data['domain']) || id,
|
|
27
|
+
facts: _extends({}, data)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function messageTemplateIndexedRecord(id, data) {
|
|
31
|
+
if (!data) return null;
|
|
32
|
+
const template = normalizeCrmEmailTemplate(data);
|
|
33
|
+
if (!template.name) return null;
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
name: template.name,
|
|
37
|
+
facts: {
|
|
38
|
+
kind: template.kind,
|
|
39
|
+
subject: template.subject,
|
|
40
|
+
body: template.body
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function orgIndex(collection, record, firestore) {
|
|
45
|
+
const of = (orgId)=>firestore().collection('orgs').doc(orgId).collection(collection);
|
|
46
|
+
const visible = (data, hostId)=>!hostId || visibleToHost(data == null ? void 0 : data['visibleTo'], hostId);
|
|
47
|
+
return {
|
|
48
|
+
async list ({ orgId, hostId, limit }) {
|
|
49
|
+
if (!orgId || limit <= 0) return {
|
|
50
|
+
records: [],
|
|
51
|
+
truncated: false
|
|
52
|
+
};
|
|
53
|
+
// One more than asked, so `truncated` is a fact; scope and names are
|
|
54
|
+
// applied after the read.
|
|
55
|
+
const snapshot = await of(orgId).orderBy('name').limit(limit + 1).get();
|
|
56
|
+
const records = snapshot.docs.slice(0, limit).filter((doc)=>visible(doc.data(), hostId)).map((doc)=>record(doc.id, doc.data())).filter((entry)=>entry !== null);
|
|
57
|
+
return {
|
|
58
|
+
records,
|
|
59
|
+
truncated: snapshot.docs.length > limit
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
async get ({ orgId, hostId, id }) {
|
|
63
|
+
if (!orgId || !id) return null;
|
|
64
|
+
const snapshot = await of(orgId).doc(id).get();
|
|
65
|
+
if (!snapshot.exists || !visible(snapshot.data(), hostId)) return null;
|
|
66
|
+
return record(snapshot.id, snapshot.data());
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const adminFirestore = ()=>firebaseAdmin.app().firestore();
|
|
71
|
+
/** Builds the two indexes over a Firestore; the CRM registers them over the admin app's. */ export function crmRecordIndexes(firestore = adminFirestore) {
|
|
72
|
+
return {
|
|
73
|
+
company: orgIndex(CRM_COLLECTIONS.companies, companyIndexedRecord, firestore),
|
|
74
|
+
messageTemplate: orgIndex(CRM_COLLECTIONS.emailTemplates, messageTemplateIndexedRecord, firestore)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=crm-record-indexes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/crm-record-indexes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CRM_COLLECTIONS } from '@aglyn/aglyn/app-utils/crm'\nimport { normalizeCrmEmailTemplate } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type {\n PluginIndexedRecord,\n PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\n\n/**\n * Two more of the CRM's record kinds, as another plugin reads them on the\n * server (AGL-3080) — a sales sequence judging the company a person works\n * for, and sending the body of a template a rep wrote — without knowing where\n * either is stored.\n *\n * Both org-scoped (`orgId` required; a site-only scope answers nothing). With\n * a `hostId`, only what that site may see.\n *\n * `company` — `name` is the company's, else its domain, else its id. Its\n * `facts` are the company as the CRM stores it: `name`, `domain`, `address`\n * (`{ line1, city, region, postalCode, country }`), `phone`, `website`,\n * `industry` and the rest of the record's fields.\n *\n * `messageTemplate` — the CRM's email templates and snippets, by the name a\n * rep gave it; one saved without a name is left out. Its `facts`:\n * `{ kind: 'template' | 'snippet', subject: string, body: string }`, the body\n * in the CRM's merge-field grammar.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nexport function companyIndexedRecord(\n id: string,\n data: Record<string, unknown> | undefined,\n): PluginIndexedRecord | null {\n if (!data) return null\n return { id, name: str(data['name']) || str(data['domain']) || id, facts: { ...data } }\n}\n\nexport function messageTemplateIndexedRecord(\n id: string,\n data: Record<string, unknown> | undefined,\n): PluginIndexedRecord | null {\n if (!data) return null\n const template = normalizeCrmEmailTemplate(data)\n if (!template.name) return null\n return {\n id,\n name: template.name,\n facts: { kind: template.kind, subject: template.subject, body: template.body },\n }\n}\n\nfunction orgIndex(\n collection: string,\n record: (id: string, data: Record<string, unknown> | undefined) => PluginIndexedRecord | null,\n firestore: () => Firestore,\n): PluginRecordIndex {\n const of = (orgId: string) => firestore().collection('orgs').doc(orgId).collection(collection)\n const visible = (data: Record<string, unknown> | undefined, hostId: string | null | undefined) =>\n !hostId || visibleToHost(data?.['visibleTo'] as string[] | undefined, hostId)\n return {\n async list({ orgId, hostId, limit }) {\n if (!orgId || limit <= 0) return { records: [], truncated: false }\n // One more than asked, so `truncated` is a fact; scope and names are\n // applied after the read.\n const snapshot = await of(orgId).orderBy('name').limit(limit + 1).get()\n const records = snapshot.docs\n .slice(0, limit)\n .filter((doc) => visible(doc.data(), hostId))\n .map((doc) => record(doc.id, doc.data()))\n .filter((entry): entry is PluginIndexedRecord => entry !== null)\n return { records, truncated: snapshot.docs.length > limit }\n },\n async get({ orgId, hostId, id }) {\n if (!orgId || !id) return null\n const snapshot = await of(orgId).doc(id).get()\n if (!snapshot.exists || !visible(snapshot.data(), hostId)) return null\n return record(snapshot.id, snapshot.data())\n },\n }\n}\n\nconst adminFirestore = () => firebaseAdmin.app().firestore()\n\n/** Builds the two indexes over a Firestore; the CRM registers them over the admin app's. */\nexport function crmRecordIndexes(firestore: () => Firestore = adminFirestore): {\n company: PluginRecordIndex\n messageTemplate: PluginRecordIndex\n} {\n return {\n company: orgIndex(CRM_COLLECTIONS.companies, companyIndexedRecord, firestore),\n messageTemplate: orgIndex(CRM_COLLECTIONS.emailTemplates, messageTemplateIndexedRecord, firestore),\n }\n}\n"],"names":["CRM_COLLECTIONS","normalizeCrmEmailTemplate","visibleToHost","firebaseAdmin","str","value","trim","companyIndexedRecord","id","data","name","facts","messageTemplateIndexedRecord","template","kind","subject","body","orgIndex","collection","record","firestore","of","orgId","doc","visible","hostId","list","limit","records","truncated","snapshot","orderBy","get","docs","slice","filter","map","entry","length","exists","adminFirestore","app","crmRecordIndexes","company","companies","messageTemplate","emailTemplates"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,6BAA4B;AAC5D,SAASC,yBAAyB,QAAQ,6CAA4C;AACtF,SAASC,aAAa,QAAQ,sCAAqC;AAKnE,SAASC,aAAa,QAAQ,iDAAgD;AAwB9E,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,OAAO,SAASC,qBACdC,EAAU,EACVC,IAAyC;IAEzC,IAAI,CAACA,MAAM,OAAO;IAClB,OAAO;QAAED;QAAIE,MAAMN,IAAIK,IAAI,CAAC,OAAO,KAAKL,IAAIK,IAAI,CAAC,SAAS,KAAKD;QAAIG,OAAO,aAAKF;IAAO;AACxF;AAEA,OAAO,SAASG,6BACdJ,EAAU,EACVC,IAAyC;IAEzC,IAAI,CAACA,MAAM,OAAO;IAClB,MAAMI,WAAWZ,0BAA0BQ;IAC3C,IAAI,CAACI,SAASH,IAAI,EAAE,OAAO;IAC3B,OAAO;QACLF;QACAE,MAAMG,SAASH,IAAI;QACnBC,OAAO;YAAEG,MAAMD,SAASC,IAAI;YAAEC,SAASF,SAASE,OAAO;YAAEC,MAAMH,SAASG,IAAI;QAAC;IAC/E;AACF;AAEA,SAASC,SACPC,UAAkB,EAClBC,MAA6F,EAC7FC,SAA0B;IAE1B,MAAMC,KAAK,CAACC,QAAkBF,YAAYF,UAAU,CAAC,QAAQK,GAAG,CAACD,OAAOJ,UAAU,CAACA;IACnF,MAAMM,UAAU,CAACf,MAA2CgB,SAC1D,CAACA,UAAUvB,cAAcO,wBAAAA,IAAM,CAAC,YAAY,EAA0BgB;IACxE,OAAO;QACL,MAAMC,MAAK,EAAEJ,KAAK,EAAEG,MAAM,EAAEE,KAAK,EAAE;YACjC,IAAI,CAACL,SAASK,SAAS,GAAG,OAAO;gBAAEC,SAAS,EAAE;gBAAEC,WAAW;YAAM;YACjE,qEAAqE;YACrE,0BAA0B;YAC1B,MAAMC,WAAW,MAAMT,GAAGC,OAAOS,OAAO,CAAC,QAAQJ,KAAK,CAACA,QAAQ,GAAGK,GAAG;YACrE,MAAMJ,UAAUE,SAASG,IAAI,CAC1BC,KAAK,CAAC,GAAGP,OACTQ,MAAM,CAAC,CAACZ,MAAQC,QAAQD,IAAId,IAAI,IAAIgB,SACpCW,GAAG,CAAC,CAACb,MAAQJ,OAAOI,IAAIf,EAAE,EAAEe,IAAId,IAAI,KACpC0B,MAAM,CAAC,CAACE,QAAwCA,UAAU;YAC7D,OAAO;gBAAET;gBAASC,WAAWC,SAASG,IAAI,CAACK,MAAM,GAAGX;YAAM;QAC5D;QACA,MAAMK,KAAI,EAAEV,KAAK,EAAEG,MAAM,EAAEjB,EAAE,EAAE;YAC7B,IAAI,CAACc,SAAS,CAACd,IAAI,OAAO;YAC1B,MAAMsB,WAAW,MAAMT,GAAGC,OAAOC,GAAG,CAACf,IAAIwB,GAAG;YAC5C,IAAI,CAACF,SAASS,MAAM,IAAI,CAACf,QAAQM,SAASrB,IAAI,IAAIgB,SAAS,OAAO;YAClE,OAAON,OAAOW,SAAStB,EAAE,EAAEsB,SAASrB,IAAI;QAC1C;IACF;AACF;AAEA,MAAM+B,iBAAiB,IAAMrC,cAAcsC,GAAG,GAAGrB,SAAS;AAE1D,0FAA0F,GAC1F,OAAO,SAASsB,iBAAiBtB,YAA6BoB,cAAc;IAI1E,OAAO;QACLG,SAAS1B,SAASjB,gBAAgB4C,SAAS,EAAErC,sBAAsBa;QACnEyB,iBAAiB5B,SAASjB,gBAAgB8C,cAAc,EAAElC,8BAA8BQ;IAC1F;AACF"}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
-
import { type CrmShareGrant, type CrmSharingObject, type CrmSharingRule, type CrmSharingSite } from '../model/crm-sharing';
|
|
18
|
+
import { type CrmShareGrant, type CrmSharingContext, type CrmSharingObject, type CrmSharingRule, type CrmSharingSite } from '../model/crm-sharing';
|
|
19
19
|
type Firestore = FirebaseFirestore.Firestore;
|
|
20
20
|
/** Test seam, and what a rule write calls so its own process reads it fresh. */
|
|
21
21
|
export declare function forgetCrmSharingRules(orgId?: string): void;
|
|
@@ -26,6 +26,12 @@ export declare function crmSharingRulesOf(firestore: Firestore, orgId: string, o
|
|
|
26
26
|
rules: CrmSharingRule[];
|
|
27
27
|
fresh: boolean;
|
|
28
28
|
}>;
|
|
29
|
+
/**
|
|
30
|
+
* What the rules for `object` are evaluated against beyond the record: the
|
|
31
|
+
* org's Lead source list, read only when an active rule asks a lead source
|
|
32
|
+
* direction (AGL-3511) — an org whose rules never do pays no read for it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function crmSharingContextFor(firestore: Firestore, orgId: string, rules: readonly CrmSharingRule[], object: CrmSharingObject): Promise<CrmSharingContext>;
|
|
29
35
|
export interface RecomputeRecordSharingInput {
|
|
30
36
|
firestore: Firestore;
|
|
31
37
|
orgId: string;
|
|
@@ -37,6 +43,8 @@ export interface RecomputeRecordSharingInput {
|
|
|
37
43
|
fresh: boolean;
|
|
38
44
|
/** The hand shares' change, for a share or an unshare. */
|
|
39
45
|
manual?: (grants: Record<string, CrmShareGrant>) => Record<string, CrmShareGrant>;
|
|
46
|
+
/** What the rules are evaluated against beyond the record — see {@link crmSharingContextFor}. */
|
|
47
|
+
context?: CrmSharingContext;
|
|
40
48
|
atMs: number;
|
|
41
49
|
}
|
|
42
50
|
export type RecomputeOutcome = 'changed' | 'unchanged' | 'missing';
|
|
@@ -39,7 +39,8 @@ import { firebaseAdmin, getOrgForHost, logOrgActivity } from "@aglyn/tenant-data
|
|
|
39
39
|
import { countCrmActivitiesForRecord } from "@aglyn/tenant-data-admin/server/crm-records";
|
|
40
40
|
import { FieldPath, FieldValue } from "firebase-admin/firestore";
|
|
41
41
|
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
42
|
-
import { activeSharingRules, CRM_SHARING_CRITERIA_FOR, CRM_SHARING_CRITERIA_KEYS, CRM_SHARING_FIELD, CRM_SHARING_IDS_MAX, CRM_SHARING_OBJECT_LABELS, CRM_SHARING_RULE_IDS_PATH, CRM_SHARING_RULES_MAX, CRM_WRITE_SCOPE_FIELD, describeShareTargets, evaluateRecordGrants, isCrmShareAccess, isCrmSharingObject, planRecordSharing, readCrmSharingRule, readCrmSharingRules, recordHeldScope, sharingPlanChanges, shareTargetsFrom, withManualShares, withoutManualShares } from "../model/crm-sharing.js";
|
|
42
|
+
import { activeSharingRules, CRM_SHARING_CRITERIA_FOR, CRM_SHARING_CRITERIA_KEYS, CRM_SHARING_FIELD, CRM_SHARING_IDS_MAX, CRM_SHARING_OBJECT_LABELS, CRM_SHARING_RULE_IDS_PATH, CRM_SHARING_RULES_MAX, CRM_WRITE_SCOPE_FIELD, describeShareTargets, evaluateRecordGrants, isCrmShareAccess, isCrmSharingObject, planRecordSharing, readCrmSharingRule, readCrmSharingRules, recordHeldScope, sharingPlanChanges, shareTargetsFrom, sharingRulesAskLeadSourceDirection, withManualShares, withoutManualShares } from "../model/crm-sharing.js";
|
|
43
|
+
import { readLeadSourcePicklist } from "./lead-source-picklist.js";
|
|
43
44
|
import { authorizeOrgCaller, orgHostIds, readCrmRouteScope } from "./org-caller.js";
|
|
44
45
|
import { crmSuiteRefusal } from "./suite-gate.js";
|
|
45
46
|
import { authorizeCrmWriter } from "./task-routes.js";
|
|
@@ -76,11 +77,22 @@ export async function crmSharingRulesOf(firestore, orgId, options = {}) {
|
|
|
76
77
|
fresh: true
|
|
77
78
|
};
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* What the rules for `object` are evaluated against beyond the record: the
|
|
82
|
+
* org's Lead source list, read only when an active rule asks a lead source
|
|
83
|
+
* direction (AGL-3511) — an org whose rules never do pays no read for it.
|
|
84
|
+
*/ export async function crmSharingContextFor(firestore, orgId, rules, object) {
|
|
85
|
+
if (!sharingRulesAskLeadSourceDirection(rules, object)) return {};
|
|
86
|
+
return {
|
|
87
|
+
leadSources: await readLeadSourcePicklist(firestore, orgId)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
79
90
|
export async function recomputeRecordSharing(input) {
|
|
80
91
|
const { firestore, orgId, object, id, atMs } = input;
|
|
81
92
|
const ref = firestore.collection('orgs').doc(orgId).collection(object).doc(id);
|
|
82
93
|
return await firestore.runTransaction(async (tx)=>{
|
|
83
94
|
var _snapshot_data, _plan_writeTo, _plan_sharing, _plan_writeTo1, _plan_sharing1;
|
|
95
|
+
var _input_context;
|
|
84
96
|
const snapshot = await tx.get(ref);
|
|
85
97
|
if (!snapshot.exists) return 'missing';
|
|
86
98
|
const record = (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {};
|
|
@@ -100,7 +112,9 @@ export async function recomputeRecordSharing(input) {
|
|
|
100
112
|
rules = readCrmSharingRules((_org_data = org.data()) != null ? _org_data : null);
|
|
101
113
|
}
|
|
102
114
|
}
|
|
103
|
-
|
|
115
|
+
// A rule read afresh above may ask a direction the caller's context was not read for.
|
|
116
|
+
const context = ((_input_context = input.context) == null ? void 0 : _input_context.leadSources) || rules === input.rules ? input.context : await crmSharingContextFor(firestore, orgId, rules, object);
|
|
117
|
+
let grants = evaluateRecordGrants(object, record, rules, atMs, context);
|
|
104
118
|
if (input.manual) grants = input.manual(grants);
|
|
105
119
|
const plan = planRecordSharing(record, grants);
|
|
106
120
|
if (!sharingPlanChanges(record, plan)) return 'unchanged';
|
|
@@ -151,12 +165,14 @@ export async function recomputeRecordSharing(input) {
|
|
|
151
165
|
changed: 0,
|
|
152
166
|
missing: 0
|
|
153
167
|
};
|
|
168
|
+
const context = await crmSharingContextFor(firestore, orgId, rules, object);
|
|
154
169
|
const outcome = await recomputeMany({
|
|
155
170
|
firestore,
|
|
156
171
|
orgId,
|
|
157
172
|
object,
|
|
158
173
|
rules,
|
|
159
174
|
fresh,
|
|
175
|
+
context,
|
|
160
176
|
atMs: Date.now()
|
|
161
177
|
}, ids);
|
|
162
178
|
return {
|
|
@@ -404,6 +420,7 @@ const ORG_TARGET = {
|
|
|
404
420
|
const { rules } = await crmSharingRulesOf(firestore, orgId, {
|
|
405
421
|
fresh: true
|
|
406
422
|
});
|
|
423
|
+
const context = await crmSharingContextFor(firestore, orgId, rules, rule.object);
|
|
407
424
|
let page;
|
|
408
425
|
if (run.mode === 'apply') {
|
|
409
426
|
let query = records.orderBy(FieldPath.documentId()).limit(RUN_PAGE);
|
|
@@ -417,7 +434,7 @@ const ORG_TARGET = {
|
|
|
417
434
|
const moving = page.docs.filter((doc)=>{
|
|
418
435
|
var _doc_data;
|
|
419
436
|
const record = (_doc_data = doc.data()) != null ? _doc_data : {};
|
|
420
|
-
const plan = planRecordSharing(record, evaluateRecordGrants(rule.object, record, rules, atMs));
|
|
437
|
+
const plan = planRecordSharing(record, evaluateRecordGrants(rule.object, record, rules, atMs, context));
|
|
421
438
|
return sharingPlanChanges(record, plan);
|
|
422
439
|
}).map((doc)=>doc.id);
|
|
423
440
|
if (run.mode === 'remove' && page.docs.length && page.docs.every((doc)=>seen.has(doc.id))) {
|
|
@@ -430,6 +447,7 @@ const ORG_TARGET = {
|
|
|
430
447
|
object: rule.object,
|
|
431
448
|
rules,
|
|
432
449
|
fresh: true,
|
|
450
|
+
context,
|
|
433
451
|
atMs
|
|
434
452
|
}, moving);
|
|
435
453
|
const done = run.mode === 'apply' ? page.size < RUN_PAGE : page.empty;
|
|
@@ -593,12 +611,14 @@ function readIds(raw) {
|
|
|
593
611
|
}
|
|
594
612
|
const access = body['access'] === 'edit' ? 'edit' : 'read';
|
|
595
613
|
const { rules, fresh } = await crmSharingRulesOf(firestore, orgId);
|
|
614
|
+
const context = await crmSharingContextFor(firestore, orgId, rules, object);
|
|
596
615
|
const outcome = await recomputeMany({
|
|
597
616
|
firestore,
|
|
598
617
|
orgId,
|
|
599
618
|
object,
|
|
600
619
|
rules,
|
|
601
620
|
fresh,
|
|
621
|
+
context,
|
|
602
622
|
atMs: nowMs,
|
|
603
623
|
manual: (grants)=>action === 'share' ? withManualShares(grants, targets, access, {
|
|
604
624
|
uid: caller.uid,
|