@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,107 @@
|
|
|
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
|
+
* NEXT ACTIVITY (AGL-2661): when the earliest open task against a record
|
|
19
|
+
* is due, denormalized onto the record as `nextTaskAtMs`.
|
|
20
|
+
*
|
|
21
|
+
* The question a sales manager asks of a LIST — "which deals have nothing
|
|
22
|
+
* scheduled?" — cannot be answered by a task query per row, so every
|
|
23
|
+
* contact, company and deal carries the answer. This module is the pure
|
|
24
|
+
* half: what the value IS given a record's open tasks, how a stored one is
|
|
25
|
+
* read back, the view clause a list filters on, and the report figure. The
|
|
26
|
+
* admin library's `recomputeCrmNextTaskAt` is the writer; the console's
|
|
27
|
+
* route is its door for client-direct task writes.
|
|
28
|
+
*/
|
|
29
|
+
import type { CrmDeal, CrmTask, CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm';
|
|
30
|
+
/** The field on a contact, a company and a deal — the one name every list reads. */
|
|
31
|
+
export declare const CRM_NEXT_ACTIVITY_FIELD = "nextTaskAtMs";
|
|
32
|
+
/**
|
|
33
|
+
* The records one task can be filed against — the ids a recompute reads
|
|
34
|
+
* off a task, and the three collections it writes to.
|
|
35
|
+
*/
|
|
36
|
+
export interface CrmNextActivityLink {
|
|
37
|
+
contactId?: string;
|
|
38
|
+
companyId?: string;
|
|
39
|
+
dealId?: string;
|
|
40
|
+
}
|
|
41
|
+
/** How the collections are keyed on a task, in the order a recompute walks them. */
|
|
42
|
+
export declare const CRM_NEXT_ACTIVITY_LINK_FIELDS: readonly ["contactId", "companyId", "dealId"];
|
|
43
|
+
export type CrmNextActivityLinkField = (typeof CRM_NEXT_ACTIVITY_LINK_FIELDS)[number];
|
|
44
|
+
/**
|
|
45
|
+
* When the earliest OPEN task in the list is due, or `null`.
|
|
46
|
+
*
|
|
47
|
+
* Only an open task with a finite due time counts: a done task is history,
|
|
48
|
+
* and an open one with no date is a to-do with no "next" about it — the
|
|
49
|
+
* record's next activity is unknown rather than immediate, and `null`
|
|
50
|
+
* says so. A pre-change writer never produced a time outside the finite
|
|
51
|
+
* range, but a stored `dueAtMs` is trusted no further than that.
|
|
52
|
+
*/
|
|
53
|
+
export declare function nextTaskAtMsOf(tasks: readonly Pick<CrmTask, 'status' | 'dueAtMs'>[]): number | null;
|
|
54
|
+
/**
|
|
55
|
+
* A stored `nextTaskAtMs`, read back: a finite positive number, else
|
|
56
|
+
* `null`. Absent — a record written before the field existed — reads as
|
|
57
|
+
* `null` too, because to every reader "not yet computed" and "nothing
|
|
58
|
+
* scheduled" draw the same dash; the Fields section's recompute is what
|
|
59
|
+
* closes the gap for a filter that asks the store rather than the row.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readNextTaskAtMs(record: {
|
|
62
|
+
nextTaskAtMs?: unknown;
|
|
63
|
+
} | null | undefined): number | null;
|
|
64
|
+
/** Whether nothing is scheduled against the record — the "stuck" test. */
|
|
65
|
+
export declare function hasNoNextActivity(record: {
|
|
66
|
+
nextTaskAtMs?: unknown;
|
|
67
|
+
} | null | undefined): boolean;
|
|
68
|
+
/** The links one task carries, with the blanks left out. */
|
|
69
|
+
export declare function nextActivityLinksOfTask(task: Partial<Pick<CrmTask, CrmNextActivityLinkField>> | null | undefined): CrmNextActivityLink;
|
|
70
|
+
/**
|
|
71
|
+
* Several links as one per record, so a bulk action over forty tasks on
|
|
72
|
+
* one deal recomputes that deal once. Order is first-seen.
|
|
73
|
+
*/
|
|
74
|
+
export declare function mergeNextActivityLinks(links: readonly (CrmNextActivityLink | null | undefined)[]): Array<{
|
|
75
|
+
field: CrmNextActivityLinkField;
|
|
76
|
+
id: string;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* The saved-view clause "No next activity" is stored as — one clause,
|
|
80
|
+
* spelled once, so the toggle a list draws and the matcher it runs agree
|
|
81
|
+
* on the name. `isEmpty` is honest here: a recompute writes `null` for a
|
|
82
|
+
* record with nothing scheduled, and a record never written reads blank
|
|
83
|
+
* too (see `readNextTaskAtMs`).
|
|
84
|
+
*/
|
|
85
|
+
export declare const CRM_NO_NEXT_ACTIVITY_CLAUSE: Readonly<CrmViewFilterClause>;
|
|
86
|
+
export declare function isNoNextActivityClause(clause: Pick<CrmViewFilterClause, 'field' | 'op'>): boolean;
|
|
87
|
+
/** A view's filters with the clause switched on or off, the rest untouched. */
|
|
88
|
+
export declare function withNoNextActivity(filters: readonly CrmViewFilterClause[], on: boolean): CrmViewFilterClause[];
|
|
89
|
+
/**
|
|
90
|
+
* The rows a "No next activity" clause keeps: every one when the view does
|
|
91
|
+
* not carry it, else those with nothing scheduled. What a list that
|
|
92
|
+
* filters through a query — companies, deals — runs over the page it
|
|
93
|
+
* loaded, the way the contacts list runs its window-only clauses.
|
|
94
|
+
*/
|
|
95
|
+
export declare function filterByNextActivity<T extends {
|
|
96
|
+
nextTaskAtMs?: unknown;
|
|
97
|
+
}>(rows: readonly T[], filters: readonly CrmViewFilterClause[]): T[];
|
|
98
|
+
/**
|
|
99
|
+
* The report figure: how many OPEN deals have nothing scheduled, and which
|
|
100
|
+
* (AGL-2661). A won deal needs no next step and a lost one has none, so
|
|
101
|
+
* only open deals are stuck; the caller hands in the window it already
|
|
102
|
+
* reads for the pipeline card, so this costs no read of its own.
|
|
103
|
+
*/
|
|
104
|
+
export declare function stuckDeals<T extends Pick<CrmDeal, 'status' | 'nextTaskAtMs'>>(deals: readonly T[]): {
|
|
105
|
+
count: number;
|
|
106
|
+
deals: T[];
|
|
107
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
* NEXT ACTIVITY (AGL-2661): when the earliest open task against a record
|
|
18
|
+
* is due, denormalized onto the record as `nextTaskAtMs`.
|
|
19
|
+
*
|
|
20
|
+
* The question a sales manager asks of a LIST — "which deals have nothing
|
|
21
|
+
* scheduled?" — cannot be answered by a task query per row, so every
|
|
22
|
+
* contact, company and deal carries the answer. This module is the pure
|
|
23
|
+
* half: what the value IS given a record's open tasks, how a stored one is
|
|
24
|
+
* read back, the view clause a list filters on, and the report figure. The
|
|
25
|
+
* admin library's `recomputeCrmNextTaskAt` is the writer; the console's
|
|
26
|
+
* route is its door for client-direct task writes.
|
|
27
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
28
|
+
/** The field on a contact, a company and a deal — the one name every list reads. */ export const CRM_NEXT_ACTIVITY_FIELD = 'nextTaskAtMs';
|
|
29
|
+
/** How the collections are keyed on a task, in the order a recompute walks them. */ export const CRM_NEXT_ACTIVITY_LINK_FIELDS = [
|
|
30
|
+
'contactId',
|
|
31
|
+
'companyId',
|
|
32
|
+
'dealId'
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* When the earliest OPEN task in the list is due, or `null`.
|
|
36
|
+
*
|
|
37
|
+
* Only an open task with a finite due time counts: a done task is history,
|
|
38
|
+
* and an open one with no date is a to-do with no "next" about it — the
|
|
39
|
+
* record's next activity is unknown rather than immediate, and `null`
|
|
40
|
+
* says so. A pre-change writer never produced a time outside the finite
|
|
41
|
+
* range, but a stored `dueAtMs` is trusted no further than that.
|
|
42
|
+
*/ export function nextTaskAtMsOf(tasks) {
|
|
43
|
+
let earliest = null;
|
|
44
|
+
for (const task of tasks){
|
|
45
|
+
if (task.status !== 'open') continue;
|
|
46
|
+
const due = task.dueAtMs;
|
|
47
|
+
if (typeof due !== 'number' || !Number.isFinite(due)) continue;
|
|
48
|
+
if (earliest === null || due < earliest) earliest = due;
|
|
49
|
+
}
|
|
50
|
+
return earliest;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A stored `nextTaskAtMs`, read back: a finite positive number, else
|
|
54
|
+
* `null`. Absent — a record written before the field existed — reads as
|
|
55
|
+
* `null` too, because to every reader "not yet computed" and "nothing
|
|
56
|
+
* scheduled" draw the same dash; the Fields section's recompute is what
|
|
57
|
+
* closes the gap for a filter that asks the store rather than the row.
|
|
58
|
+
*/ export function readNextTaskAtMs(record) {
|
|
59
|
+
const value = record == null ? void 0 : record.nextTaskAtMs;
|
|
60
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;
|
|
61
|
+
}
|
|
62
|
+
/** Whether nothing is scheduled against the record — the "stuck" test. */ export function hasNoNextActivity(record) {
|
|
63
|
+
return readNextTaskAtMs(record) === null;
|
|
64
|
+
}
|
|
65
|
+
/** The links one task carries, with the blanks left out. */ export function nextActivityLinksOfTask(task) {
|
|
66
|
+
const link = {};
|
|
67
|
+
for (const field of CRM_NEXT_ACTIVITY_LINK_FIELDS){
|
|
68
|
+
var _ref;
|
|
69
|
+
const id = String((_ref = task == null ? void 0 : task[field]) != null ? _ref : '').trim();
|
|
70
|
+
if (id) link[field] = id;
|
|
71
|
+
}
|
|
72
|
+
return link;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Several links as one per record, so a bulk action over forty tasks on
|
|
76
|
+
* one deal recomputes that deal once. Order is first-seen.
|
|
77
|
+
*/ export function mergeNextActivityLinks(links) {
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const merged = [];
|
|
80
|
+
for (const link of links){
|
|
81
|
+
for (const field of CRM_NEXT_ACTIVITY_LINK_FIELDS){
|
|
82
|
+
var _ref;
|
|
83
|
+
const id = String((_ref = link == null ? void 0 : link[field]) != null ? _ref : '').trim();
|
|
84
|
+
if (!id) continue;
|
|
85
|
+
const key = `${field}:${id}`;
|
|
86
|
+
if (seen.has(key)) continue;
|
|
87
|
+
seen.add(key);
|
|
88
|
+
merged.push({
|
|
89
|
+
field,
|
|
90
|
+
id
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return merged;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The saved-view clause "No next activity" is stored as — one clause,
|
|
98
|
+
* spelled once, so the toggle a list draws and the matcher it runs agree
|
|
99
|
+
* on the name. `isEmpty` is honest here: a recompute writes `null` for a
|
|
100
|
+
* record with nothing scheduled, and a record never written reads blank
|
|
101
|
+
* too (see `readNextTaskAtMs`).
|
|
102
|
+
*/ export const CRM_NO_NEXT_ACTIVITY_CLAUSE = Object.freeze({
|
|
103
|
+
field: CRM_NEXT_ACTIVITY_FIELD,
|
|
104
|
+
op: 'isEmpty',
|
|
105
|
+
value: ''
|
|
106
|
+
});
|
|
107
|
+
export function isNoNextActivityClause(clause) {
|
|
108
|
+
return clause.field === CRM_NEXT_ACTIVITY_FIELD && clause.op === 'isEmpty';
|
|
109
|
+
}
|
|
110
|
+
/** A view's filters with the clause switched on or off, the rest untouched. */ export function withNoNextActivity(filters, on) {
|
|
111
|
+
const rest = filters.filter((clause)=>!isNoNextActivityClause(clause));
|
|
112
|
+
return on ? [
|
|
113
|
+
...rest,
|
|
114
|
+
_extends({}, CRM_NO_NEXT_ACTIVITY_CLAUSE)
|
|
115
|
+
] : rest;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The rows a "No next activity" clause keeps: every one when the view does
|
|
119
|
+
* not carry it, else those with nothing scheduled. What a list that
|
|
120
|
+
* filters through a query — companies, deals — runs over the page it
|
|
121
|
+
* loaded, the way the contacts list runs its window-only clauses.
|
|
122
|
+
*/ export function filterByNextActivity(rows, filters) {
|
|
123
|
+
return filters.some(isNoNextActivityClause) ? rows.filter(hasNoNextActivity) : [
|
|
124
|
+
...rows
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The report figure: how many OPEN deals have nothing scheduled, and which
|
|
129
|
+
* (AGL-2661). A won deal needs no next step and a lost one has none, so
|
|
130
|
+
* only open deals are stuck; the caller hands in the window it already
|
|
131
|
+
* reads for the pipeline card, so this costs no read of its own.
|
|
132
|
+
*/ export function stuckDeals(deals) {
|
|
133
|
+
const stuck = deals.filter((deal)=>deal.status === 'open' && hasNoNextActivity(deal));
|
|
134
|
+
return {
|
|
135
|
+
count: stuck.length,
|
|
136
|
+
deals: stuck
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=crm-next-activity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/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 * NEXT ACTIVITY (AGL-2661): when the earliest open task against a record\n * is due, denormalized onto the record as `nextTaskAtMs`.\n *\n * The question a sales manager asks of a LIST — \"which deals have nothing\n * scheduled?\" — cannot be answered by a task query per row, so every\n * contact, company and deal carries the answer. This module is the pure\n * half: what the value IS given a record's open tasks, how a stored one is\n * read back, the view clause a list filters on, and the report figure. The\n * admin library's `recomputeCrmNextTaskAt` is the writer; the console's\n * route is its door for client-direct task writes.\n */\n\nimport type { CrmDeal, CrmTask, CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm'\n\n/** The field on a contact, a company and a deal — the one name every list reads. */\nexport const CRM_NEXT_ACTIVITY_FIELD = 'nextTaskAtMs'\n\n/**\n * The records one task can be filed against — the ids a recompute reads\n * off a task, and the three collections it writes to.\n */\nexport interface CrmNextActivityLink {\n contactId?: string\n companyId?: string\n dealId?: string\n}\n\n/** How the collections are keyed on a task, in the order a recompute walks them. */\nexport const CRM_NEXT_ACTIVITY_LINK_FIELDS = ['contactId', 'companyId', 'dealId'] as const\n\nexport type CrmNextActivityLinkField = (typeof CRM_NEXT_ACTIVITY_LINK_FIELDS)[number]\n\n/**\n * When the earliest OPEN task in the list is due, or `null`.\n *\n * Only an open task with a finite due time counts: a done task is history,\n * and an open one with no date is a to-do with no \"next\" about it — the\n * record's next activity is unknown rather than immediate, and `null`\n * says so. A pre-change writer never produced a time outside the finite\n * range, but a stored `dueAtMs` is trusted no further than that.\n */\nexport function nextTaskAtMsOf(\n tasks: readonly Pick<CrmTask, 'status' | 'dueAtMs'>[],\n): number | null {\n let earliest: number | null = null\n for (const task of tasks) {\n if (task.status !== 'open') continue\n const due = task.dueAtMs\n if (typeof due !== 'number' || !Number.isFinite(due)) continue\n if (earliest === null || due < earliest) earliest = due\n }\n return earliest\n}\n\n/**\n * A stored `nextTaskAtMs`, read back: a finite positive number, else\n * `null`. Absent — a record written before the field existed — reads as\n * `null` too, because to every reader \"not yet computed\" and \"nothing\n * scheduled\" draw the same dash; the Fields section's recompute is what\n * closes the gap for a filter that asks the store rather than the row.\n */\nexport function readNextTaskAtMs(\n record: { nextTaskAtMs?: unknown } | null | undefined,\n): number | null {\n const value = record?.nextTaskAtMs\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null\n}\n\n/** Whether nothing is scheduled against the record — the \"stuck\" test. */\nexport function hasNoNextActivity(\n record: { nextTaskAtMs?: unknown } | null | undefined,\n): boolean {\n return readNextTaskAtMs(record) === null\n}\n\n/** The links one task carries, with the blanks left out. */\nexport function nextActivityLinksOfTask(\n task: Partial<Pick<CrmTask, CrmNextActivityLinkField>> | null | undefined,\n): CrmNextActivityLink {\n const link: CrmNextActivityLink = {}\n for (const field of CRM_NEXT_ACTIVITY_LINK_FIELDS) {\n const id = String(task?.[field] ?? '').trim()\n if (id) link[field] = id\n }\n return link\n}\n\n/**\n * Several links as one per record, so a bulk action over forty tasks on\n * one deal recomputes that deal once. Order is first-seen.\n */\nexport function mergeNextActivityLinks(\n links: readonly (CrmNextActivityLink | null | undefined)[],\n): Array<{ field: CrmNextActivityLinkField; id: string }> {\n const seen = new Set<string>()\n const merged: Array<{ field: CrmNextActivityLinkField; id: string }> = []\n for (const link of links) {\n for (const field of CRM_NEXT_ACTIVITY_LINK_FIELDS) {\n const id = String(link?.[field] ?? '').trim()\n if (!id) continue\n const key = `${field}:${id}`\n if (seen.has(key)) continue\n seen.add(key)\n merged.push({ field, id })\n }\n }\n return merged\n}\n\n/**\n * The saved-view clause \"No next activity\" is stored as — one clause,\n * spelled once, so the toggle a list draws and the matcher it runs agree\n * on the name. `isEmpty` is honest here: a recompute writes `null` for a\n * record with nothing scheduled, and a record never written reads blank\n * too (see `readNextTaskAtMs`).\n */\nexport const CRM_NO_NEXT_ACTIVITY_CLAUSE: Readonly<CrmViewFilterClause> = Object.freeze({\n field: CRM_NEXT_ACTIVITY_FIELD,\n op: 'isEmpty',\n value: '',\n})\n\nexport function isNoNextActivityClause(clause: Pick<CrmViewFilterClause, 'field' | 'op'>): boolean {\n return clause.field === CRM_NEXT_ACTIVITY_FIELD && clause.op === 'isEmpty'\n}\n\n/** A view's filters with the clause switched on or off, the rest untouched. */\nexport function withNoNextActivity(\n filters: readonly CrmViewFilterClause[],\n on: boolean,\n): CrmViewFilterClause[] {\n const rest = filters.filter((clause) => !isNoNextActivityClause(clause))\n return on ? [...rest, { ...CRM_NO_NEXT_ACTIVITY_CLAUSE }] : rest\n}\n\n/**\n * The rows a \"No next activity\" clause keeps: every one when the view does\n * not carry it, else those with nothing scheduled. What a list that\n * filters through a query — companies, deals — runs over the page it\n * loaded, the way the contacts list runs its window-only clauses.\n */\nexport function filterByNextActivity<T extends { nextTaskAtMs?: unknown }>(\n rows: readonly T[],\n filters: readonly CrmViewFilterClause[],\n): T[] {\n return filters.some(isNoNextActivityClause) ? rows.filter(hasNoNextActivity) : [...rows]\n}\n\n/**\n * The report figure: how many OPEN deals have nothing scheduled, and which\n * (AGL-2661). A won deal needs no next step and a lost one has none, so\n * only open deals are stuck; the caller hands in the window it already\n * reads for the pipeline card, so this costs no read of its own.\n */\nexport function stuckDeals<T extends Pick<CrmDeal, 'status' | 'nextTaskAtMs'>>(\n deals: readonly T[],\n): { count: number; deals: T[] } {\n const stuck = deals.filter((deal) => deal.status === 'open' && hasNoNextActivity(deal))\n return { count: stuck.length, deals: stuck }\n}\n"],"names":["CRM_NEXT_ACTIVITY_FIELD","CRM_NEXT_ACTIVITY_LINK_FIELDS","nextTaskAtMsOf","tasks","earliest","task","status","due","dueAtMs","Number","isFinite","readNextTaskAtMs","record","value","nextTaskAtMs","hasNoNextActivity","nextActivityLinksOfTask","link","field","id","String","trim","mergeNextActivityLinks","links","seen","Set","merged","key","has","add","push","CRM_NO_NEXT_ACTIVITY_CLAUSE","Object","freeze","op","isNoNextActivityClause","clause","withNoNextActivity","filters","on","rest","filter","filterByNextActivity","rows","some","stuckDeals","deals","stuck","deal","count","length"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;CAWC;AAID,kFAAkF,GAClF,OAAO,MAAMA,0BAA0B,eAAc;AAYrD,kFAAkF,GAClF,OAAO,MAAMC,gCAAgC;IAAC;IAAa;IAAa;CAAS,CAAS;AAI1F;;;;;;;;CAQC,GACD,OAAO,SAASC,eACdC,KAAqD;IAErD,IAAIC,WAA0B;IAC9B,KAAK,MAAMC,QAAQF,MAAO;QACxB,IAAIE,KAAKC,MAAM,KAAK,QAAQ;QAC5B,MAAMC,MAAMF,KAAKG,OAAO;QACxB,IAAI,OAAOD,QAAQ,YAAY,CAACE,OAAOC,QAAQ,CAACH,MAAM;QACtD,IAAIH,aAAa,QAAQG,MAAMH,UAAUA,WAAWG;IACtD;IACA,OAAOH;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASO,iBACdC,MAAqD;IAErD,MAAMC,QAAQD,0BAAAA,OAAQE,YAAY;IAClC,OAAO,OAAOD,UAAU,YAAYJ,OAAOC,QAAQ,CAACG,UAAUA,QAAQ,IAAIA,QAAQ;AACpF;AAEA,wEAAwE,GACxE,OAAO,SAASE,kBACdH,MAAqD;IAErD,OAAOD,iBAAiBC,YAAY;AACtC;AAEA,0DAA0D,GAC1D,OAAO,SAASI,wBACdX,IAAyE;IAEzE,MAAMY,OAA4B,CAAC;IACnC,KAAK,MAAMC,SAASjB,8BAA+B;;QACjD,MAAMkB,KAAKC,eAAOf,wBAAAA,IAAM,CAACa,MAAM,mBAAI,IAAIG,IAAI;QAC3C,IAAIF,IAAIF,IAAI,CAACC,MAAM,GAAGC;IACxB;IACA,OAAOF;AACT;AAEA;;;CAGC,GACD,OAAO,SAASK,uBACdC,KAA0D;IAE1D,MAAMC,OAAO,IAAIC;IACjB,MAAMC,SAAiE,EAAE;IACzE,KAAK,MAAMT,QAAQM,MAAO;QACxB,KAAK,MAAML,SAASjB,8BAA+B;;YACjD,MAAMkB,KAAKC,eAAOH,wBAAAA,IAAM,CAACC,MAAM,mBAAI,IAAIG,IAAI;YAC3C,IAAI,CAACF,IAAI;YACT,MAAMQ,MAAM,GAAGT,MAAM,CAAC,EAAEC,IAAI;YAC5B,IAAIK,KAAKI,GAAG,CAACD,MAAM;YACnBH,KAAKK,GAAG,CAACF;YACTD,OAAOI,IAAI,CAAC;gBAAEZ;gBAAOC;YAAG;QAC1B;IACF;IACA,OAAOO;AACT;AAEA;;;;;;CAMC,GACD,OAAO,MAAMK,8BAA6DC,OAAOC,MAAM,CAAC;IACtFf,OAAOlB;IACPkC,IAAI;IACJrB,OAAO;AACT,GAAE;AAEF,OAAO,SAASsB,uBAAuBC,MAAiD;IACtF,OAAOA,OAAOlB,KAAK,KAAKlB,2BAA2BoC,OAAOF,EAAE,KAAK;AACnE;AAEA,6EAA6E,GAC7E,OAAO,SAASG,mBACdC,OAAuC,EACvCC,EAAW;IAEX,MAAMC,OAAOF,QAAQG,MAAM,CAAC,CAACL,SAAW,CAACD,uBAAuBC;IAChE,OAAOG,KAAK;WAAIC;QAAM,aAAKT;KAA8B,GAAGS;AAC9D;AAEA;;;;;CAKC,GACD,OAAO,SAASE,qBACdC,IAAkB,EAClBL,OAAuC;IAEvC,OAAOA,QAAQM,IAAI,CAACT,0BAA0BQ,KAAKF,MAAM,CAAC1B,qBAAqB;WAAI4B;KAAK;AAC1F;AAEA;;;;;CAKC,GACD,OAAO,SAASE,WACdC,KAAmB;IAEnB,MAAMC,QAAQD,MAAML,MAAM,CAAC,CAACO,OAASA,KAAK1C,MAAM,KAAK,UAAUS,kBAAkBiC;IACjF,OAAO;QAAEC,OAAOF,MAAMG,MAAM;QAAEJ,OAAOC;IAAM;AAC7C"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { InteractionRecipe, InteractionRecipeInput } from '@aglyn/aglyn/plugin-manager/interaction-recipes';
|
|
18
|
+
export declare const CRM_ACTION_RECIPE_IDS: readonly ["welcomeNewLead", "followUpWonDeal", "reengageStaleLead", "tagByForm"];
|
|
19
|
+
export type CrmActionRecipeId = (typeof CRM_ACTION_RECIPE_IDS)[number];
|
|
20
|
+
/** What a recipe is handed before it builds — today only the form `tagByForm` reads. */
|
|
21
|
+
export type CrmActionRecipeInput = InteractionRecipeInput;
|
|
22
|
+
export interface CrmActionRecipe extends InteractionRecipe {
|
|
23
|
+
id: CrmActionRecipeId;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* How long the stale-lead recipe holds before it decides a lead has gone
|
|
27
|
+
* quiet. A week: long enough that a rep who is working the lead has had a
|
|
28
|
+
* chance to move its stage, short enough that a lead nobody touched is
|
|
29
|
+
* still warm when the reminder lands.
|
|
30
|
+
*/
|
|
31
|
+
export declare const STALE_LEAD_WAIT_MINUTES: number;
|
|
32
|
+
/**
|
|
33
|
+
* The tag a form's captures get: the form's own name, cut to the tag cap.
|
|
34
|
+
* The name is what the person calls the form, so it is the tag they would
|
|
35
|
+
* have typed; the editor is open to change it before anything is saved.
|
|
36
|
+
*/
|
|
37
|
+
export declare function crmRecipeTagForForm(formName: string): string;
|
|
38
|
+
export declare const CRM_ACTION_RECIPES: readonly CrmActionRecipe[];
|
|
39
|
+
/** The recipe with this id, or null for a string that names none. */
|
|
40
|
+
export declare function crmActionRecipe(id: unknown): CrmActionRecipe | null;
|
|
@@ -0,0 +1,259 @@
|
|
|
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
|
+
*/ import { CONTACT_TAG_MAX_LENGTH } from "@aglyn/aglyn/app-utils/crm-kinds";
|
|
17
|
+
/*
|
|
18
|
+
* THE CRM'S RECIPES (AGL-2626).
|
|
19
|
+
*
|
|
20
|
+
* A recipe is a ready-to-edit automation — a trigger, its conditions and an
|
|
21
|
+
* ordered step list — built from the vocabulary the automation editor already
|
|
22
|
+
* offers and handed to that editor as a draft. Nothing is written until the
|
|
23
|
+
* person saves, and every field is theirs to change first: the recipe decides
|
|
24
|
+
* where the editor starts, not what the site runs.
|
|
25
|
+
*
|
|
26
|
+
* The CRM's own, because what each one is FOR is the CRM's: who a new lead is,
|
|
27
|
+
* when a deal is won, when a lead has gone quiet. They reach the automation
|
|
28
|
+
* editor through the platform's recipe seam
|
|
29
|
+
* (`plugin-manager/interaction-recipes`, AGL-3080), registered from this
|
|
30
|
+
* plugin's declarations; the org hub's Recipes card and its install routes
|
|
31
|
+
* read them here. Their ids are declared in `plugins.config.json`
|
|
32
|
+
* (`interactionRecipes`) because a stored automation names one.
|
|
33
|
+
*
|
|
34
|
+
* Definitions only. Three of the four are complete as written. `tagByForm`
|
|
35
|
+
* needs a form, and a form belongs to a site, so the picker that supplies it
|
|
36
|
+
* is the editor's business and host-scoped by nature. That is the seam an
|
|
37
|
+
* org-level mount keeps: the recipes are the same at either level, and only
|
|
38
|
+
* the form picker knows where it is.
|
|
39
|
+
*/ export const CRM_ACTION_RECIPE_IDS = [
|
|
40
|
+
'welcomeNewLead',
|
|
41
|
+
'followUpWonDeal',
|
|
42
|
+
'reengageStaleLead',
|
|
43
|
+
'tagByForm'
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* The field a resumed wait carries in its scope when it ended on the CLOCK
|
|
47
|
+
* rather than on the event it watched for — the automation engine's word for
|
|
48
|
+
* it, written into the step guard the stale-lead recipe stores.
|
|
49
|
+
*/ const WAIT_TIMED_OUT_FIELD = '_waitTimedOut';
|
|
50
|
+
/**
|
|
51
|
+
* How long the stale-lead recipe holds before it decides a lead has gone
|
|
52
|
+
* quiet. A week: long enough that a rep who is working the lead has had a
|
|
53
|
+
* chance to move its stage, short enough that a lead nobody touched is
|
|
54
|
+
* still warm when the reminder lands.
|
|
55
|
+
*/ export const STALE_LEAD_WAIT_MINUTES = 7 * 24 * 60;
|
|
56
|
+
/**
|
|
57
|
+
* The tag a form's captures get: the form's own name, cut to the tag cap.
|
|
58
|
+
* The name is what the person calls the form, so it is the tag they would
|
|
59
|
+
* have typed; the editor is open to change it before anything is saved.
|
|
60
|
+
*/ export function crmRecipeTagForForm(formName) {
|
|
61
|
+
return formName.trim().slice(0, CONTACT_TAG_MAX_LENGTH);
|
|
62
|
+
}
|
|
63
|
+
export const CRM_ACTION_RECIPES = [
|
|
64
|
+
{
|
|
65
|
+
id: 'welcomeNewLead',
|
|
66
|
+
title: 'Welcome a new lead',
|
|
67
|
+
description: 'When a form makes a new lead: rotate in an owner, book a call for ' + 'tomorrow, send a thank-you, and tag them website.',
|
|
68
|
+
/*
|
|
69
|
+
* ON A NEW LEAD (AGL-3458), because that is the record a lead-routed form
|
|
70
|
+
* makes: under the one-record model (AGL-3232) a form with lead routing
|
|
71
|
+
* on files a LEAD and no contact, so a new-contact trigger would never
|
|
72
|
+
* reach the people this recipe is named for. `formId` is on the `lead`
|
|
73
|
+
* event exactly when a form filed the lead, so the condition keeps the
|
|
74
|
+
* recipe to forms and leaves a booking request to the booking's own
|
|
75
|
+
* confirmation. Every step below acts on the lead the event names when
|
|
76
|
+
* the workspace holds no contact for the person.
|
|
77
|
+
*
|
|
78
|
+
* The owner first, because the task that follows names no assignee and
|
|
79
|
+
* so goes to whoever owns the lead when it is created — the member the
|
|
80
|
+
* rotation just chose. Round robin rather than a named member: a recipe
|
|
81
|
+
* cannot know who is on the team, and the pool under CRM → Settings is
|
|
82
|
+
* the one place that does. On a workspace with no pool the step fails
|
|
83
|
+
* and the run continues, so the call, the email and the tag still land
|
|
84
|
+
* and the run history says who was not assigned.
|
|
85
|
+
*
|
|
86
|
+
* The email comes before any wait, which makes it an immediate reply to
|
|
87
|
+
* what the visitor just did — a transactional reply, sent from the org's
|
|
88
|
+
* own identity to the address the event carries, with no unsubscribe.
|
|
89
|
+
*
|
|
90
|
+
* Its words promise no response time. The org hub installs this recipe
|
|
91
|
+
* into a site without its editor opening, so the business never reads
|
|
92
|
+
* the sentence it is sending, and a "within a day" written here would be
|
|
93
|
+
* a commitment made on its behalf that nothing in the run keeps.
|
|
94
|
+
*/ build: ()=>({
|
|
95
|
+
recipe: 'welcomeNewLead',
|
|
96
|
+
name: 'Welcome a new lead',
|
|
97
|
+
trigger: {
|
|
98
|
+
event: 'lead',
|
|
99
|
+
conditions: [
|
|
100
|
+
{
|
|
101
|
+
field: 'formId',
|
|
102
|
+
op: 'notEmpty'
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
combinator: 'and'
|
|
106
|
+
},
|
|
107
|
+
steps: [
|
|
108
|
+
{
|
|
109
|
+
type: 'assignContactOwner',
|
|
110
|
+
roundRobin: true
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: 'createCrmTask',
|
|
114
|
+
title: 'Call the new lead',
|
|
115
|
+
kind: 'call',
|
|
116
|
+
dueInDays: 1
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'sendEmail',
|
|
120
|
+
subject: 'Thanks for getting in touch',
|
|
121
|
+
body: 'Hi {{firstName|there}},\n\nThanks for reaching out. We have your ' + 'message and will reply to this email address.'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'addContactTag',
|
|
125
|
+
tag: 'website'
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
enabled: true
|
|
129
|
+
})
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'followUpWonDeal',
|
|
133
|
+
title: 'Follow up a won deal',
|
|
134
|
+
description: 'When a deal is won — which makes the contact a Customer on its own — ' + 'book a check-in call a week out.',
|
|
135
|
+
/*
|
|
136
|
+
* No stage step (AGL-2641): the win itself floors the contact at
|
|
137
|
+
* `customer` before `dealWon` is announced, so a step setting the stage
|
|
138
|
+
* here would at best repeat the write and at worst move an evangelist
|
|
139
|
+
* back to customer — a SET, which is what the step is, and not the
|
|
140
|
+
* floor the win applies. The recipe books the follow-up and nothing
|
|
141
|
+
* else.
|
|
142
|
+
*/ build: ()=>({
|
|
143
|
+
recipe: 'followUpWonDeal',
|
|
144
|
+
name: 'Follow up a won deal',
|
|
145
|
+
trigger: {
|
|
146
|
+
event: 'dealWon'
|
|
147
|
+
},
|
|
148
|
+
steps: [
|
|
149
|
+
{
|
|
150
|
+
type: 'createCrmTask',
|
|
151
|
+
title: 'Check in with the new customer',
|
|
152
|
+
kind: 'call',
|
|
153
|
+
dueInDays: 7
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
enabled: true
|
|
157
|
+
})
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'reengageStaleLead',
|
|
161
|
+
title: 'Re-engage a stale lead',
|
|
162
|
+
description: 'When a contact becomes a lead: wait a week, and if their stage has ' + 'not moved, book a call to bring them back.',
|
|
163
|
+
/*
|
|
164
|
+
* "Unless the stage moved on" is the wait's own event: the flow watches
|
|
165
|
+
* for the next stage change on this person and gives up after a week.
|
|
166
|
+
* Resumed by the clock, the run carries `_waitTimedOut`, and the task
|
|
167
|
+
* step's guard reads it; resumed by the event, the field is absent and
|
|
168
|
+
* the task is skipped — the lead was worked, and nobody is told to
|
|
169
|
+
* re-engage somebody who just moved to Sales qualified.
|
|
170
|
+
*/ build: ()=>({
|
|
171
|
+
recipe: 'reengageStaleLead',
|
|
172
|
+
name: 'Re-engage a stale lead',
|
|
173
|
+
trigger: {
|
|
174
|
+
event: 'contactStageChanged',
|
|
175
|
+
conditions: [
|
|
176
|
+
{
|
|
177
|
+
field: 'lifecycleStage',
|
|
178
|
+
op: 'equals',
|
|
179
|
+
value: 'lead'
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
combinator: 'and'
|
|
183
|
+
},
|
|
184
|
+
steps: [
|
|
185
|
+
{
|
|
186
|
+
type: 'waitForEvent',
|
|
187
|
+
eventName: 'contactStageChanged',
|
|
188
|
+
timeoutMinutes: STALE_LEAD_WAIT_MINUTES
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: 'createCrmTask',
|
|
192
|
+
title: 'Re-engage a lead that has gone quiet',
|
|
193
|
+
kind: 'call',
|
|
194
|
+
dueInDays: 1,
|
|
195
|
+
when: {
|
|
196
|
+
conditions: [
|
|
197
|
+
{
|
|
198
|
+
field: WAIT_TIMED_OUT_FIELD,
|
|
199
|
+
op: 'notEmpty'
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
enabled: true
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'tagByForm',
|
|
210
|
+
title: 'Tag by form',
|
|
211
|
+
description: 'When a form you pick makes a new lead or contact: tag them with the ' + 'form’s name.',
|
|
212
|
+
picks: {
|
|
213
|
+
kind: 'form',
|
|
214
|
+
label: 'Form',
|
|
215
|
+
plural: 'forms',
|
|
216
|
+
prompt: 'Pick the form whose new leads or contacts get the tag.',
|
|
217
|
+
none: 'This site has no forms yet. Add one in the besigner, then come back.'
|
|
218
|
+
},
|
|
219
|
+
/*
|
|
220
|
+
* The event follows the form's routing (AGL-3458): a lead-routed form
|
|
221
|
+
* makes a lead and no contact, so keyed on `contactCreated` it would
|
|
222
|
+
* never fire. The Forms plugin shares the routing as the picked form's
|
|
223
|
+
* `routesLeads` fact; both events carry `formId` when a form made the
|
|
224
|
+
* record.
|
|
225
|
+
*/ build: (input)=>{
|
|
226
|
+
var _ref;
|
|
227
|
+
var _form_facts;
|
|
228
|
+
const form = input == null ? void 0 : input.picked;
|
|
229
|
+
return {
|
|
230
|
+
recipe: 'tagByForm',
|
|
231
|
+
name: form ? `Tag ${form.name.trim()} submissions` : 'Tag by form',
|
|
232
|
+
trigger: {
|
|
233
|
+
event: (form == null ? void 0 : (_form_facts = form.facts) == null ? void 0 : _form_facts['routesLeads']) === true ? 'lead' : 'contactCreated',
|
|
234
|
+
conditions: [
|
|
235
|
+
{
|
|
236
|
+
field: 'formId',
|
|
237
|
+
op: 'equals',
|
|
238
|
+
value: (_ref = form == null ? void 0 : form.id) != null ? _ref : ''
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
combinator: 'and'
|
|
242
|
+
},
|
|
243
|
+
steps: [
|
|
244
|
+
{
|
|
245
|
+
type: 'addContactTag',
|
|
246
|
+
tag: form ? crmRecipeTagForForm(form.name) : ''
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
enabled: true
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
];
|
|
254
|
+
/** The recipe with this id, or null for a string that names none. */ export function crmActionRecipe(id) {
|
|
255
|
+
var _CRM_ACTION_RECIPES_find;
|
|
256
|
+
return (_CRM_ACTION_RECIPES_find = CRM_ACTION_RECIPES.find((recipe)=>recipe.id === id)) != null ? _CRM_ACTION_RECIPES_find : null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//# sourceMappingURL=crm-recipes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/crm-recipes.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 { CONTACT_TAG_MAX_LENGTH } from '@aglyn/aglyn/app-utils/crm-kinds'\nimport type {\n InteractionRecipe,\n InteractionRecipeInput,\n} from '@aglyn/aglyn/plugin-manager/interaction-recipes'\n\n/*\n * THE CRM'S RECIPES (AGL-2626).\n *\n * A recipe is a ready-to-edit automation — a trigger, its conditions and an\n * ordered step list — built from the vocabulary the automation editor already\n * offers and handed to that editor as a draft. Nothing is written until the\n * person saves, and every field is theirs to change first: the recipe decides\n * where the editor starts, not what the site runs.\n *\n * The CRM's own, because what each one is FOR is the CRM's: who a new lead is,\n * when a deal is won, when a lead has gone quiet. They reach the automation\n * editor through the platform's recipe seam\n * (`plugin-manager/interaction-recipes`, AGL-3080), registered from this\n * plugin's declarations; the org hub's Recipes card and its install routes\n * read them here. Their ids are declared in `plugins.config.json`\n * (`interactionRecipes`) because a stored automation names one.\n *\n * Definitions only. Three of the four are complete as written. `tagByForm`\n * needs a form, and a form belongs to a site, so the picker that supplies it\n * is the editor's business and host-scoped by nature. That is the seam an\n * org-level mount keeps: the recipes are the same at either level, and only\n * the form picker knows where it is.\n */\nexport const CRM_ACTION_RECIPE_IDS = [\n 'welcomeNewLead',\n 'followUpWonDeal',\n 'reengageStaleLead',\n 'tagByForm',\n] as const\n\nexport type CrmActionRecipeId = (typeof CRM_ACTION_RECIPE_IDS)[number]\n\n/** What a recipe is handed before it builds — today only the form `tagByForm` reads. */\nexport type CrmActionRecipeInput = InteractionRecipeInput\n\nexport interface CrmActionRecipe extends InteractionRecipe {\n id: CrmActionRecipeId\n}\n\n/**\n * The field a resumed wait carries in its scope when it ended on the CLOCK\n * rather than on the event it watched for — the automation engine's word for\n * it, written into the step guard the stale-lead recipe stores.\n */\nconst WAIT_TIMED_OUT_FIELD = '_waitTimedOut'\n\n/**\n * How long the stale-lead recipe holds before it decides a lead has gone\n * quiet. A week: long enough that a rep who is working the lead has had a\n * chance to move its stage, short enough that a lead nobody touched is\n * still warm when the reminder lands.\n */\nexport const STALE_LEAD_WAIT_MINUTES = 7 * 24 * 60\n\n/**\n * The tag a form's captures get: the form's own name, cut to the tag cap.\n * The name is what the person calls the form, so it is the tag they would\n * have typed; the editor is open to change it before anything is saved.\n */\nexport function crmRecipeTagForForm(formName: string): string {\n return formName.trim().slice(0, CONTACT_TAG_MAX_LENGTH)\n}\n\nexport const CRM_ACTION_RECIPES: readonly CrmActionRecipe[] = [\n {\n id: 'welcomeNewLead',\n title: 'Welcome a new lead',\n description:\n 'When a form makes a new lead: rotate in an owner, book a call for ' +\n 'tomorrow, send a thank-you, and tag them website.',\n /*\n * ON A NEW LEAD (AGL-3458), because that is the record a lead-routed form\n * makes: under the one-record model (AGL-3232) a form with lead routing\n * on files a LEAD and no contact, so a new-contact trigger would never\n * reach the people this recipe is named for. `formId` is on the `lead`\n * event exactly when a form filed the lead, so the condition keeps the\n * recipe to forms and leaves a booking request to the booking's own\n * confirmation. Every step below acts on the lead the event names when\n * the workspace holds no contact for the person.\n *\n * The owner first, because the task that follows names no assignee and\n * so goes to whoever owns the lead when it is created — the member the\n * rotation just chose. Round robin rather than a named member: a recipe\n * cannot know who is on the team, and the pool under CRM → Settings is\n * the one place that does. On a workspace with no pool the step fails\n * and the run continues, so the call, the email and the tag still land\n * and the run history says who was not assigned.\n *\n * The email comes before any wait, which makes it an immediate reply to\n * what the visitor just did — a transactional reply, sent from the org's\n * own identity to the address the event carries, with no unsubscribe.\n *\n * Its words promise no response time. The org hub installs this recipe\n * into a site without its editor opening, so the business never reads\n * the sentence it is sending, and a \"within a day\" written here would be\n * a commitment made on its behalf that nothing in the run keeps.\n */\n build: () => ({\n recipe: 'welcomeNewLead',\n name: 'Welcome a new lead',\n trigger: {\n event: 'lead',\n conditions: [{ field: 'formId', op: 'notEmpty' }],\n combinator: 'and',\n },\n steps: [\n { type: 'assignContactOwner', roundRobin: true },\n {\n type: 'createCrmTask',\n title: 'Call the new lead',\n kind: 'call',\n dueInDays: 1,\n },\n {\n type: 'sendEmail',\n subject: 'Thanks for getting in touch',\n body:\n 'Hi {{firstName|there}},\\n\\nThanks for reaching out. We have your ' +\n 'message and will reply to this email address.',\n },\n { type: 'addContactTag', tag: 'website' },\n ],\n enabled: true,\n }),\n },\n {\n id: 'followUpWonDeal',\n title: 'Follow up a won deal',\n description:\n 'When a deal is won — which makes the contact a Customer on its own — ' +\n 'book a check-in call a week out.',\n /*\n * No stage step (AGL-2641): the win itself floors the contact at\n * `customer` before `dealWon` is announced, so a step setting the stage\n * here would at best repeat the write and at worst move an evangelist\n * back to customer — a SET, which is what the step is, and not the\n * floor the win applies. The recipe books the follow-up and nothing\n * else.\n */\n build: () => ({\n recipe: 'followUpWonDeal',\n name: 'Follow up a won deal',\n trigger: { event: 'dealWon' },\n steps: [\n {\n type: 'createCrmTask',\n title: 'Check in with the new customer',\n kind: 'call',\n dueInDays: 7,\n },\n ],\n enabled: true,\n }),\n },\n {\n id: 'reengageStaleLead',\n title: 'Re-engage a stale lead',\n description:\n 'When a contact becomes a lead: wait a week, and if their stage has ' +\n 'not moved, book a call to bring them back.',\n /*\n * \"Unless the stage moved on\" is the wait's own event: the flow watches\n * for the next stage change on this person and gives up after a week.\n * Resumed by the clock, the run carries `_waitTimedOut`, and the task\n * step's guard reads it; resumed by the event, the field is absent and\n * the task is skipped — the lead was worked, and nobody is told to\n * re-engage somebody who just moved to Sales qualified.\n */\n build: () => ({\n recipe: 'reengageStaleLead',\n name: 'Re-engage a stale lead',\n trigger: {\n event: 'contactStageChanged',\n conditions: [{ field: 'lifecycleStage', op: 'equals', value: 'lead' }],\n combinator: 'and',\n },\n steps: [\n {\n type: 'waitForEvent',\n eventName: 'contactStageChanged',\n timeoutMinutes: STALE_LEAD_WAIT_MINUTES,\n },\n {\n type: 'createCrmTask',\n title: 'Re-engage a lead that has gone quiet',\n kind: 'call',\n dueInDays: 1,\n when: {\n conditions: [{ field: WAIT_TIMED_OUT_FIELD, op: 'notEmpty' }],\n },\n },\n ],\n enabled: true,\n }),\n },\n {\n id: 'tagByForm',\n title: 'Tag by form',\n description:\n 'When a form you pick makes a new lead or contact: tag them with the ' +\n 'form’s name.',\n picks: {\n kind: 'form',\n label: 'Form',\n plural: 'forms',\n prompt: 'Pick the form whose new leads or contacts get the tag.',\n none: 'This site has no forms yet. Add one in the besigner, then come back.',\n },\n /*\n * The event follows the form's routing (AGL-3458): a lead-routed form\n * makes a lead and no contact, so keyed on `contactCreated` it would\n * never fire. The Forms plugin shares the routing as the picked form's\n * `routesLeads` fact; both events carry `formId` when a form made the\n * record.\n */\n build: (input) => {\n const form = input?.picked\n return {\n recipe: 'tagByForm',\n name: form ? `Tag ${form.name.trim()} submissions` : 'Tag by form',\n trigger: {\n event: form?.facts?.['routesLeads'] === true ? 'lead' : 'contactCreated',\n conditions: [{ field: 'formId', op: 'equals', value: form?.id ?? '' }],\n combinator: 'and',\n },\n steps: [{ type: 'addContactTag', tag: form ? crmRecipeTagForForm(form.name) : '' }],\n enabled: true,\n }\n },\n },\n]\n\n/** The recipe with this id, or null for a string that names none. */\nexport function crmActionRecipe(id: unknown): CrmActionRecipe | null {\n return CRM_ACTION_RECIPES.find((recipe) => recipe.id === id) ?? null\n}\n"],"names":["CONTACT_TAG_MAX_LENGTH","CRM_ACTION_RECIPE_IDS","WAIT_TIMED_OUT_FIELD","STALE_LEAD_WAIT_MINUTES","crmRecipeTagForForm","formName","trim","slice","CRM_ACTION_RECIPES","id","title","description","build","recipe","name","trigger","event","conditions","field","op","combinator","steps","type","roundRobin","kind","dueInDays","subject","body","tag","enabled","value","eventName","timeoutMinutes","when","picks","label","plural","prompt","none","input","form","picked","facts","crmActionRecipe","find"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,mCAAkC;AAMzE;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAMC,wBAAwB;IACnC;IACA;IACA;IACA;CACD,CAAS;AAWV;;;;CAIC,GACD,MAAMC,uBAAuB;AAE7B;;;;;CAKC,GACD,OAAO,MAAMC,0BAA0B,IAAI,KAAK,GAAE;AAElD;;;;CAIC,GACD,OAAO,SAASC,oBAAoBC,QAAgB;IAClD,OAAOA,SAASC,IAAI,GAAGC,KAAK,CAAC,GAAGP;AAClC;AAEA,OAAO,MAAMQ,qBAAiD;IAC5D;QACEC,IAAI;QACJC,OAAO;QACPC,aACE,uEACA;QACF;;;;;;;;;;;;;;;;;;;;;;;;;;KA0BC,GACDC,OAAO,IAAO,CAAA;gBACZC,QAAQ;gBACRC,MAAM;gBACNC,SAAS;oBACPC,OAAO;oBACPC,YAAY;wBAAC;4BAAEC,OAAO;4BAAUC,IAAI;wBAAW;qBAAE;oBACjDC,YAAY;gBACd;gBACAC,OAAO;oBACL;wBAAEC,MAAM;wBAAsBC,YAAY;oBAAK;oBAC/C;wBACED,MAAM;wBACNZ,OAAO;wBACPc,MAAM;wBACNC,WAAW;oBACb;oBACA;wBACEH,MAAM;wBACNI,SAAS;wBACTC,MACE,sEACA;oBACJ;oBACA;wBAAEL,MAAM;wBAAiBM,KAAK;oBAAU;iBACzC;gBACDC,SAAS;YACX,CAAA;IACF;IACA;QACEpB,IAAI;QACJC,OAAO;QACPC,aACE,0EACA;QACF;;;;;;;KAOC,GACDC,OAAO,IAAO,CAAA;gBACZC,QAAQ;gBACRC,MAAM;gBACNC,SAAS;oBAAEC,OAAO;gBAAU;gBAC5BK,OAAO;oBACL;wBACEC,MAAM;wBACNZ,OAAO;wBACPc,MAAM;wBACNC,WAAW;oBACb;iBACD;gBACDI,SAAS;YACX,CAAA;IACF;IACA;QACEpB,IAAI;QACJC,OAAO;QACPC,aACE,wEACA;QACF;;;;;;;KAOC,GACDC,OAAO,IAAO,CAAA;gBACZC,QAAQ;gBACRC,MAAM;gBACNC,SAAS;oBACPC,OAAO;oBACPC,YAAY;wBAAC;4BAAEC,OAAO;4BAAkBC,IAAI;4BAAUW,OAAO;wBAAO;qBAAE;oBACtEV,YAAY;gBACd;gBACAC,OAAO;oBACL;wBACEC,MAAM;wBACNS,WAAW;wBACXC,gBAAgB7B;oBAClB;oBACA;wBACEmB,MAAM;wBACNZ,OAAO;wBACPc,MAAM;wBACNC,WAAW;wBACXQ,MAAM;4BACJhB,YAAY;gCAAC;oCAAEC,OAAOhB;oCAAsBiB,IAAI;gCAAW;6BAAE;wBAC/D;oBACF;iBACD;gBACDU,SAAS;YACX,CAAA;IACF;IACA;QACEpB,IAAI;QACJC,OAAO;QACPC,aACE,yEACA;QACFuB,OAAO;YACLV,MAAM;YACNW,OAAO;YACPC,QAAQ;YACRC,QAAQ;YACRC,MAAM;QACR;QACA;;;;;;KAMC,GACD1B,OAAO,CAAC2B;;gBAMKC;YALX,MAAMA,OAAOD,yBAAAA,MAAOE,MAAM;YAC1B,OAAO;gBACL5B,QAAQ;gBACRC,MAAM0B,OAAO,CAAC,IAAI,EAAEA,KAAK1B,IAAI,CAACR,IAAI,GAAG,YAAY,CAAC,GAAG;gBACrDS,SAAS;oBACPC,OAAOwB,CAAAA,yBAAAA,cAAAA,KAAME,KAAK,qBAAXF,WAAa,CAAC,cAAc,MAAK,OAAO,SAAS;oBACxDvB,YAAY;wBAAC;4BAAEC,OAAO;4BAAUC,IAAI;4BAAUW,KAAK,UAAEU,wBAAAA,KAAM/B,EAAE,mBAAI;wBAAG;qBAAE;oBACtEW,YAAY;gBACd;gBACAC,OAAO;oBAAC;wBAAEC,MAAM;wBAAiBM,KAAKY,OAAOpC,oBAAoBoC,KAAK1B,IAAI,IAAI;oBAAG;iBAAE;gBACnFe,SAAS;YACX;QACF;IACF;CACD,CAAA;AAED,mEAAmE,GACnE,OAAO,SAASc,gBAAgBlC,EAAW;QAClCD;IAAP,QAAOA,2BAAAA,mBAAmBoC,IAAI,CAAC,CAAC/B,SAAWA,OAAOJ,EAAE,KAAKA,eAAlDD,2BAAyD;AAClE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 PluginRecordCountSource } from '@aglyn/aglyn/plugin-manager/plugin-record-counts';
|
|
18
|
+
/**
|
|
19
|
+
* The leads a site CAPTURED (AGL-3275), counted for another plugin's figure
|
|
20
|
+
* (AGL-3080). A lead is one document per person under the organization,
|
|
21
|
+
* stamped in `capturedByHostIds` with every site that met them — so this is
|
|
22
|
+
* exactly the population a site's own lead outcomes are drawn from.
|
|
23
|
+
*
|
|
24
|
+
* Not `visibleTo`: that also names the sites a consent group shares a person
|
|
25
|
+
* with. The price is that a collaborator scoped to one site cannot run the
|
|
26
|
+
* count — the rules prove a lead read by `visibleTo` — and the reader
|
|
27
|
+
* withholds the figure for them.
|
|
28
|
+
*/
|
|
29
|
+
export declare const leadRecordCountSource: PluginRecordCountSource;
|
|
30
|
+
/**
|
|
31
|
+
* The organization's contacts — ONE row per person for every site in it, so
|
|
32
|
+
* the count crosses sites, which the reader is told.
|
|
33
|
+
*/
|
|
34
|
+
export declare const contactRecordCountSource: PluginRecordCountSource;
|
|
35
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */
|
|
36
|
+
export declare function registerCrmRecordCounts(): void;
|