@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +13 -13
- package/src/lib/components/activity-list.js +12 -5
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.d.ts +1 -4
- package/src/lib/components/companies-bulk-bar.js +7 -20
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.d.ts +3 -3
- package/src/lib/components/companies-section.js +149 -46
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +281 -20
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-properties-card.js +99 -8
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +3 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +33 -10
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +0 -69
- package/src/lib/components/contact-properties-card.js +350 -60
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-reports-to-field.d.ts +46 -0
- package/src/lib/components/contact-reports-to-field.js +67 -0
- package/src/lib/components/contact-reports-to-field.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
- package/src/lib/components/contacts-bulk-bar.js +7 -19
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +46 -33
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-call-actions.d.ts +10 -0
- package/src/lib/components/crm-call-actions.js +26 -9
- package/src/lib/components/crm-call-actions.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +15 -0
- package/src/lib/components/crm-record-picker.js +16 -10
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
- package/src/lib/components/crm-transfer-buttons.js +167 -0
- package/src/lib/components/crm-transfer-buttons.js.map +1 -0
- package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
- package/src/lib/components/deal-contact-roles-card.js +368 -0
- package/src/lib/components/deal-contact-roles-card.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +3 -2
- package/src/lib/components/deal-detail-page.js +25 -6
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.d.ts +10 -0
- package/src/lib/components/deal-edit-drawer.js +219 -10
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-properties-card.d.ts +7 -0
- package/src/lib/components/deal-properties-card.js +34 -3
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.d.ts +4 -0
- package/src/lib/components/deal-stage-card.js +10 -2
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.d.ts +0 -3
- package/src/lib/components/deals-bulk-bar.js +7 -20
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/deals-section.d.ts +3 -3
- package/src/lib/components/deals-section.js +123 -63
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +31 -17
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-convert-dialog.d.ts +4 -0
- package/src/lib/components/lead-convert-dialog.js +23 -1
- package/src/lib/components/lead-convert-dialog.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +8 -2
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-profile-fields.d.ts +71 -0
- package/src/lib/components/lead-profile-fields.js +386 -0
- package/src/lib/components/lead-profile-fields.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +2 -1
- package/src/lib/components/lead-properties-card.js +85 -176
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-source-select.d.ts +4 -23
- package/src/lib/components/lead-source-select.js +9 -73
- package/src/lib/components/lead-source-select.js.map +1 -1
- package/src/lib/components/lead-status-chip.d.ts +9 -3
- package/src/lib/components/lead-status-chip.js +8 -4
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-status-options.d.ts +20 -0
- package/src/lib/components/lead-status-options.js +51 -0
- package/src/lib/components/lead-status-options.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
- package/src/lib/components/lead-unqualify-dialog.js +50 -18
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.d.ts +0 -3
- package/src/lib/components/leads-bulk-bar.js +29 -29
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +120 -68
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.d.ts +4 -0
- package/src/lib/components/linked-deals-card.js +47 -3
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.js +43 -3
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +8 -0
- package/src/lib/components/new-contact-drawer.js +108 -6
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/new-lead-drawer.d.ts +9 -2
- package/src/lib/components/new-lead-drawer.js +88 -121
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/picklist-select.d.ts +36 -0
- package/src/lib/components/picklist-select.js +114 -0
- package/src/lib/components/picklist-select.js.map +1 -0
- package/src/lib/components/picklist-values-card.d.ts +34 -0
- package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
- package/src/lib/components/picklist-values-card.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
- package/src/lib/components/pipeline-stages-dialog.js +26 -4
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/pipelines-dialog.js +15 -6
- package/src/lib/components/pipelines-dialog.js.map +1 -1
- package/src/lib/components/recent-activity-feed.js +20 -5
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/recipes-card.js +4 -2
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-tasks-card.js +15 -8
- package/src/lib/components/record-tasks-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.d.ts +6 -0
- package/src/lib/components/reports/forecast-card.js +101 -3
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
- package/src/lib/components/reports/lead-funnel-card.js +9 -5
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.d.ts +23 -0
- package/src/lib/components/reports/lead-source-card.js +318 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -0
- package/src/lib/components/reports/report-export.js +1 -1
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports-section.js +8 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
- package/src/lib/components/sharing-rule-drawer.js +108 -19
- package/src/lib/components/sharing-rule-drawer.js.map +1 -1
- package/src/lib/components/sharing-rules-card.js +11 -3
- package/src/lib/components/sharing-rules-card.js.map +1 -1
- package/src/lib/components/task-cells.d.ts +9 -1
- package/src/lib/components/task-cells.js +9 -4
- package/src/lib/components/task-cells.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +111 -43
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
- package/src/lib/components/tasks-bulk-bar.js +7 -20
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
- package/src/lib/components/tasks-calendar.d.ts +3 -0
- package/src/lib/components/tasks-calendar.js +8 -1
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.d.ts +3 -3
- package/src/lib/components/tasks-section.js +58 -43
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +2 -9
- package/src/lib/constants/api-routes.js +1 -7
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +11 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +14 -0
- package/src/lib/constants/company-filters.js +51 -1
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +2 -0
- package/src/lib/constants/contact-filters.js +18 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +22 -1
- package/src/lib/constants/deal-filters.js +67 -4
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +3 -0
- package/src/lib/declarations.console-server.js +35 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +35 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +11 -0
- package/src/lib/declarations.server.js +92 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-company-picklists.d.ts +15 -0
- package/src/lib/hooks/use-company-picklists.js +55 -0
- package/src/lib/hooks/use-company-picklists.js.map +1 -0
- package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
- package/src/lib/hooks/use-crm-picklist.js +53 -0
- package/src/lib/hooks/use-crm-picklist.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
- package/src/lib/hooks/use-crm-record-names.js +45 -19
- package/src/lib/hooks/use-crm-record-names.js.map +1 -1
- package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
- package/src/lib/hooks/use-crm-task-picklists.js +45 -0
- package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +14 -0
- package/src/lib/hooks/use-deals.js +29 -0
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
- package/src/lib/hooks/use-lead-picklists.js +46 -0
- package/src/lib/hooks/use-lead-picklists.js.map +1 -0
- package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
- package/src/lib/hooks/use-lead-source-picklist.js +12 -22
- package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
- package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
- package/src/lib/hooks/use-lead-status-picklist.js +40 -0
- package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
- package/src/lib/model/companies.d.ts +32 -3
- package/src/lib/model/companies.js +133 -16
- package/src/lib/model/companies.js.map +1 -1
- package/src/lib/model/contact-merge.d.ts +27 -3
- package/src/lib/model/contact-merge.js +81 -7
- package/src/lib/model/contact-merge.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +17 -0
- package/src/lib/model/contact-record.js +15 -1
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +31 -2
- package/src/lib/model/contact-update.js +14 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +5 -0
- package/src/lib/model/crm-list-query.js +8 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +244 -0
- package/src/lib/model/crm-recipes.js.map +1 -0
- package/src/lib/model/crm-record-counts.d.ts +36 -0
- package/src/lib/model/crm-record-counts.js +54 -0
- package/src/lib/model/crm-record-counts.js.map +1 -0
- package/src/lib/model/crm-record-lists.d.ts +23 -0
- package/src/lib/model/crm-record-lists.js +161 -0
- package/src/lib/model/crm-record-lists.js.map +1 -0
- package/src/lib/model/crm-reports.d.ts +73 -3
- package/src/lib/model/crm-reports.js +120 -1
- package/src/lib/model/crm-reports.js.map +1 -1
- package/src/lib/model/crm-sharing.d.ts +21 -3
- package/src/lib/model/crm-sharing.js +27 -6
- package/src/lib/model/crm-sharing.js.map +1 -1
- package/src/lib/model/deal-board-model.d.ts +8 -2
- package/src/lib/model/deal-board-model.js +15 -2
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +45 -0
- package/src/lib/model/deal-contact-roles.js +73 -0
- package/src/lib/model/deal-contact-roles.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +56 -0
- package/src/lib/model/deal-form-model.js +79 -7
- package/src/lib/model/deal-form-model.js.map +1 -1
- package/src/lib/model/download-text-file.d.ts +23 -0
- package/src/lib/model/download-text-file.js +34 -0
- package/src/lib/model/download-text-file.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +36 -2
- package/src/lib/model/lead-filters.js +123 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +38 -0
- package/src/lib/model/lead-source-direction.js +52 -0
- package/src/lib/model/lead-source-direction.js.map +1 -0
- package/src/lib/model/picklist-values.d.ts +119 -0
- package/src/lib/model/picklist-values.js +256 -0
- package/src/lib/model/picklist-values.js.map +1 -0
- package/src/lib/model/record-facts-disclosed.d.ts +81 -0
- package/src/lib/model/record-facts-disclosed.js +281 -0
- package/src/lib/model/record-facts-disclosed.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +215 -34
- package/src/lib/model/record-facts.js +307 -128
- package/src/lib/model/record-facts.js.map +1 -1
- package/src/lib/model/task-picklists.d.ts +52 -0
- package/src/lib/model/task-picklists.js +83 -0
- package/src/lib/model/task-picklists.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +13 -0
- package/src/lib/model/task-routes.js +23 -4
- package/src/lib/model/task-routes.js.map +1 -1
- package/src/lib/plan-entitlements.js +6 -4
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts.js +220 -14
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +14 -1
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-companies.js +123 -14
- package/src/lib/server/api-v1/crm-companies.js.map +1 -1
- package/src/lib/server/api-v1/crm-deals.js +291 -26
- package/src/lib/server/api-v1/crm-deals.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +144 -38
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-tasks.js +75 -33
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +0 -11
- package/src/lib/server/api-v1/openapi.js +171 -16
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/assign-contact-owner.d.ts +162 -0
- package/src/lib/server/assign-contact-owner.js +340 -0
- package/src/lib/server/assign-contact-owner.js.map +1 -0
- package/src/lib/server/associate-company-by-domain.d.ts +70 -0
- package/src/lib/server/associate-company-by-domain.js +153 -0
- package/src/lib/server/associate-company-by-domain.js.map +1 -0
- package/src/lib/server/automation-steps.d.ts +139 -0
- package/src/lib/server/automation-steps.js +472 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.js +65 -21
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/capture-host-contact.d.ts +104 -0
- package/src/lib/server/capture-host-contact.js +151 -0
- package/src/lib/server/capture-host-contact.js.map +1 -0
- package/src/lib/server/company-children.d.ts +25 -0
- package/src/lib/server/company-children.js +42 -0
- package/src/lib/server/company-children.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +18 -0
- package/src/lib/server/company-delete.js +44 -21
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/contact-email-history.js +21 -19
- package/src/lib/server/contact-email-history.js.map +1 -1
- package/src/lib/server/contact-remove.js +27 -2
- package/src/lib/server/contact-remove.js.map +1 -1
- package/src/lib/server/contact-reports-to.d.ts +61 -0
- package/src/lib/server/contact-reports-to.js +114 -0
- package/src/lib/server/contact-reports-to.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +42 -1
- package/src/lib/server/contact-update.js +204 -23
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +46 -1
- package/src/lib/server/convert-host-lead.js +173 -15
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +12 -5
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-record-indexes.d.ts +45 -0
- package/src/lib/server/crm-record-indexes.js +78 -0
- package/src/lib/server/crm-record-indexes.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +9 -1
- package/src/lib/server/crm-sharing.js +23 -3
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/deal-contact-roles.d.ts +27 -0
- package/src/lib/server/deal-contact-roles.js +89 -0
- package/src/lib/server/deal-contact-roles.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +5 -0
- package/src/lib/server/lead-convert.js +19 -3
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +17 -2
- package/src/lib/server/lead-create.js +46 -14
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +6 -0
- package/src/lib/server/lead-nurturing.js +57 -20
- package/src/lib/server/lead-nurturing.js.map +1 -1
- package/src/lib/server/lead-picklists.d.ts +35 -0
- package/src/lib/server/lead-picklists.js +52 -0
- package/src/lib/server/lead-picklists.js.map +1 -0
- package/src/lib/server/lead-source-picklist.d.ts +1 -13
- package/src/lib/server/lead-source-picklist.js +6 -33
- package/src/lib/server/lead-source-picklist.js.map +1 -1
- package/src/lib/server/merge-contacts.js +22 -4
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +53 -0
- package/src/lib/server/person-eraser.js +149 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/person-records.d.ts +69 -0
- package/src/lib/server/person-records.js +317 -0
- package/src/lib/server/person-records.js.map +1 -0
- package/src/lib/server/person-refund.d.ts +97 -0
- package/src/lib/server/person-refund.js +221 -0
- package/src/lib/server/person-refund.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +21 -0
- package/src/lib/server/picklist-values.js +308 -0
- package/src/lib/server/picklist-values.js.map +1 -0
- package/src/lib/server/read-picklist.d.ts +44 -0
- package/src/lib/server/read-picklist.js +65 -0
- package/src/lib/server/read-picklist.js.map +1 -0
- package/src/lib/server/recipe-routes.js +15 -7
- package/src/lib/server/recipe-routes.js.map +1 -1
- package/src/lib/server/record-email-state.js +7 -1
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.d.ts +3 -0
- package/src/lib/server/record-facts.js +233 -31
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-origin.d.ts +27 -0
- package/src/lib/server/record-origin.js +104 -0
- package/src/lib/server/record-origin.js.map +1 -0
- package/src/lib/server/record-timeline.js +15 -2
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/suite-gate.js +4 -3
- package/src/lib/server/suite-gate.js.map +1 -1
- package/src/lib/server/task-routes.js +30 -16
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/record-facts.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 {\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_SOURCE_LABELS,\n CRM_ACTIVITY_KIND_LABELS,\n CRM_EMAIL_DELIVERY_STATE_LABELS,\n CRM_LEAD_STATUS_LABELS,\n CRM_TASK_KIND_LABELS,\n contactDisplayName,\n crmLeadStatus,\n customImportTarget,\n dealStageById,\n fieldDefinitionObject,\n interactionsForGroup,\n isContactLifecycleStage,\n isCrmEmailDeliveryState,\n readContactFacet,\n type ConsentGroup,\n type ContactFieldDefinition,\n type ContactInteraction,\n type CrmActivity,\n type CrmCompany,\n type CrmDeal,\n type CrmFieldObject,\n type CrmPipeline,\n type CrmTask,\n} from '@aglyn/aglyn/server'\nimport {\n COMPANY_IMPORT_FIELD_LABELS,\n COMPANY_IMPORT_FIELDS,\n} from './crm-company-import'\nimport {\n CONTACT_IMPORT_FIELD_LABELS,\n CONTACT_IMPORT_FIELDS,\n} from './crm-import'\nimport { DEAL_IMPORT_FIELD_LABELS, DEAL_IMPORT_FIELDS } from './crm-deal-import'\nimport { LEAD_IMPORT_FIELD_LABELS, LEAD_IMPORT_FIELDS } from './crm-lead-import'\n\n/**\n * WHAT THE CRM TELLS ANOTHER PLUGIN ABOUT ONE OF ITS RECORDS (AGL-2917).\n *\n * The facts a reader on the core's record-facts seam reports: one contact,\n * company, deal or lead as a person on the team sees it, and the fields a\n * spreadsheet import may fill. The server half reads the documents and\n * applies who may see them (`server/record-facts.ts`); this half decides\n * which of their fields leave the CRM at all, so the list below is the whole\n * of what a caller — an assistant summarizing a record, an automation\n * deciding on one — can pass on.\n *\n * ## What never leaves\n *\n * No email address, phone number or postal address of anyone; no marketing\n * consent; no custom field VALUE; no team member's name or id; no document\n * id of any record; no file; no order or payment detail beyond a count. A\n * logged email carries its subject and a cut of its body, never the address\n * it went to or came from. An import catalog carries field keys, labels and\n * types, never a row of the file.\n *\n * Every text a person wrote into a record — a name, a job title, a tag, notes,\n * a logged activity, a capture's summary, a task's or a deal's title, a\n * reason — is reported as written, except that an email address or a phone\n * number inside it is replaced by a placeholder first ({@link crmFactProse}):\n * an import that put an address in the name column leaves no address behind.\n * What the workspace configured — pipeline, stage and field names — and a\n * company's domain are reported as they are. A postal address typed into a\n * note is not recognized and leaves as typed.\n *\n * ## Stable bytes\n *\n * Every builder writes its keys in one order, dates as UTC calendar days and\n * money as a currency code and a fixed-point amount, so the same records read\n * twice report the same facts — a caller may key a cache on them, and one\n * that does sees a change exactly when something it was told has changed.\n */\n\n/** The records a reader is registered for, by the seam's resource name. */\nexport const CRM_RECORD_FACTS_RESOURCES = {\n contact: 'crm.contact',\n company: 'crm.company',\n deal: 'crm.deal',\n lead: 'crm.lead',\n} as const\n\nexport type CrmRecordFactsKind = keyof typeof CRM_RECORD_FACTS_RESOURCES\n\n/** The import catalogs, read by collection name as the record id. */\nexport const CRM_IMPORT_FACTS_RESOURCE = 'crm.import'\n\nexport const CRM_IMPORT_FACTS_COLLECTIONS = ['contacts', 'companies', 'deals', 'leads'] as const\n\nexport type CrmImportFactsCollection = (typeof CRM_IMPORT_FACTS_COLLECTIONS)[number]\n\nexport function isCrmImportFactsCollection(value: unknown): value is CrmImportFactsCollection {\n return typeof value === 'string' && (CRM_IMPORT_FACTS_COLLECTIONS as readonly string[]).includes(value)\n}\n\n/** Newest timeline entries reported for a record. */\nexport const CRM_FACTS_TIMELINE_MAX = 12\n/** Open tasks reported for a record. */\nexport const CRM_FACTS_TASKS_MAX = 8\n/** Deals reported for a contact or a company. */\nexport const CRM_FACTS_DEALS_MAX = 5\n/** Tags reported for a record. */\nexport const CRM_FACTS_TAGS_MAX = 10\n/** How much of a logged activity's text, or a platform capture's summary, is reported. */\nexport const CRM_FACTS_TEXT_MAX = 280\n/** How much of a record's notes is reported. */\nexport const CRM_FACTS_NOTES_MAX = 600\n/** How long a name, a title or a subject may run. */\nexport const CRM_FACTS_LABEL_MAX = 120\n/** Custom fields an import catalog reports. */\nexport const CRM_FACTS_CUSTOM_FIELDS_MAX = 40\n\n/** One thing that happened, as a record's timeline reports it. */\nexport interface CrmTimelineFact {\n /** The UTC day it happened, `YYYY-MM-DD`. */\n on: string\n /** What happened: a logged kind (Call, Email) or the capture it came through (Form, Booking). */\n kind: string\n /** `outbound` or `inbound` for an email; absent otherwise. */\n direction?: string\n subject?: string\n text?: string\n outcome?: string\n /** How far a sent email got: Sent, Delivered, Opened, Clicked, Bounced, Complained. */\n delivery?: string\n}\n\n/** One task still to do. */\nexport interface CrmTaskFact {\n title: string\n kind: string\n priority: string\n /** The UTC day it is due, or `null` for a task with no due date. */\n due: string | null\n overdue: boolean\n}\n\n/** One deal, as a contact's or a company's facts report it. */\nexport interface CrmDealFact {\n title: string\n stage: string\n status: string\n amount: string | null\n expectedClose: string | null\n}\n\nexport interface CrmContactFacts {\n record: 'contact'\n name: string\n jobTitle: string\n company: string\n lifecycleStage: string\n tags: string[]\n /** The captures that met the person: Form, Booking, Customer. */\n sources: string[]\n orders: number\n lastPurchase: string | null\n since: string | null\n lastEmailEngagement: string | null\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n deals: CrmDealFact[]\n}\n\nexport interface CrmCompanyFacts {\n record: 'company'\n name: string\n domain: string\n industry: string\n tags: string[]\n people: number\n since: string | null\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n deals: CrmDealFact[]\n}\n\n/** One stage of a deal's pipeline, in order. */\nexport interface CrmStageFact {\n id: string\n name: string\n kind: 'open' | 'won' | 'lost'\n}\n\nexport interface CrmDealFacts {\n record: 'deal'\n title: string\n pipeline: string\n stages: CrmStageFact[]\n stageId: string\n stage: string\n status: string\n amount: string | null\n expectedClose: string | null\n inStageSince: string | null\n lostReason: string\n /** The name of the person the deal is with, as the deal copied it. */\n contact: string\n company: string\n products: number\n since: string | null\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n}\n\nexport interface CrmLeadFacts {\n record: 'lead'\n name: string\n status: string\n /** Where the lead was captured: Form, Booking, Sign-up, Import. */\n sources: string[]\n captures: number\n firstSeen: string | null\n lastSeen: string | null\n assigned: boolean\n converted: boolean\n unqualifiedReason: string\n notes: string\n timeline: CrmTimelineFact[]\n}\n\nexport type CrmRecordFacts = CrmContactFacts | CrmCompanyFacts | CrmDealFacts | CrmLeadFacts\n\n/** What a column of a file may hold, as an import field expects it. */\nexport type CrmImportFieldType = 'email' | 'phone' | 'number' | 'date' | 'yes-no' | 'url' | 'text'\n\n/** One field an import may fill. */\nexport interface CrmImportFieldFact {\n /** The mapping target: a standard field's key, or `custom:<key>`. */\n key: string\n label: string\n type: CrmImportFieldType\n required: boolean\n}\n\nexport interface CrmImportFacts {\n record: 'import'\n collection: CrmImportFactsCollection\n fields: CrmImportFieldFact[]\n}\n\nconst DAY_MS = 86_400_000\n\n/** A value as trimmed, single-spaced text of at most `max` characters. */\nexport function crmFactText(value: unknown, max: number): string {\n if (typeof value !== 'string') return ''\n const text = value.replace(/\\s+/g, ' ').trim()\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\n}\n\n/** Epoch millis, a `Date` or a stored timestamp, as epoch millis; `null` for anything else. */\nexport function crmFactMillis(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) && value > 0 ? value : null\n if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null\n const toMillis = (value as { toMillis?: () => number } | null | undefined)?.toMillis\n if (typeof toMillis === 'function') {\n const millis = toMillis.call(value)\n return Number.isFinite(millis) ? millis : null\n }\n return null\n}\n\n/** A UTC calendar day, `YYYY-MM-DD`, or `null`. */\nexport function crmFactDay(value: unknown): string | null {\n const millis = crmFactMillis(value)\n return millis === null ? null : new Date(millis).toISOString().slice(0, 10)\n}\n\n/** An amount as a currency code and a fixed-point figure: `USD 1250.00`. */\nexport function crmFactMoney(cents: unknown, currency: unknown): string | null {\n if (typeof cents !== 'number' || !Number.isFinite(cents)) return null\n const code = (typeof currency === 'string' && /^[a-z]{3}$/i.test(currency) ? currency : 'usd').toUpperCase()\n return `${code} ${(cents / 100).toFixed(2)}`\n}\n\n/** An email address written inside free text. */\nconst EMAIL_IN_PROSE = /[\\w.+-]+@[\\w-]+(?:\\.[\\w-]+)+/g\n/** A run of digits and the separators a phone number is written with. */\nconst DIGIT_RUN_IN_PROSE = /\\+?\\(?\\d[\\d\\s().-]{7,}\\d/g\n/** Calendar days, which a run of digits may be and a phone number is not. */\nconst DAYS_ONLY = /^\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{4}-\\d{2}-\\d{2})*$/\n\nexport const CRM_FACTS_EMAIL_PLACEHOLDER = '[email address]'\nexport const CRM_FACTS_PHONE_PLACEHOLDER = '[phone number]'\n\n/**\n * Free text the team wrote, as {@link crmFactText} reports it, with every\n * email address, and every run of ten digits or more that is not a list of\n * days, replaced by a placeholder. The fields that hold an address or a number\n * are never reported, and this keeps one typed into a note from leaving\n * either. Replaced before the cut, so a cut never halves an address into\n * something unrecognized.\n */\nexport function crmFactProse(value: unknown, max: number): string {\n if (typeof value !== 'string') return ''\n const scrubbed = value\n .replace(EMAIL_IN_PROSE, CRM_FACTS_EMAIL_PLACEHOLDER)\n .replace(DIGIT_RUN_IN_PROSE, (run) =>\n DAYS_ONLY.test(run.trim()) || run.replace(/\\D/g, '').length < 10 ? run : CRM_FACTS_PHONE_PLACEHOLDER,\n )\n return crmFactText(scrubbed, max)\n}\n\nfunction tagsOf(value: unknown): string[] {\n return Array.isArray(value)\n ? value\n .map((tag) => crmFactProse(tag, 40))\n .filter(Boolean)\n .slice(0, CRM_FACTS_TAGS_MAX)\n : []\n}\n\n/** A logged activity as a timeline entry. */\nexport function crmActivityFact(activity: Partial<CrmActivity>): CrmTimelineFact | null {\n const on = crmFactDay(activity.atMs)\n if (!on) return null\n const kind = activity.kind && CRM_ACTIVITY_KIND_LABELS[activity.kind] ? activity.kind : 'other'\n const fact: CrmTimelineFact = { on, kind: CRM_ACTIVITY_KIND_LABELS[kind] }\n if (kind === 'email' && (activity.direction === 'outbound' || activity.direction === 'inbound')) {\n fact.direction = activity.direction\n }\n const subject = crmFactProse(activity.subject ?? activity.threadSubject, CRM_FACTS_LABEL_MAX)\n if (subject) fact.subject = subject\n const text = crmFactProse(activity.body, CRM_FACTS_TEXT_MAX)\n if (text) fact.text = text\n const outcome = crmFactProse(activity.outcome, CRM_FACTS_LABEL_MAX)\n if (outcome) fact.outcome = outcome\n if (kind === 'email' && isCrmEmailDeliveryState(activity.deliveryState)) {\n fact.delivery = CRM_EMAIL_DELIVERY_STATE_LABELS[activity.deliveryState]\n }\n return fact\n}\n\n/** A capture the platform recorded on a contact, as a timeline entry. */\nexport function crmInteractionFact(interaction: Partial<ContactInteraction>): CrmTimelineFact | null {\n const on = crmFactDay(interaction.atMs)\n if (!on) return null\n const kind =\n interaction.type && CONTACT_SOURCE_LABELS[interaction.type] ? CONTACT_SOURCE_LABELS[interaction.type] : 'Capture'\n const text = crmFactProse(interaction.summary, CRM_FACTS_TEXT_MAX)\n return text ? { on, kind, text } : { on, kind }\n}\n\n/**\n * Timeline entries newest first, at most {@link CRM_FACTS_TIMELINE_MAX}. A\n * tie keeps the order the entries arrived in, so the same reads report the\n * same list.\n */\nexport function crmTimelineFacts(\n entries: ReadonlyArray<{ atMs: unknown; fact: CrmTimelineFact | null }>,\n): CrmTimelineFact[] {\n return entries\n .map((entry, index) => ({ at: crmFactMillis(entry.atMs) ?? 0, index, fact: entry.fact }))\n .filter((entry): entry is { at: number; index: number; fact: CrmTimelineFact } => entry.fact !== null)\n .sort((a, b) => b.at - a.at || a.index - b.index)\n .slice(0, CRM_FACTS_TIMELINE_MAX)\n .map((entry) => entry.fact)\n}\n\n/** The open tasks among `tasks`, soonest due first, undated last. */\nexport function crmOpenTaskFacts(tasks: ReadonlyArray<Partial<CrmTask>>, nowMs: number): CrmTaskFact[] {\n return tasks\n .filter((task) => task.status !== 'done' && crmFactText(task.title, CRM_FACTS_LABEL_MAX))\n .map((task, index) => ({ task, index, due: crmFactMillis(task.dueAtMs) }))\n .sort((a, b) => (a.due ?? Number.POSITIVE_INFINITY) - (b.due ?? Number.POSITIVE_INFINITY) || a.index - b.index)\n .slice(0, CRM_FACTS_TASKS_MAX)\n .map(({ task, due }) => ({\n title: crmFactProse(task.title, CRM_FACTS_LABEL_MAX),\n kind: task.kind && CRM_TASK_KIND_LABELS[task.kind] ? CRM_TASK_KIND_LABELS[task.kind] : 'To-do',\n priority: task.priority === 'high' || task.priority === 'low' ? task.priority : 'normal',\n due: due === null ? null : crmFactDay(due),\n // Overdue by the UTC day: a task due today is not overdue until tomorrow.\n overdue: due !== null && Math.floor(due / DAY_MS) < Math.floor(nowMs / DAY_MS),\n }))\n}\n\n/** One deal, with its stage named by the pipeline it is in. */\nexport function crmDealFact(deal: Partial<CrmDeal>, pipeline: CrmPipeline | null | undefined): CrmDealFact {\n const stage = dealStageById(pipeline ?? undefined, String(deal.stageId ?? ''))\n return {\n title: crmFactProse(deal.title, CRM_FACTS_LABEL_MAX),\n stage: crmFactText(stage?.name, CRM_FACTS_LABEL_MAX),\n status: deal.status === 'won' || deal.status === 'lost' ? deal.status : 'open',\n amount: crmFactMoney(deal.amountCents, deal.currency),\n expectedClose: crmFactDay(deal.expectedCloseAtMs),\n }\n}\n\n/** Deals, open ones first, then most recently updated. */\nexport function crmDealFacts(\n deals: ReadonlyArray<Partial<CrmDeal>>,\n pipelines: ReadonlyMap<string, CrmPipeline>,\n): CrmDealFact[] {\n return deals\n .map((deal, index) => ({ deal, index, updated: crmFactMillis(deal.updatedAt) ?? 0 }))\n .sort(\n (a, b) =>\n Number(a.deal.status !== 'open') - Number(b.deal.status !== 'open') ||\n b.updated - a.updated ||\n a.index - b.index,\n )\n .slice(0, CRM_FACTS_DEALS_MAX)\n .map(({ deal }) => crmDealFact(deal, pipelines.get(String(deal.pipelineId ?? ''))))\n}\n\nexport interface ContactFactsInput {\n row: Record<string, unknown>\n /** The holder whose facet the person is read through. */\n group: ConsentGroup\n activities: ReadonlyArray<Partial<CrmActivity>>\n tasks: ReadonlyArray<Partial<CrmTask>>\n deals: ReadonlyArray<Partial<CrmDeal>>\n pipelines: ReadonlyMap<string, CrmPipeline>\n nowMs: number\n}\n\nexport function contactFacts(input: ContactFactsInput): CrmContactFacts {\n const { row, group } = input\n const facet = readContactFacet(row, group.groupId)\n const sources = Object.entries(facet.sources ?? {})\n .filter(([, on]) => on === true)\n .map(([source]) => CONTACT_SOURCE_LABELS[source as keyof typeof CONTACT_SOURCE_LABELS] ?? '')\n .filter(Boolean)\n const interactions = interactionsForGroup(facet.interactions, group.hostIds)\n return {\n record: 'contact',\n name: crmFactProse(contactDisplayName(row, group.groupId), CRM_FACTS_LABEL_MAX),\n jobTitle: crmFactProse(facet.jobTitle, CRM_FACTS_LABEL_MAX),\n company: crmFactProse(facet.companyName, CRM_FACTS_LABEL_MAX),\n lifecycleStage: isContactLifecycleStage(facet.lifecycleStage)\n ? CONTACT_LIFECYCLE_STAGE_LABELS[facet.lifecycleStage]\n : '',\n tags: tagsOf(facet.tags),\n sources: [...new Set(sources)].sort(),\n orders: typeof facet.ordersCount === 'number' && facet.ordersCount > 0 ? Math.floor(facet.ordersCount) : 0,\n lastPurchase: crmFactDay(facet.lastPurchaseAtMs),\n since: crmFactDay(row['createdAt']),\n lastEmailEngagement: crmFactDay(facet.lastEmailEngagementAtMs),\n notes: crmFactProse(facet.notes, CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts([\n ...input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ...interactions.map((interaction) => ({ atMs: interaction.atMs, fact: crmInteractionFact(interaction) })),\n ]),\n openTasks: crmOpenTaskFacts(input.tasks, input.nowMs),\n deals: crmDealFacts(input.deals, input.pipelines),\n }\n}\n\nexport interface CompanyFactsInput {\n company: Partial<CrmCompany>\n activities: ReadonlyArray<Partial<CrmActivity>>\n tasks: ReadonlyArray<Partial<CrmTask>>\n deals: ReadonlyArray<Partial<CrmDeal>>\n pipelines: ReadonlyMap<string, CrmPipeline>\n nowMs: number\n}\n\nexport function companyFacts(input: CompanyFactsInput): CrmCompanyFacts {\n const { company } = input\n return {\n record: 'company',\n name: crmFactProse(company.name, CRM_FACTS_LABEL_MAX),\n domain: crmFactText(company.domain, CRM_FACTS_LABEL_MAX),\n industry: crmFactProse(company.industry, CRM_FACTS_LABEL_MAX),\n tags: tagsOf(company.tags),\n people:\n typeof company.contactsCount === 'number' && company.contactsCount > 0\n ? Math.floor(company.contactsCount)\n : 0,\n since: crmFactDay(company.createdAt),\n notes: crmFactProse(company.notes, CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts(\n input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ),\n openTasks: crmOpenTaskFacts(input.tasks, input.nowMs),\n deals: crmDealFacts(input.deals, input.pipelines),\n }\n}\n\nexport interface DealFactsInput {\n deal: Partial<CrmDeal> & { contactName?: unknown; companyName?: unknown }\n pipeline: CrmPipeline | null\n activities: ReadonlyArray<Partial<CrmActivity>>\n tasks: ReadonlyArray<Partial<CrmTask>>\n nowMs: number\n}\n\nexport function dealFacts(input: DealFactsInput): CrmDealFacts {\n const { deal, pipeline } = input\n const summary = crmDealFact(deal, pipeline)\n const stages = [...(pipeline?.stages ?? [])]\n .sort((a, b) => Number(a.order ?? 0) - Number(b.order ?? 0))\n .map((stage) => ({\n id: String(stage.id),\n name: crmFactText(stage.name, CRM_FACTS_LABEL_MAX),\n kind: stage.kind === 'won' || stage.kind === 'lost' ? stage.kind : ('open' as const),\n }))\n return {\n record: 'deal',\n title: summary.title,\n pipeline: crmFactText(pipeline?.name, CRM_FACTS_LABEL_MAX),\n stages,\n stageId: String(deal.stageId ?? ''),\n stage: summary.stage,\n status: summary.status,\n amount: summary.amount,\n expectedClose: summary.expectedClose,\n inStageSince: crmFactDay(deal.stageChangedAtMs),\n lostReason: crmFactProse(deal.lostReason, CRM_FACTS_LABEL_MAX),\n contact: crmFactProse(deal.contactName, CRM_FACTS_LABEL_MAX),\n company: crmFactProse(deal.companyName, CRM_FACTS_LABEL_MAX),\n products: Array.isArray(deal.lineItems) ? deal.lineItems.length : 0,\n since: crmFactDay(deal.createdAt),\n notes: crmFactProse(deal.notes, CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts(\n input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ),\n openTasks: crmOpenTaskFacts(input.tasks, input.nowMs),\n }\n}\n\n/** What a lead's capture surfaces are called, the lead history card's words. */\nfunction leadSourceFact(source: string): string {\n if (source === 'signup') return 'Sign-up'\n if (source === 'booking') return 'Booking'\n if (source === 'import') return CONTACT_SOURCE_LABELS.import\n if (source === 'form' || source.startsWith('form:')) return CONTACT_SOURCE_LABELS.form\n return 'Other'\n}\n\nexport interface LeadFactsInput {\n lead: Record<string, unknown>\n activities: ReadonlyArray<Partial<CrmActivity>>\n}\n\nexport function leadFacts(input: LeadFactsInput): CrmLeadFacts {\n const { lead } = input\n const rawSources = Array.isArray(lead['sources'])\n ? lead['sources']\n : typeof lead['source'] === 'string'\n ? [lead['source']]\n : []\n const captures = Number(lead['submissionCount'])\n return {\n record: 'lead',\n name: crmFactProse(lead['name'], CRM_FACTS_LABEL_MAX),\n status: CRM_LEAD_STATUS_LABELS[crmLeadStatus(lead as { status?: never })],\n sources: [...new Set(rawSources.map((source) => leadSourceFact(String(source))))].sort(),\n captures: Number.isFinite(captures) && captures > 0 ? Math.floor(captures) : 0,\n firstSeen: crmFactDay(lead['firstSeenAtMs']),\n lastSeen: crmFactDay(lead['lastSeenAtMs']),\n assigned: typeof lead['ownerUid'] === 'string' && lead['ownerUid'] !== '',\n converted: typeof lead['convertedContactId'] === 'string' && lead['convertedContactId'] !== '',\n unqualifiedReason: crmFactProse(lead['unqualifiedReason'], CRM_FACTS_LABEL_MAX),\n notes: crmFactProse(lead['notes'], CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts(\n input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ),\n }\n}\n\n/** The standard fields of each import, with the type a column must hold for each. */\nconst STANDARD_IMPORT_FIELDS: Record<\n CrmImportFactsCollection,\n { keys: readonly string[]; labels: Record<string, string>; required: string; types: Record<string, CrmImportFieldType> }\n> = {\n contacts: {\n keys: CONTACT_IMPORT_FIELDS,\n labels: CONTACT_IMPORT_FIELD_LABELS,\n required: 'email',\n types: { email: 'email', phone: 'phone', ownerEmail: 'email', marketingConsent: 'yes-no' },\n },\n companies: {\n keys: COMPANY_IMPORT_FIELDS,\n labels: COMPANY_IMPORT_FIELD_LABELS,\n required: 'name',\n types: { phone: 'phone', ownerEmail: 'email', website: 'url' },\n },\n deals: {\n keys: DEAL_IMPORT_FIELDS,\n labels: DEAL_IMPORT_FIELD_LABELS,\n required: 'title',\n types: { amount: 'number', ownerEmail: 'email', expectedClose: 'date' },\n },\n leads: {\n keys: LEAD_IMPORT_FIELDS,\n labels: LEAD_IMPORT_FIELD_LABELS,\n required: 'email',\n types: { email: 'email', ownerEmail: 'email' },\n },\n}\n\n/** The object a collection's custom fields describe; leads and deals import none. */\nconst CUSTOM_FIELD_OBJECT: Partial<Record<CrmImportFactsCollection, CrmFieldObject>> = {\n contacts: 'contact',\n companies: 'company',\n}\n\nconst CUSTOM_FIELD_TYPES: Record<string, CrmImportFieldType> = {\n number: 'number',\n date: 'date',\n checkbox: 'yes-no',\n url: 'url',\n}\n\n/**\n * The fields an import of `collection` may fill: the standard ones in the\n * mapping menu's order, then the org's active custom fields for the record\n * the collection holds, in their own order.\n */\nexport function importFacts(\n collection: CrmImportFactsCollection,\n customFields: ReadonlyArray<Partial<ContactFieldDefinition>>,\n): CrmImportFacts {\n const standard = STANDARD_IMPORT_FIELDS[collection]\n const object = CUSTOM_FIELD_OBJECT[collection]\n const custom = object\n ? customFields\n .filter((field) => field.key && !field.retiredAt && fieldDefinitionObject(field as ContactFieldDefinition) === object)\n .sort((a, b) => Number(a.order ?? 0) - Number(b.order ?? 0) || String(a.key).localeCompare(String(b.key)))\n .slice(0, CRM_FACTS_CUSTOM_FIELDS_MAX)\n .map((field) => ({\n key: customImportTarget(String(field.key)),\n label: crmFactText(field.label, CRM_FACTS_LABEL_MAX) || String(field.key),\n type: CUSTOM_FIELD_TYPES[String(field.type)] ?? ('text' as const),\n required: false,\n }))\n : []\n return {\n record: 'import',\n collection,\n fields: [\n ...standard.keys.map((key) => ({\n key,\n label: standard.labels[key] ?? key,\n type: standard.types[key] ?? ('text' as const),\n required: key === standard.required,\n })),\n ...custom,\n ],\n }\n}\n"],"names":["CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_SOURCE_LABELS","CRM_ACTIVITY_KIND_LABELS","CRM_EMAIL_DELIVERY_STATE_LABELS","CRM_LEAD_STATUS_LABELS","CRM_TASK_KIND_LABELS","contactDisplayName","crmLeadStatus","customImportTarget","dealStageById","fieldDefinitionObject","interactionsForGroup","isContactLifecycleStage","isCrmEmailDeliveryState","readContactFacet","COMPANY_IMPORT_FIELD_LABELS","COMPANY_IMPORT_FIELDS","CONTACT_IMPORT_FIELD_LABELS","CONTACT_IMPORT_FIELDS","DEAL_IMPORT_FIELD_LABELS","DEAL_IMPORT_FIELDS","LEAD_IMPORT_FIELD_LABELS","LEAD_IMPORT_FIELDS","CRM_RECORD_FACTS_RESOURCES","contact","company","deal","lead","CRM_IMPORT_FACTS_RESOURCE","CRM_IMPORT_FACTS_COLLECTIONS","isCrmImportFactsCollection","value","includes","CRM_FACTS_TIMELINE_MAX","CRM_FACTS_TASKS_MAX","CRM_FACTS_DEALS_MAX","CRM_FACTS_TAGS_MAX","CRM_FACTS_TEXT_MAX","CRM_FACTS_NOTES_MAX","CRM_FACTS_LABEL_MAX","CRM_FACTS_CUSTOM_FIELDS_MAX","DAY_MS","crmFactText","max","text","replace","trim","length","slice","trimEnd","crmFactMillis","Number","isFinite","Date","getTime","toMillis","millis","call","crmFactDay","toISOString","crmFactMoney","cents","currency","code","test","toUpperCase","toFixed","EMAIL_IN_PROSE","DIGIT_RUN_IN_PROSE","DAYS_ONLY","CRM_FACTS_EMAIL_PLACEHOLDER","CRM_FACTS_PHONE_PLACEHOLDER","crmFactProse","scrubbed","run","tagsOf","Array","isArray","map","tag","filter","Boolean","crmActivityFact","activity","on","atMs","kind","fact","direction","subject","threadSubject","body","outcome","deliveryState","delivery","crmInteractionFact","interaction","type","summary","crmTimelineFacts","entries","entry","index","at","sort","a","b","crmOpenTaskFacts","tasks","nowMs","task","status","title","due","dueAtMs","POSITIVE_INFINITY","priority","overdue","Math","floor","crmDealFact","pipeline","stage","undefined","String","stageId","name","amount","amountCents","expectedClose","expectedCloseAtMs","crmDealFacts","deals","pipelines","updated","updatedAt","get","pipelineId","contactFacts","input","facet","row","group","groupId","sources","Object","source","interactions","hostIds","record","jobTitle","companyName","lifecycleStage","tags","Set","orders","ordersCount","lastPurchase","lastPurchaseAtMs","since","lastEmailEngagement","lastEmailEngagementAtMs","notes","timeline","activities","openTasks","companyFacts","domain","industry","people","contactsCount","createdAt","dealFacts","stages","order","id","inStageSince","stageChangedAtMs","lostReason","contactName","products","lineItems","leadSourceFact","import","startsWith","form","leadFacts","rawSources","captures","firstSeen","lastSeen","assigned","converted","unqualifiedReason","STANDARD_IMPORT_FIELDS","contacts","keys","labels","required","types","email","phone","ownerEmail","marketingConsent","companies","website","leads","CUSTOM_FIELD_OBJECT","CUSTOM_FIELD_TYPES","number","date","checkbox","url","importFacts","collection","customFields","standard","object","custom","field","key","retiredAt","localeCompare","label","fields"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/record-facts.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 */\nimport {\n CONTACT_ALTERNATE_EMAILS_FIELD,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_SOURCE_LABELS,\n CRM_ACTIVITY_KIND_LABELS,\n CRM_EMAIL_DELIVERY_STATE_LABELS,\n CRM_TASK_KIND_LABELS,\n contactDisplayName,\n crmLeadStatusLabel,\n CRM_FORECAST_CATEGORY_LABELS,\n crmActivityDirection,\n customImportTarget,\n DEAL_NEXT_STEP_MAX,\n dealContactRolesOf,\n dealForecastCategory,\n dealProbability,\n dealStageById,\n dealStageForecastCategory,\n fieldDefinitionObject,\n interactionsForGroup,\n isContactLifecycleStage,\n isCrmEmailDeliveryState,\n readContactFacet,\n readMarketingBasis,\n type AglynPostalAddress,\n type ConsentGroup,\n type ContactFieldDefinition,\n type ContactInteraction,\n type CrmActivity,\n type CrmCompany,\n type CrmDeal,\n type CrmFieldObject,\n type CrmPicklist,\n type CrmPipeline,\n type CrmTask,\n} from '@aglyn/aglyn/server'\nimport type { TransferField, TransferFieldType } from '@aglyn/aglyn/data-transfer'\nimport {\n COMPANY_TRANSFER_FIELDS,\n CONTACT_TRANSFER_FIELDS,\n DEAL_TRANSFER_FIELDS,\n LEAD_TRANSFER_FIELDS,\n} from '../transfer/fields'\n\n/**\n * WHAT THE CRM TELLS ANOTHER PLUGIN ABOUT ONE OF ITS RECORDS (AGL-2917).\n *\n * The facts a reader on the core's record-facts seam reports: one contact,\n * company, deal or lead as a person on the team sees it, and the fields a\n * spreadsheet import may fill. The server half reads the documents and\n * applies who may see them (`server/record-facts.ts`); this half shapes\n * them, so the list below is the whole of what a caller — an assistant\n * summarizing a record, an automation deciding on one — can pass on.\n *\n * ## The whole record (AGL-3520)\n *\n * These builders answer only for an organization with\n * `release_crm_assist_whole_record` on; with it off, the readers answer\n * `record-facts-disclosed.ts`, the fields the published pages promise.\n * A record is reported as the CRM shows it: every standard field —\n * Salesforce's included — with its email addresses, phone numbers, postal\n * addresses, birthdate, assistant and reports-to; the labels of its\n * picklists as the record holds them; its custom field values under their\n * labels; its marketing consent; its notes, newest timeline entries and\n * open tasks and deals. Text a person wrote is reported as written.\n *\n * ## What still never leaves\n *\n * No authentication token, no account identifier and no internal record\n * id. A related record — the company a person works for, the contact a deal\n * is with, the person someone reports to, a parent company, a campaign — is\n * named by its name; a team member by their display name. The one id a\n * caller is handed is a pipeline stage's, which a stage proposal must name.\n * An import catalog carries field keys, labels and types, never a row of\n * the file.\n *\n * ## Bounded\n *\n * Every list and every long text is cut — notes to\n * {@link CRM_FACTS_NOTES_MAX}, a timeline entry to {@link CRM_FACTS_TEXT_MAX},\n * at most {@link CRM_FACTS_CUSTOM_FIELDS_MAX} custom fields — so a record\n * holding everything still fits the request a caller builds from it.\n *\n * ## Stable bytes\n *\n * Every builder writes its keys in one order, dates as UTC calendar days and\n * money as a currency code and a fixed-point amount, so the same records read\n * twice report the same facts — a caller may key a cache on them, and one\n * that does sees a change exactly when something it was told has changed.\n */\n\n/** The records a reader is registered for, by the seam's resource name. */\nexport const CRM_RECORD_FACTS_RESOURCES = {\n contact: 'crm.contact',\n company: 'crm.company',\n deal: 'crm.deal',\n lead: 'crm.lead',\n} as const\n\nexport type CrmRecordFactsKind = keyof typeof CRM_RECORD_FACTS_RESOURCES\n\n/** The import catalogs, read by collection name as the record id. */\nexport const CRM_IMPORT_FACTS_RESOURCE = 'crm.import'\n\nexport const CRM_IMPORT_FACTS_COLLECTIONS = ['contacts', 'companies', 'deals', 'leads'] as const\n\nexport type CrmImportFactsCollection = (typeof CRM_IMPORT_FACTS_COLLECTIONS)[number]\n\nexport function isCrmImportFactsCollection(value: unknown): value is CrmImportFactsCollection {\n return typeof value === 'string' && (CRM_IMPORT_FACTS_COLLECTIONS as readonly string[]).includes(value)\n}\n\n/** Newest timeline entries reported for a record. */\nexport const CRM_FACTS_TIMELINE_MAX = 12\n/** Open tasks reported for a record. */\nexport const CRM_FACTS_TASKS_MAX = 8\n/** Deals reported for a contact or a company. */\nexport const CRM_FACTS_DEALS_MAX = 5\n/** Tags reported for a record. */\nexport const CRM_FACTS_TAGS_MAX = 10\n/** How much of a logged activity's text, a capture's summary, a task's notes or a custom value is reported. */\nexport const CRM_FACTS_TEXT_MAX = 280\n/** How much of a record's notes is reported. */\nexport const CRM_FACTS_NOTES_MAX = 600\n/** How long a name, a title or a subject may run. */\nexport const CRM_FACTS_LABEL_MAX = 120\n/** Custom fields an import catalog, or a record, reports. */\nexport const CRM_FACTS_CUSTOM_FIELDS_MAX = 40\n/** Products a deal's facts list. */\nexport const CRM_FACTS_PRODUCTS_MAX = 20\n/** Email addresses and campaigns a record's facts list. */\nexport const CRM_FACTS_LIST_MAX = 10\n\n/** One thing that happened, as a record's timeline reports it. */\nexport interface CrmTimelineFact {\n /** The UTC day it happened, `YYYY-MM-DD`. */\n on: string\n /** What happened: a logged kind (Call, Email) or the capture it came through (Form, Booking). */\n kind: string\n /** `outbound` or `inbound` for an email; also `internal` for a call (AGL-3517); absent otherwise. */\n direction?: string\n /** Who an email came from and went to, as the activity recorded them (AGL-3520). */\n from?: string\n to?: string\n subject?: string\n text?: string\n outcome?: string\n /** How far a sent email got: Sent, Delivered, Opened, Clicked, Bounced, Complained. */\n delivery?: string\n}\n\n/** One task still to do. */\nexport interface CrmTaskFact {\n title: string\n /** The org's Type label when the task holds one, else its kind's name (AGL-3517). */\n kind: string\n /** The meaning — `high`, `normal` or `low` — whatever the org calls it. */\n priority: string\n /** The org's Status label (\"In Progress\"), when the task holds one (AGL-3517). */\n status?: string\n /** The UTC day it is due, or `null` for a task with no due date. */\n due: string | null\n overdue: boolean\n /** Who it is assigned to, by name (AGL-3520). */\n assignee?: string\n /** What the task says beyond its title (AGL-3520). */\n notes?: string\n}\n\n/** One deal, as a contact's or a company's facts report it. */\nexport interface CrmDealFact {\n title: string\n stage: string\n status: string\n amount: string | null\n expectedClose: string | null\n}\n\n/** One custom field's value, under the label the org gave the field (AGL-3520). */\nexport interface CrmCustomFact {\n label: string\n value: string\n}\n\n/** One product on a deal (AGL-3520). */\nexport interface CrmProductFact {\n name: string\n quantity: number\n unitAmount: string | null\n}\n\nexport interface CrmContactFacts {\n record: 'contact'\n name: string\n salutation: string\n firstName: string\n lastName: string\n /** Every address the person is reached at: the canonical one first (AGL-3520). */\n emails: string[]\n phone: string\n mobilePhone: string\n homePhone: string\n otherPhone: string\n fax: string\n jobTitle: string\n department: string\n /** A `YYYY-MM-DD` day, or `''`. */\n birthdate: string\n assistant: string\n assistantPhone: string\n /** The name of the contact the person reports to (AGL-3515). */\n reportsTo: string\n mailingAddress: string\n otherAddress: string\n /** They asked not to be phoned — a fact an assistant drafting a next step must respect. */\n doNotCall: boolean\n company: string\n lifecycleStage: string\n leadSource: string\n owner: string\n /** Whether the person agreed to marketing email from this site, and since when. */\n marketingConsent: string\n tags: string[]\n /** The captures that met the person: Form, Booking, Customer. */\n sources: string[]\n orders: number\n lastPurchase: string | null\n since: string | null\n lastEmailEngagement: string | null\n custom: CrmCustomFact[]\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n deals: CrmDealFact[]\n}\n\nexport interface CrmCompanyFacts {\n record: 'company'\n name: string\n domain: string\n website: string\n phone: string\n fax: string\n industry: string\n /** Salesforce's Account fields (AGL-3514). */\n type: string\n rating: string\n ownership: string\n accountSource: string\n employees: number | null\n annualRevenue: string | null\n accountNumber: string\n site: string\n tickerSymbol: string\n sicCode: string\n billingAddress: string\n shippingAddress: string\n /** The name of the company this one sits under. */\n parentCompany: string\n owner: string\n tags: string[]\n people: number\n since: string | null\n custom: CrmCustomFact[]\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n deals: CrmDealFact[]\n}\n\n/** One stage of a deal's pipeline, in order. */\nexport interface CrmStageFact {\n id: string\n name: string\n kind: 'open' | 'won' | 'lost'\n /** Chance of closing from the stage, 0–100 (AGL-3516). */\n probability: number\n /** The forecast category a deal takes in the stage, as its label. */\n forecastCategory: string\n}\n\n/** One contact on a deal, as the facts name them (AGL-3521). */\nexport interface CrmContactRoleFact {\n /** The person's name, or `''` where it could not be read. */\n name: string\n /** The role's label, or `''` for none. */\n role: string\n primary: boolean\n}\n\n/** The contacts a deal's facts name — the committee, not the whole list. */\nexport const CRM_FACTS_CONTACT_ROLES_MAX = 10\n\nexport interface CrmDealFacts {\n record: 'deal'\n title: string\n pipeline: string\n stages: CrmStageFact[]\n stageId: string\n stage: string\n status: string\n amount: string | null\n expectedClose: string | null\n inStageSince: string | null\n lostReason: string\n /*\n * Salesforce's Opportunity fields (AGL-3516).\n */\n type: string\n leadSource: string\n nextStep: string\n /** The deal's own odds when it overrides the stage's, else the stage's; `null` for neither. */\n probability: number | null\n /** The forecast category's label. */\n forecastCategory: string\n /** The campaign the deal is credited to, by name (AGL-3520). */\n campaign: string\n /** The name of the person the deal is with, as the deal copied it. */\n contact: string\n /**\n * Every contact on the deal and the part each plays (AGL-3521), the\n * Primary first — named as the reader resolved them, at most\n * {@link CRM_FACTS_CONTACT_ROLES_MAX}.\n */\n contactRoles: CrmContactRoleFact[]\n company: string\n owner: string\n products: CrmProductFact[]\n since: string | null\n custom: CrmCustomFact[]\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n}\n\nexport interface CrmLeadFacts {\n record: 'lead'\n name: string\n /*\n * The lead's own profile and Salesforce's standard lead fields\n * (AGL-3231, AGL-3513), with how to reach the person (AGL-3520).\n */\n salutation: string\n firstName: string\n lastName: string\n email: string\n phone: string\n mobilePhone: string\n fax: string\n website: string\n address: string\n company: string\n jobTitle: string\n leadSource: string\n industry: string\n rating: string\n employees: number | null\n annualRevenue: string | null\n /** They asked not to be phoned — a fact an assistant drafting a next step must respect. */\n doNotCall: boolean\n status: string\n owner: string\n /** The campaigns the lead is filed under, by name. */\n campaigns: string[]\n marketingConsent: string\n tags: string[]\n /** Where the lead was captured: Form, Booking, Sign-up, Import. */\n sources: string[]\n captures: number\n firstSeen: string | null\n lastSeen: string | null\n assigned: boolean\n converted: boolean\n unqualifiedReason: string\n custom: CrmCustomFact[]\n notes: string\n timeline: CrmTimelineFact[]\n openTasks: CrmTaskFact[]\n}\n\nexport type CrmRecordFacts = CrmContactFacts | CrmCompanyFacts | CrmDealFacts | CrmLeadFacts\n\n/** What a column of a file may hold, as an import field expects it. */\nexport type CrmImportFieldType = 'email' | 'phone' | 'number' | 'date' | 'yes-no' | 'url' | 'text'\n\n/** One field an import may fill. */\nexport interface CrmImportFieldFact {\n /** The mapping target: a standard field's key, or `custom:<key>`. */\n key: string\n label: string\n type: CrmImportFieldType\n required: boolean\n}\n\nexport interface CrmImportFacts {\n record: 'import'\n collection: CrmImportFactsCollection\n fields: CrmImportFieldFact[]\n}\n\n/**\n * What the server half resolves for a builder, because a fact names a\n * related record or a person and the builder reads no document: a team\n * member's display name, the record's custom field definitions, and the\n * names of the records it points at.\n */\nexport interface CrmFactsNames {\n /** A team member's display name for a uid; `''` when the roster has none. */\n member?: (uid: string) => string\n /** The org's custom field definitions, every object's; each builder takes its own. */\n customFields?: ReadonlyArray<Partial<ContactFieldDefinition>>\n}\n\nconst DAY_MS = 86_400_000\n\n/** A value as trimmed, single-spaced text of at most `max` characters. */\nexport function crmFactText(value: unknown, max: number): string {\n if (typeof value !== 'string') return ''\n const text = value.replace(/\\s+/g, ' ').trim()\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\n}\n\n/** Epoch millis, a `Date` or a stored timestamp, as epoch millis; `null` for anything else. */\nexport function crmFactMillis(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) && value > 0 ? value : null\n if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null\n const toMillis = (value as { toMillis?: () => number } | null | undefined)?.toMillis\n if (typeof toMillis === 'function') {\n const millis = toMillis.call(value)\n return Number.isFinite(millis) ? millis : null\n }\n return null\n}\n\n/** A UTC calendar day, `YYYY-MM-DD`, or `null`. */\nexport function crmFactDay(value: unknown): string | null {\n const millis = crmFactMillis(value)\n return millis === null ? null : new Date(millis).toISOString().slice(0, 10)\n}\n\n/** An amount as a currency code and a fixed-point figure: `USD 1250.00`. */\nexport function crmFactMoney(cents: unknown, currency: unknown): string | null {\n if (typeof cents !== 'number' || !Number.isFinite(cents)) return null\n const code = (typeof currency === 'string' && /^[a-z]{3}$/i.test(currency) ? currency : 'usd').toUpperCase()\n return `${code} ${(cents / 100).toFixed(2)}`\n}\n\n/** A postal address on one line, its parts in the order an envelope writes them; `''` for none. */\nexport function crmFactAddress(address: Partial<AglynPostalAddress> | null | undefined): string {\n if (!address || typeof address !== 'object') return ''\n const part = (value: unknown) => crmFactText(value, CRM_FACTS_LABEL_MAX)\n const cityLine = [part(address.city), [part(address.state), part(address.postalCode)].filter(Boolean).join(' ')]\n .filter(Boolean)\n .join(', ')\n return [part(address.line1), part(address.line2), cityLine, part(address.country)].filter(Boolean).join(', ')\n}\n\n/** A marketing basis as a sentence's worth: opted in (and since when), declined, or nothing recorded. */\nexport function crmFactConsent(record: Record<string, unknown>, group: ConsentGroup | null | undefined): string {\n if (!group) return ''\n const consent = readMarketingBasis(record, group)\n if (consent.basis === 'granted') {\n const since = crmFactDay(consent.basisAtMs)\n return since ? `opted in on ${since}` : 'opted in'\n }\n return consent.basis === 'declined' ? 'declined' : 'none recorded'\n}\n\n/** A custom value as text: a day for a date, yes or no for a checkbox. */\nfunction customValueText(value: unknown, type: unknown): string {\n if (value === null || value === undefined || value === '') return ''\n if (typeof value === 'boolean') return value ? 'yes' : 'no'\n if (type === 'date') return crmFactDay(typeof value === 'string' ? Date.parse(value) : value) ?? crmFactText(String(value), CRM_FACTS_TEXT_MAX)\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : ''\n return crmFactText(String(value), CRM_FACTS_TEXT_MAX)\n}\n\n/**\n * A record's custom values under their fields' labels, in the fields'\n * order — the active fields of `object` first, then a value whose field was\n * retired or never defined, under its key.\n */\nexport function crmCustomFacts(\n custom: unknown,\n object: CrmFieldObject,\n definitions: ReadonlyArray<Partial<ContactFieldDefinition>> = [],\n): CrmCustomFact[] {\n if (!custom || typeof custom !== 'object' || Array.isArray(custom)) return []\n const values = custom as Record<string, unknown>\n const own = definitions\n .filter((field) => field.key && fieldDefinitionObject(field as ContactFieldDefinition) === object)\n .sort(\n (a, b) =>\n Number(Boolean(a.retiredAt)) - Number(Boolean(b.retiredAt)) ||\n Number(a.order ?? 0) - Number(b.order ?? 0) ||\n String(a.key).localeCompare(String(b.key)),\n )\n const facts: CrmCustomFact[] = []\n const seen = new Set<string>()\n for (const field of own) {\n const key = String(field.key)\n seen.add(key)\n const value = customValueText(values[key], field.type)\n if (value) facts.push({ label: crmFactText(field.label, CRM_FACTS_LABEL_MAX) || key, value })\n }\n for (const key of Object.keys(values).sort()) {\n if (seen.has(key)) continue\n const value = customValueText(values[key], undefined)\n if (value) facts.push({ label: key, value })\n }\n return facts.slice(0, CRM_FACTS_CUSTOM_FIELDS_MAX)\n}\n\nfunction tagsOf(value: unknown): string[] {\n return Array.isArray(value)\n ? value\n .map((tag) => crmFactText(tag, 40))\n .filter(Boolean)\n .slice(0, CRM_FACTS_TAGS_MAX)\n : []\n}\n\nconst memberName = (names: CrmFactsNames | undefined, uid: unknown): string =>\n typeof uid === 'string' && uid ? crmFactText(names?.member?.(uid) ?? '', CRM_FACTS_LABEL_MAX) : ''\n\n/** A logged activity as a timeline entry. */\nexport function crmActivityFact(activity: Partial<CrmActivity>): CrmTimelineFact | null {\n const on = crmFactDay(activity.atMs)\n if (!on) return null\n const kind = activity.kind && CRM_ACTIVITY_KIND_LABELS[activity.kind] ? activity.kind : 'other'\n const fact: CrmTimelineFact = { on, kind: CRM_ACTIVITY_KIND_LABELS[kind] }\n const direction = crmActivityDirection(kind, activity.direction)\n if (direction) fact.direction = direction\n const from = crmFactText(activity.from, CRM_FACTS_LABEL_MAX)\n if (from) fact.from = from\n const to = crmFactText(activity.to, CRM_FACTS_LABEL_MAX)\n if (to) fact.to = to\n const subject = crmFactText(activity.subject ?? activity.threadSubject, CRM_FACTS_LABEL_MAX)\n if (subject) fact.subject = subject\n const text = crmFactText(activity.body, CRM_FACTS_TEXT_MAX)\n if (text) fact.text = text\n const outcome = crmFactText(activity.outcome, CRM_FACTS_LABEL_MAX)\n if (outcome) fact.outcome = outcome\n if (kind === 'email' && isCrmEmailDeliveryState(activity.deliveryState)) {\n fact.delivery = CRM_EMAIL_DELIVERY_STATE_LABELS[activity.deliveryState]\n }\n return fact\n}\n\n/** A capture the platform recorded on a contact, as a timeline entry. */\nexport function crmInteractionFact(interaction: Partial<ContactInteraction>): CrmTimelineFact | null {\n const on = crmFactDay(interaction.atMs)\n if (!on) return null\n const kind =\n interaction.type && CONTACT_SOURCE_LABELS[interaction.type] ? CONTACT_SOURCE_LABELS[interaction.type] : 'Capture'\n const text = crmFactText(interaction.summary, CRM_FACTS_TEXT_MAX)\n return text ? { on, kind, text } : { on, kind }\n}\n\n/**\n * Timeline entries newest first, at most {@link CRM_FACTS_TIMELINE_MAX}. A\n * tie keeps the order the entries arrived in, so the same reads report the\n * same list.\n */\nexport function crmTimelineFacts(\n entries: ReadonlyArray<{ atMs: unknown; fact: CrmTimelineFact | null }>,\n): CrmTimelineFact[] {\n return entries\n .map((entry, index) => ({ at: crmFactMillis(entry.atMs) ?? 0, index, fact: entry.fact }))\n .filter((entry): entry is { at: number; index: number; fact: CrmTimelineFact } => entry.fact !== null)\n .sort((a, b) => b.at - a.at || a.index - b.index)\n .slice(0, CRM_FACTS_TIMELINE_MAX)\n .map((entry) => entry.fact)\n}\n\n/** The open tasks among `tasks`, soonest due first, undated last. */\nexport function crmOpenTaskFacts(\n tasks: ReadonlyArray<Partial<CrmTask>>,\n nowMs: number,\n names?: CrmFactsNames,\n): CrmTaskFact[] {\n return tasks\n .filter((task) => task.status !== 'done' && crmFactText(task.title, CRM_FACTS_LABEL_MAX))\n .map((task, index) => ({ task, index, due: crmFactMillis(task.dueAtMs) }))\n .sort((a, b) => (a.due ?? Number.POSITIVE_INFINITY) - (b.due ?? Number.POSITIVE_INFINITY) || a.index - b.index)\n .slice(0, CRM_FACTS_TASKS_MAX)\n .map(({ task, due }) => {\n const assignee = memberName(names, task.assigneeUid)\n const notes = crmFactText(task.notes, CRM_FACTS_TEXT_MAX)\n return {\n title: crmFactText(task.title, CRM_FACTS_LABEL_MAX),\n kind:\n crmFactText(task.typeLabel, CRM_FACTS_LABEL_MAX) ||\n (task.kind && CRM_TASK_KIND_LABELS[task.kind] ? CRM_TASK_KIND_LABELS[task.kind] : 'To-do'),\n priority: task.priority === 'high' || task.priority === 'low' ? task.priority : 'normal',\n ...(crmFactText(task.statusLabel, CRM_FACTS_LABEL_MAX)\n ? { status: crmFactText(task.statusLabel, CRM_FACTS_LABEL_MAX) }\n : {}),\n due: due === null ? null : crmFactDay(due),\n // Overdue by the UTC day: a task due today is not overdue until tomorrow.\n overdue: due !== null && Math.floor(due / DAY_MS) < Math.floor(nowMs / DAY_MS),\n ...(assignee ? { assignee } : {}),\n ...(notes ? { notes } : {}),\n }\n })\n}\n\n/** One deal, with its stage named by the pipeline it is in. */\nexport function crmDealFact(deal: Partial<CrmDeal>, pipeline: CrmPipeline | null | undefined): CrmDealFact {\n const stage = dealStageById(pipeline ?? undefined, String(deal.stageId ?? ''))\n return {\n title: crmFactText(deal.title, CRM_FACTS_LABEL_MAX),\n stage: crmFactText(stage?.name, CRM_FACTS_LABEL_MAX),\n status: deal.status === 'won' || deal.status === 'lost' ? deal.status : 'open',\n amount: crmFactMoney(deal.amountCents, deal.currency),\n expectedClose: crmFactDay(deal.expectedCloseAtMs),\n }\n}\n\n/** Deals, open ones first, then most recently updated. */\nexport function crmDealFacts(\n deals: ReadonlyArray<Partial<CrmDeal>>,\n pipelines: ReadonlyMap<string, CrmPipeline>,\n): CrmDealFact[] {\n return deals\n .map((deal, index) => ({ deal, index, updated: crmFactMillis(deal.updatedAt) ?? 0 }))\n .sort(\n (a, b) =>\n Number(a.deal.status !== 'open') - Number(b.deal.status !== 'open') ||\n b.updated - a.updated ||\n a.index - b.index,\n )\n .slice(0, CRM_FACTS_DEALS_MAX)\n .map(({ deal }) => crmDealFact(deal, pipelines.get(String(deal.pipelineId ?? ''))))\n}\n\nconst label = (value: unknown) => crmFactText(value, CRM_FACTS_LABEL_MAX)\n\nexport interface ContactFactsInput {\n row: Record<string, unknown>\n /** The holder whose facet the person is read through. */\n group: ConsentGroup\n activities: ReadonlyArray<Partial<CrmActivity>>\n tasks: ReadonlyArray<Partial<CrmTask>>\n deals: ReadonlyArray<Partial<CrmDeal>>\n pipelines: ReadonlyMap<string, CrmPipeline>\n nowMs: number\n names?: CrmFactsNames\n /** The name of the contact the holder's reports-to names, when the reader could see it. */\n reportsToName?: string\n}\n\nexport function contactFacts(input: ContactFactsInput): CrmContactFacts {\n const { row, group, names } = input\n const facet = readContactFacet(row, group.groupId)\n const sources = Object.entries(facet.sources ?? {})\n .filter(([, on]) => on === true)\n .map(([source]) => CONTACT_SOURCE_LABELS[source as keyof typeof CONTACT_SOURCE_LABELS] ?? '')\n .filter(Boolean)\n const interactions = interactionsForGroup(facet.interactions, group.hostIds)\n const alternates = Array.isArray(row[CONTACT_ALTERNATE_EMAILS_FIELD])\n ? (row[CONTACT_ALTERNATE_EMAILS_FIELD] as unknown[])\n : []\n const emails = [...new Set([row['email'], ...alternates].map(label).filter(Boolean))].slice(0, CRM_FACTS_LIST_MAX)\n return {\n record: 'contact',\n name: label(contactDisplayName(row, group.groupId)),\n salutation: label(facet.salutation),\n firstName: label(facet.firstName),\n lastName: label(facet.lastName),\n emails,\n phone: label(facet.phone ?? row['phone']),\n mobilePhone: label(facet.mobilePhone),\n homePhone: label(facet.homePhone),\n otherPhone: label(facet.otherPhone),\n fax: label(facet.fax),\n jobTitle: label(facet.jobTitle),\n department: label(facet.department),\n birthdate: label(facet.birthdate),\n assistant: label(facet.assistantName),\n assistantPhone: label(facet.assistantPhone),\n reportsTo: label(input.reportsToName),\n mailingAddress: crmFactAddress(facet.address),\n otherAddress: crmFactAddress(facet.otherAddress),\n doNotCall: facet.doNotCall === true,\n company: label(facet.companyName),\n lifecycleStage: isContactLifecycleStage(facet.lifecycleStage)\n ? CONTACT_LIFECYCLE_STAGE_LABELS[facet.lifecycleStage]\n : '',\n leadSource: label(facet.leadSource),\n owner: memberName(names, facet.ownerUid),\n marketingConsent: crmFactConsent(row, group),\n tags: tagsOf(facet.tags),\n sources: [...new Set(sources)].sort(),\n orders: typeof facet.ordersCount === 'number' && facet.ordersCount > 0 ? Math.floor(facet.ordersCount) : 0,\n lastPurchase: crmFactDay(facet.lastPurchaseAtMs),\n since: crmFactDay(row['createdAt']),\n lastEmailEngagement: crmFactDay(facet.lastEmailEngagementAtMs),\n custom: crmCustomFacts(facet.custom, 'contact', names?.customFields),\n notes: crmFactText(facet.notes, CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts([\n ...input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ...interactions.map((interaction) => ({ atMs: interaction.atMs, fact: crmInteractionFact(interaction) })),\n ]),\n openTasks: crmOpenTaskFacts(input.tasks, input.nowMs, names),\n deals: crmDealFacts(input.deals, input.pipelines),\n }\n}\n\nexport interface CompanyFactsInput {\n company: Partial<CrmCompany>\n activities: ReadonlyArray<Partial<CrmActivity>>\n tasks: ReadonlyArray<Partial<CrmTask>>\n deals: ReadonlyArray<Partial<CrmDeal>>\n pipelines: ReadonlyMap<string, CrmPipeline>\n nowMs: number\n names?: CrmFactsNames\n /** The parent company's name, when the reader could see it. */\n parentCompanyName?: string\n}\n\nexport function companyFacts(input: CompanyFactsInput): CrmCompanyFacts {\n const { company, names } = input\n return {\n record: 'company',\n name: label(company.name),\n domain: label(company.domain),\n website: label(company.website),\n phone: label(company.phone),\n fax: label(company.fax),\n industry: label(company.industry),\n type: label(company.type),\n rating: label(company.rating),\n ownership: label(company.ownership),\n accountSource: label(company.accountSource),\n employees:\n typeof company.numberOfEmployees === 'number' && company.numberOfEmployees >= 0\n ? Math.floor(company.numberOfEmployees)\n : null,\n annualRevenue: crmFactMoney(company.annualRevenueCents, company.currency),\n accountNumber: label(company.accountNumber),\n site: label(company.site),\n tickerSymbol: label(company.tickerSymbol),\n sicCode: label(company.sicCode),\n billingAddress: crmFactAddress(company.address),\n shippingAddress: crmFactAddress(company.shippingAddress),\n parentCompany: label(input.parentCompanyName),\n owner: memberName(names, company.ownerUid),\n tags: tagsOf(company.tags),\n people:\n typeof company.contactsCount === 'number' && company.contactsCount > 0\n ? Math.floor(company.contactsCount)\n : 0,\n since: crmFactDay(company.createdAt),\n custom: crmCustomFacts(company.custom, 'company', names?.customFields),\n notes: crmFactText(company.notes, CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts(\n input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ),\n openTasks: crmOpenTaskFacts(input.tasks, input.nowMs, names),\n deals: crmDealFacts(input.deals, input.pipelines),\n }\n}\n\nexport interface DealFactsInput {\n deal: Partial<CrmDeal> & { contactName?: unknown; companyName?: unknown }\n /** The names of the deal's contacts, by id, as the reader may see them (AGL-3521). */\n contactNames?: ReadonlyMap<string, string>\n pipeline: CrmPipeline | null\n activities: ReadonlyArray<Partial<CrmActivity>>\n tasks: ReadonlyArray<Partial<CrmTask>>\n nowMs: number\n names?: CrmFactsNames\n /** The name of the campaign the deal is credited to, when the reader could see it. */\n campaignName?: string\n}\n\nexport function dealFacts(input: DealFactsInput): CrmDealFacts {\n const { deal, pipeline, names } = input\n const summary = crmDealFact(deal, pipeline)\n const stages = [...(pipeline?.stages ?? [])]\n .sort((a, b) => Number(a.order ?? 0) - Number(b.order ?? 0))\n .map((stage) => ({\n id: String(stage.id),\n name: crmFactText(stage.name, CRM_FACTS_LABEL_MAX),\n kind: stage.kind === 'won' || stage.kind === 'lost' ? stage.kind : ('open' as const),\n probability: Math.min(100, Math.max(0, Number(stage.probability) || 0)),\n forecastCategory: CRM_FORECAST_CATEGORY_LABELS[dealStageForecastCategory(stage)],\n }))\n const stage = dealStageById(pipeline ?? undefined, String(deal.stageId ?? ''))\n const products = (Array.isArray(deal.lineItems) ? deal.lineItems : [])\n .slice(0, CRM_FACTS_PRODUCTS_MAX)\n .map((item) => ({\n name: label(item?.name),\n quantity: Number(item?.quantity) || 0,\n unitAmount: crmFactMoney(item?.unitAmountCents, item?.currency ?? deal.currency),\n }))\n return {\n record: 'deal',\n title: summary.title,\n pipeline: crmFactText(pipeline?.name, CRM_FACTS_LABEL_MAX),\n stages,\n stageId: String(deal.stageId ?? ''),\n stage: summary.stage,\n status: summary.status,\n amount: summary.amount,\n expectedClose: summary.expectedClose,\n inStageSince: crmFactDay(deal.stageChangedAtMs),\n lostReason: label(deal.lostReason),\n type: label(deal.type),\n leadSource: label(deal.leadSource),\n nextStep: crmFactText(deal.nextStep, DEAL_NEXT_STEP_MAX),\n probability: dealProbability(deal, stage),\n forecastCategory: CRM_FORECAST_CATEGORY_LABELS[dealForecastCategory(deal, stage)],\n campaign: label(input.campaignName),\n contact: label(deal.contactName),\n contactRoles: dealContactRoleFacts(deal, input.contactNames),\n company: label(deal.companyName),\n owner: memberName(names, deal.ownerUid),\n products,\n since: crmFactDay(deal.createdAt),\n custom: crmCustomFacts(deal.custom, 'deal', names?.customFields),\n notes: crmFactText(deal.notes, CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts(\n input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ),\n openTasks: crmOpenTaskFacts(input.tasks, input.nowMs, names),\n }\n}\n\n/**\n * A deal's contact roles as facts (AGL-3521): the Primary first, each by the\n * name the reader resolved — a contact the reader may not see, or could\n * not read, is left out rather than named by id.\n */\nexport function dealContactRoleFacts(\n deal: { contactId?: unknown; contactRoles?: unknown },\n names: ReadonlyMap<string, string> = new Map(),\n): CrmContactRoleFact[] {\n return dealContactRolesOf(deal)\n .sort((a, b) => Number(b.primary) - Number(a.primary))\n .filter((row) => names.has(row.contactId))\n .slice(0, CRM_FACTS_CONTACT_ROLES_MAX)\n .map((row) => ({\n name: label(names.get(row.contactId)),\n role: crmFactText(row.role, CRM_FACTS_LABEL_MAX),\n primary: row.primary,\n }))\n}\n\n/** What a lead's capture surfaces are called, the lead history card's words. */\nexport function leadSourceFact(source: string): string {\n if (source === 'signup') return 'Sign-up'\n if (source === 'booking') return 'Booking'\n if (source === 'import') return CONTACT_SOURCE_LABELS.import\n if (source === 'form' || source.startsWith('form:')) return CONTACT_SOURCE_LABELS.form\n return 'Other'\n}\n\nexport interface LeadFactsInput {\n lead: Record<string, unknown>\n activities: ReadonlyArray<Partial<CrmActivity>>\n /** The tasks filed against the lead (AGL-3520); none when the reader read none. */\n tasks?: ReadonlyArray<Partial<CrmTask>>\n nowMs?: number\n /** The org's Lead status list, whose label the status fact reads (AGL-3512); the standard ones without it. */\n leadStatuses?: CrmPicklist\n /** The site's consent group, which the lead's marketing consent is read under. */\n group?: ConsentGroup | null\n names?: CrmFactsNames\n /** The names of the campaigns the lead is filed under, as the reader resolved them. */\n campaignNames?: readonly string[]\n}\n\nexport function leadFacts(input: LeadFactsInput): CrmLeadFacts {\n const { lead, leadStatuses, names } = input\n const rawSources = Array.isArray(lead['sources'])\n ? lead['sources']\n : typeof lead['source'] === 'string'\n ? [lead['source']]\n : []\n const captures = Number(lead['submissionCount'])\n return {\n record: 'lead',\n name: label(lead['name']),\n salutation: label(lead['salutation']),\n firstName: label(lead['firstName']),\n lastName: label(lead['lastName']),\n email: label(lead['email']),\n phone: label(lead['phone']),\n mobilePhone: label(lead['mobilePhone']),\n fax: label(lead['fax']),\n website: label(lead['website']),\n address: crmFactAddress(lead['address'] as Partial<AglynPostalAddress> | null),\n company: label(lead['company']),\n jobTitle: label(lead['jobTitle']),\n leadSource: label(lead['leadSource']),\n industry: label(lead['industry']),\n rating: label(lead['rating']),\n employees:\n typeof lead['numberOfEmployees'] === 'number' && lead['numberOfEmployees'] >= 0\n ? Math.floor(lead['numberOfEmployees'])\n : null,\n annualRevenue: crmFactMoney(lead['annualRevenueCents'], lead['currency']),\n doNotCall: lead['doNotCall'] === true,\n status: crmLeadStatusLabel(lead as { status?: never; statusLabel?: string }, leadStatuses),\n owner: memberName(names, lead['ownerUid']),\n campaigns: (input.campaignNames ?? []).map(label).filter(Boolean).slice(0, CRM_FACTS_LIST_MAX),\n marketingConsent: crmFactConsent(lead, input.group),\n tags: tagsOf(lead['tags']),\n sources: [...new Set(rawSources.map((source) => leadSourceFact(String(source))))].sort(),\n captures: Number.isFinite(captures) && captures > 0 ? Math.floor(captures) : 0,\n firstSeen: crmFactDay(lead['firstSeenAtMs']),\n lastSeen: crmFactDay(lead['lastSeenAtMs']),\n assigned: typeof lead['ownerUid'] === 'string' && lead['ownerUid'] !== '',\n converted: typeof lead['convertedContactId'] === 'string' && lead['convertedContactId'] !== '',\n unqualifiedReason: label(lead['unqualifiedReason']),\n custom: crmCustomFacts(lead['custom'], 'lead', names?.customFields),\n notes: crmFactText(lead['notes'], CRM_FACTS_NOTES_MAX),\n timeline: crmTimelineFacts(\n input.activities.map((activity) => ({ atMs: activity.atMs, fact: crmActivityFact(activity) })),\n ),\n openTasks: crmOpenTaskFacts(input.tasks ?? [], input.nowMs ?? Date.now(), names),\n }\n}\n\n/** A column's kind, as the matcher is told it, for each transfer field type. */\nconst TRANSFER_TYPES: Partial<Record<TransferFieldType, CrmImportFieldType>> = {\n email: 'email',\n phone: 'phone',\n url: 'url',\n number: 'number',\n integer: 'number',\n currency: 'number',\n percent: 'number',\n date: 'date',\n datetime: 'date',\n boolean: 'yes-no',\n}\n\n/** The fields a transfer catalog lets a file write, as the import facts list them. */\nfunction transferImportFields(fields: readonly TransferField[]): {\n keys: readonly string[]\n labels: Record<string, string>\n required: string\n types: Record<string, CrmImportFieldType>\n} {\n const writable = fields.filter((field) => !field.readOnly && !field.derived && !field.system)\n return {\n keys: writable.map((field) => field.id),\n labels: Object.fromEntries(writable.map((field) => [field.id, field.label])),\n required: writable.find((field) => field.required)?.id ?? '',\n types: Object.fromEntries(\n writable.flatMap((field) => (TRANSFER_TYPES[field.type] ? [[field.id, TRANSFER_TYPES[field.type] as CrmImportFieldType]] : [])),\n ),\n }\n}\n\n/** The standard fields of each import, with the type a column must hold for each. */\nconst STANDARD_IMPORT_FIELDS: Record<\n CrmImportFactsCollection,\n { keys: readonly string[]; labels: Record<string, string>; required: string; types: Record<string, CrmImportFieldType> }\n> = {\n // The transfer catalogs' own ids (AGL-3527): what the import wizard maps to.\n contacts: transferImportFields(CONTACT_TRANSFER_FIELDS),\n companies: transferImportFields(COMPANY_TRANSFER_FIELDS),\n deals: transferImportFields(DEAL_TRANSFER_FIELDS),\n leads: transferImportFields(LEAD_TRANSFER_FIELDS),\n}\n\n/** The object a collection's custom fields describe. */\nconst CUSTOM_FIELD_OBJECT: Partial<Record<CrmImportFactsCollection, CrmFieldObject>> = {\n contacts: 'contact',\n companies: 'company',\n deals: 'deal',\n leads: 'lead',\n}\n\nconst CUSTOM_FIELD_TYPES: Record<string, CrmImportFieldType> = {\n number: 'number',\n date: 'date',\n checkbox: 'yes-no',\n url: 'url',\n}\n\n/**\n * The fields an import of `collection` may fill: the standard ones in the\n * mapping menu's order, then the org's active custom fields for the record\n * the collection holds, in their own order.\n */\nexport function importFacts(\n collection: CrmImportFactsCollection,\n customFields: ReadonlyArray<Partial<ContactFieldDefinition>>,\n): CrmImportFacts {\n const standard = STANDARD_IMPORT_FIELDS[collection]\n const object = CUSTOM_FIELD_OBJECT[collection]\n const custom = object\n ? customFields\n .filter((field) => field.key && !field.retiredAt && fieldDefinitionObject(field as ContactFieldDefinition) === object)\n .sort((a, b) => Number(a.order ?? 0) - Number(b.order ?? 0) || String(a.key).localeCompare(String(b.key)))\n .slice(0, CRM_FACTS_CUSTOM_FIELDS_MAX)\n .map((field) => ({\n key: customImportTarget(String(field.key)),\n label: crmFactText(field.label, CRM_FACTS_LABEL_MAX) || String(field.key),\n type: CUSTOM_FIELD_TYPES[String(field.type)] ?? ('text' as const),\n required: false,\n }))\n : []\n return {\n record: 'import',\n collection,\n fields: [\n ...standard.keys.map((key) => ({\n key,\n label: standard.labels[key] ?? key,\n type: standard.types[key] ?? ('text' as const),\n required: key === standard.required,\n })),\n ...custom,\n ],\n }\n}\n"],"names":["CONTACT_ALTERNATE_EMAILS_FIELD","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_SOURCE_LABELS","CRM_ACTIVITY_KIND_LABELS","CRM_EMAIL_DELIVERY_STATE_LABELS","CRM_TASK_KIND_LABELS","contactDisplayName","crmLeadStatusLabel","CRM_FORECAST_CATEGORY_LABELS","crmActivityDirection","customImportTarget","DEAL_NEXT_STEP_MAX","dealContactRolesOf","dealForecastCategory","dealProbability","dealStageById","dealStageForecastCategory","fieldDefinitionObject","interactionsForGroup","isContactLifecycleStage","isCrmEmailDeliveryState","readContactFacet","readMarketingBasis","COMPANY_TRANSFER_FIELDS","CONTACT_TRANSFER_FIELDS","DEAL_TRANSFER_FIELDS","LEAD_TRANSFER_FIELDS","CRM_RECORD_FACTS_RESOURCES","contact","company","deal","lead","CRM_IMPORT_FACTS_RESOURCE","CRM_IMPORT_FACTS_COLLECTIONS","isCrmImportFactsCollection","value","includes","CRM_FACTS_TIMELINE_MAX","CRM_FACTS_TASKS_MAX","CRM_FACTS_DEALS_MAX","CRM_FACTS_TAGS_MAX","CRM_FACTS_TEXT_MAX","CRM_FACTS_NOTES_MAX","CRM_FACTS_LABEL_MAX","CRM_FACTS_CUSTOM_FIELDS_MAX","CRM_FACTS_PRODUCTS_MAX","CRM_FACTS_LIST_MAX","CRM_FACTS_CONTACT_ROLES_MAX","DAY_MS","crmFactText","max","text","replace","trim","length","slice","trimEnd","crmFactMillis","Number","isFinite","Date","getTime","toMillis","millis","call","crmFactDay","toISOString","crmFactMoney","cents","currency","code","test","toUpperCase","toFixed","crmFactAddress","address","part","cityLine","city","state","postalCode","filter","Boolean","join","line1","line2","country","crmFactConsent","record","group","consent","basis","since","basisAtMs","customValueText","type","undefined","parse","String","crmCustomFacts","custom","object","definitions","Array","isArray","values","own","field","key","sort","a","b","retiredAt","order","localeCompare","facts","seen","Set","add","push","label","Object","keys","has","tagsOf","map","tag","memberName","names","uid","member","crmActivityFact","activity","on","atMs","kind","fact","direction","from","to","subject","threadSubject","body","outcome","deliveryState","delivery","crmInteractionFact","interaction","summary","crmTimelineFacts","entries","entry","index","at","crmOpenTaskFacts","tasks","nowMs","task","status","title","due","dueAtMs","POSITIVE_INFINITY","assignee","assigneeUid","notes","typeLabel","priority","statusLabel","overdue","Math","floor","crmDealFact","pipeline","stage","stageId","name","amount","amountCents","expectedClose","expectedCloseAtMs","crmDealFacts","deals","pipelines","updated","updatedAt","get","pipelineId","contactFacts","input","facet","row","groupId","sources","source","interactions","hostIds","alternates","emails","salutation","firstName","lastName","phone","mobilePhone","homePhone","otherPhone","fax","jobTitle","department","birthdate","assistant","assistantName","assistantPhone","reportsTo","reportsToName","mailingAddress","otherAddress","doNotCall","companyName","lifecycleStage","leadSource","owner","ownerUid","marketingConsent","tags","orders","ordersCount","lastPurchase","lastPurchaseAtMs","lastEmailEngagement","lastEmailEngagementAtMs","customFields","timeline","activities","openTasks","companyFacts","domain","website","industry","rating","ownership","accountSource","employees","numberOfEmployees","annualRevenue","annualRevenueCents","accountNumber","site","tickerSymbol","sicCode","billingAddress","shippingAddress","parentCompany","parentCompanyName","people","contactsCount","createdAt","dealFacts","stages","id","probability","min","forecastCategory","products","lineItems","item","quantity","unitAmount","unitAmountCents","inStageSince","stageChangedAtMs","lostReason","nextStep","campaign","campaignName","contactName","contactRoles","dealContactRoleFacts","contactNames","Map","primary","contactId","role","leadSourceFact","import","startsWith","form","leadFacts","leadStatuses","rawSources","captures","email","campaigns","campaignNames","firstSeen","lastSeen","assigned","converted","unqualifiedReason","now","TRANSFER_TYPES","url","number","integer","percent","date","datetime","boolean","transferImportFields","fields","writable","readOnly","derived","system","labels","fromEntries","required","find","types","flatMap","STANDARD_IMPORT_FIELDS","contacts","companies","leads","CUSTOM_FIELD_OBJECT","CUSTOM_FIELD_TYPES","checkbox","importFacts","collection","standard"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 CrmTask, type CrmTaskKind, type CrmTaskPicklists, type CrmTaskPriority } from '@aglyn/aglyn/app-utils/crm';
|
|
18
|
+
/** What a task write stores for its type, priority and status. */
|
|
19
|
+
export interface CrmTaskPicklistWrite {
|
|
20
|
+
kind: CrmTaskKind;
|
|
21
|
+
priority: CrmTaskPriority;
|
|
22
|
+
typeLabel: string | null;
|
|
23
|
+
priorityLabel: string | null;
|
|
24
|
+
statusLabel: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** What a writer asked for: a meaning, a label beside it, or both. */
|
|
27
|
+
export interface CrmTaskPicklistRequest {
|
|
28
|
+
kind: CrmTaskKind;
|
|
29
|
+
priority: CrmTaskPriority;
|
|
30
|
+
typeLabel?: string | null;
|
|
31
|
+
priorityLabel?: string | null;
|
|
32
|
+
statusLabel?: string | null;
|
|
33
|
+
}
|
|
34
|
+
/** The stored task an edit starts from, or `null` for a new one. */
|
|
35
|
+
export type CrmTaskPicklistExisting = Partial<Pick<CrmTask, 'kind' | 'priority' | 'status' | 'typeLabel' | 'priorityLabel' | 'statusLabel'>> | null;
|
|
36
|
+
/**
|
|
37
|
+
* The type, priority and status labels a save stores (AGL-3517), against
|
|
38
|
+
* the org's lists — or the sentence it is refused with.
|
|
39
|
+
*
|
|
40
|
+
* Type and priority follow the label when one is named. Status does not
|
|
41
|
+
* move: a save may relabel an open task with another open value ("In
|
|
42
|
+
* Progress") or a done one with another done value, but the tick is what
|
|
43
|
+
* completes a task — it fires the workflow event — so a label of the other
|
|
44
|
+
* meaning is refused rather than taken as a completion.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveCrmTaskPicklistWrite(picklists: CrmTaskPicklists, request: CrmTaskPicklistRequest, existing: CrmTaskPicklistExisting): {
|
|
47
|
+
ok: true;
|
|
48
|
+
write: CrmTaskPicklistWrite;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
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 { CRM_TASK_PICKLIST_IDS, crmPicklistLabelForNew, resolveCrmSemanticPicklistWrite } from "@aglyn/aglyn/app-utils/crm";
|
|
17
|
+
/**
|
|
18
|
+
* One semantic field: a label wins and carries its value's meaning; with
|
|
19
|
+
* none, the task keeps its own label while it still means `meaning`, and
|
|
20
|
+
* otherwise takes the label a new task of that meaning starts with.
|
|
21
|
+
*/ function resolveOne(id, picklist, meaning, label, held) {
|
|
22
|
+
if (label) {
|
|
23
|
+
const resolved = resolveCrmSemanticPicklistWrite(id, picklist, label, held == null ? void 0 : held.label);
|
|
24
|
+
if (resolved) return resolved;
|
|
25
|
+
}
|
|
26
|
+
if ((held == null ? void 0 : held.label) && held.meaning === meaning) return {
|
|
27
|
+
ok: true,
|
|
28
|
+
meaning,
|
|
29
|
+
label: held.label
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
ok: true,
|
|
33
|
+
meaning,
|
|
34
|
+
label: crmPicklistLabelForNew(picklist, meaning)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The type, priority and status labels a save stores (AGL-3517), against
|
|
39
|
+
* the org's lists — or the sentence it is refused with.
|
|
40
|
+
*
|
|
41
|
+
* Type and priority follow the label when one is named. Status does not
|
|
42
|
+
* move: a save may relabel an open task with another open value ("In
|
|
43
|
+
* Progress") or a done one with another done value, but the tick is what
|
|
44
|
+
* completes a task — it fires the workflow event — so a label of the other
|
|
45
|
+
* meaning is refused rather than taken as a completion.
|
|
46
|
+
*/ export function resolveCrmTaskPicklistWrite(picklists, request, existing) {
|
|
47
|
+
const type = resolveOne(CRM_TASK_PICKLIST_IDS.type, picklists.type, request.kind, request.typeLabel, existing ? {
|
|
48
|
+
meaning: existing.kind,
|
|
49
|
+
label: existing.typeLabel
|
|
50
|
+
} : null);
|
|
51
|
+
if (type.ok === false) return type;
|
|
52
|
+
const priority = resolveOne(CRM_TASK_PICKLIST_IDS.priority, picklists.priority, request.priority, request.priorityLabel, existing ? {
|
|
53
|
+
meaning: existing.priority,
|
|
54
|
+
label: existing.priorityLabel
|
|
55
|
+
} : null);
|
|
56
|
+
if (priority.ok === false) return priority;
|
|
57
|
+
const status = (existing == null ? void 0 : existing.status) === 'done' ? 'done' : 'open';
|
|
58
|
+
const statusLabel = resolveOne(CRM_TASK_PICKLIST_IDS.status, picklists.status, status, request.statusLabel, existing ? {
|
|
59
|
+
meaning: existing.status,
|
|
60
|
+
label: existing.statusLabel
|
|
61
|
+
} : {
|
|
62
|
+
meaning: 'open'
|
|
63
|
+
});
|
|
64
|
+
if (statusLabel.ok === false) return statusLabel;
|
|
65
|
+
if (statusLabel.meaning !== status) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: status === 'open' ? `“${statusLabel.label}” completes a task. Tick the task done instead.` : `“${statusLabel.label}” is an open status. Reopen the task instead.`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
write: {
|
|
74
|
+
kind: type.meaning,
|
|
75
|
+
priority: priority.meaning,
|
|
76
|
+
typeLabel: type.label,
|
|
77
|
+
priorityLabel: priority.label,
|
|
78
|
+
statusLabel: statusLabel.label
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=task-picklists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/task-picklists.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 {\n CRM_TASK_PICKLIST_IDS,\n type CrmPicklist,\n type CrmPicklistId,\n crmPicklistLabelForNew,\n type CrmTask,\n type CrmTaskKind,\n type CrmTaskPicklists,\n type CrmTaskPriority,\n type CrmTaskStatus,\n resolveCrmSemanticPicklistWrite,\n} from '@aglyn/aglyn/app-utils/crm'\n\n/** What a task write stores for its type, priority and status. */\nexport interface CrmTaskPicklistWrite {\n kind: CrmTaskKind\n priority: CrmTaskPriority\n typeLabel: string | null\n priorityLabel: string | null\n statusLabel: string | null\n}\n\n/** What a writer asked for: a meaning, a label beside it, or both. */\nexport interface CrmTaskPicklistRequest {\n kind: CrmTaskKind\n priority: CrmTaskPriority\n typeLabel?: string | null\n priorityLabel?: string | null\n statusLabel?: string | null\n}\n\n/** The stored task an edit starts from, or `null` for a new one. */\nexport type CrmTaskPicklistExisting = Partial<\n Pick<CrmTask, 'kind' | 'priority' | 'status' | 'typeLabel' | 'priorityLabel' | 'statusLabel'>\n> | null\n\n/**\n * One semantic field: a label wins and carries its value's meaning; with\n * none, the task keeps its own label while it still means `meaning`, and\n * otherwise takes the label a new task of that meaning starts with.\n */\nfunction resolveOne(\n id: CrmPicklistId,\n picklist: CrmPicklist,\n meaning: string,\n label: string | null | undefined,\n held: { meaning?: string; label?: string | null } | null,\n): { ok: true; meaning: string; label: string | null } | { ok: false; error: string } {\n if (label) {\n const resolved = resolveCrmSemanticPicklistWrite(id, picklist, label, held?.label)\n if (resolved) return resolved\n }\n if (held?.label && held.meaning === meaning) return { ok: true, meaning, label: held.label }\n return { ok: true, meaning, label: crmPicklistLabelForNew(picklist, meaning) }\n}\n\n/**\n * The type, priority and status labels a save stores (AGL-3517), against\n * the org's lists — or the sentence it is refused with.\n *\n * Type and priority follow the label when one is named. Status does not\n * move: a save may relabel an open task with another open value (\"In\n * Progress\") or a done one with another done value, but the tick is what\n * completes a task — it fires the workflow event — so a label of the other\n * meaning is refused rather than taken as a completion.\n */\nexport function resolveCrmTaskPicklistWrite(\n picklists: CrmTaskPicklists,\n request: CrmTaskPicklistRequest,\n existing: CrmTaskPicklistExisting,\n): { ok: true; write: CrmTaskPicklistWrite } | { ok: false; error: string } {\n const type = resolveOne(\n CRM_TASK_PICKLIST_IDS.type,\n picklists.type,\n request.kind,\n request.typeLabel,\n existing ? { meaning: existing.kind, label: existing.typeLabel } : null,\n )\n if (type.ok === false) return type\n const priority = resolveOne(\n CRM_TASK_PICKLIST_IDS.priority,\n picklists.priority,\n request.priority,\n request.priorityLabel,\n existing ? { meaning: existing.priority, label: existing.priorityLabel } : null,\n )\n if (priority.ok === false) return priority\n const status: CrmTaskStatus = existing?.status === 'done' ? 'done' : 'open'\n const statusLabel = resolveOne(\n CRM_TASK_PICKLIST_IDS.status,\n picklists.status,\n status,\n request.statusLabel,\n existing ? { meaning: existing.status, label: existing.statusLabel } : { meaning: 'open' },\n )\n if (statusLabel.ok === false) return statusLabel\n if (statusLabel.meaning !== status) {\n return {\n ok: false,\n error:\n status === 'open'\n ? `“${statusLabel.label}” completes a task. Tick the task done instead.`\n : `“${statusLabel.label}” is an open status. Reopen the task instead.`,\n }\n }\n return {\n ok: true,\n write: {\n kind: type.meaning as CrmTaskKind,\n priority: priority.meaning as CrmTaskPriority,\n typeLabel: type.label,\n priorityLabel: priority.label,\n statusLabel: statusLabel.label,\n },\n }\n}\n"],"names":["CRM_TASK_PICKLIST_IDS","crmPicklistLabelForNew","resolveCrmSemanticPicklistWrite","resolveOne","id","picklist","meaning","label","held","resolved","ok","resolveCrmTaskPicklistWrite","picklists","request","existing","type","kind","typeLabel","priority","priorityLabel","status","statusLabel","error","write"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,qBAAqB,EAGrBC,sBAAsB,EAMtBC,+BAA+B,QAC1B,6BAA4B;AAyBnC;;;;CAIC,GACD,SAASC,WACPC,EAAiB,EACjBC,QAAqB,EACrBC,OAAe,EACfC,KAAgC,EAChCC,IAAwD;IAExD,IAAID,OAAO;QACT,MAAME,WAAWP,gCAAgCE,IAAIC,UAAUE,OAAOC,wBAAAA,KAAMD,KAAK;QACjF,IAAIE,UAAU,OAAOA;IACvB;IACA,IAAID,CAAAA,wBAAAA,KAAMD,KAAK,KAAIC,KAAKF,OAAO,KAAKA,SAAS,OAAO;QAAEI,IAAI;QAAMJ;QAASC,OAAOC,KAAKD,KAAK;IAAC;IAC3F,OAAO;QAAEG,IAAI;QAAMJ;QAASC,OAAON,uBAAuBI,UAAUC;IAAS;AAC/E;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASK,4BACdC,SAA2B,EAC3BC,OAA+B,EAC/BC,QAAiC;IAEjC,MAAMC,OAAOZ,WACXH,sBAAsBe,IAAI,EAC1BH,UAAUG,IAAI,EACdF,QAAQG,IAAI,EACZH,QAAQI,SAAS,EACjBH,WAAW;QAAER,SAASQ,SAASE,IAAI;QAAET,OAAOO,SAASG,SAAS;IAAC,IAAI;IAErE,IAAIF,KAAKL,EAAE,KAAK,OAAO,OAAOK;IAC9B,MAAMG,WAAWf,WACfH,sBAAsBkB,QAAQ,EAC9BN,UAAUM,QAAQ,EAClBL,QAAQK,QAAQ,EAChBL,QAAQM,aAAa,EACrBL,WAAW;QAAER,SAASQ,SAASI,QAAQ;QAAEX,OAAOO,SAASK,aAAa;IAAC,IAAI;IAE7E,IAAID,SAASR,EAAE,KAAK,OAAO,OAAOQ;IAClC,MAAME,SAAwBN,CAAAA,4BAAAA,SAAUM,MAAM,MAAK,SAAS,SAAS;IACrE,MAAMC,cAAclB,WAClBH,sBAAsBoB,MAAM,EAC5BR,UAAUQ,MAAM,EAChBA,QACAP,QAAQQ,WAAW,EACnBP,WAAW;QAAER,SAASQ,SAASM,MAAM;QAAEb,OAAOO,SAASO,WAAW;IAAC,IAAI;QAAEf,SAAS;IAAO;IAE3F,IAAIe,YAAYX,EAAE,KAAK,OAAO,OAAOW;IACrC,IAAIA,YAAYf,OAAO,KAAKc,QAAQ;QAClC,OAAO;YACLV,IAAI;YACJY,OACEF,WAAW,SACP,CAAC,CAAC,EAAEC,YAAYd,KAAK,CAAC,+CAA+C,CAAC,GACtE,CAAC,CAAC,EAAEc,YAAYd,KAAK,CAAC,6CAA6C,CAAC;QAC5E;IACF;IACA,OAAO;QACLG,IAAI;QACJa,OAAO;YACLP,MAAMD,KAAKT,OAAO;YAClBY,UAAUA,SAASZ,OAAO;YAC1BW,WAAWF,KAAKR,KAAK;YACrBY,eAAeD,SAASX,KAAK;YAC7Bc,aAAaA,YAAYd,KAAK;QAChC;IACF;AACF"}
|
|
@@ -43,6 +43,19 @@ export interface CrmTaskFields {
|
|
|
43
43
|
contactId: string | null;
|
|
44
44
|
companyId: string | null;
|
|
45
45
|
dealId: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* The org's labels for the kind, the priority and the status (AGL-3517)
|
|
48
|
+
* — values of the `taskType`, `taskPriority` and `taskStatus` picklists.
|
|
49
|
+
* A label wins over the meaning beside it: the route stores the meaning
|
|
50
|
+
* the label's value carries. ABSENT means the route picks the label for
|
|
51
|
+
* the meaning — the task's own when it still means that, else the one a
|
|
52
|
+
* new task of that meaning starts with. A status label may name only a
|
|
53
|
+
* value of the task's own status (open, or done); ticking and unticking
|
|
54
|
+
* are what move a task between the two.
|
|
55
|
+
*/
|
|
56
|
+
typeLabel?: string;
|
|
57
|
+
priorityLabel?: string;
|
|
58
|
+
statusLabel?: string;
|
|
46
59
|
/**
|
|
47
60
|
* When the assignee is reminded (AGL-2659): a time, `null` for none, or
|
|
48
61
|
* ABSENT for "nothing said" — which the route answers with the rule in
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
// The pure module, not the barrel: the server routes import this file.
|
|
18
|
+
import { normalizeCrmPicklistLabel } from "@aglyn/aglyn/app-utils/crm";
|
|
17
19
|
/**
|
|
18
20
|
* The contract between the tasks console and the plugin's two task routes
|
|
19
21
|
* (AGL-2599), in one module both halves import so the field a drawer sends
|
|
@@ -157,6 +159,16 @@ const PRIORITIES = new Set([
|
|
|
157
159
|
};
|
|
158
160
|
}
|
|
159
161
|
}
|
|
162
|
+
// Judged against the org's lists by the route, which reads them.
|
|
163
|
+
const labels = {};
|
|
164
|
+
for (const key of [
|
|
165
|
+
'typeLabel',
|
|
166
|
+
'priorityLabel',
|
|
167
|
+
'statusLabel'
|
|
168
|
+
]){
|
|
169
|
+
const label = normalizeCrmPicklistLabel(raw[key]);
|
|
170
|
+
if (label) labels[key] = label;
|
|
171
|
+
}
|
|
160
172
|
return {
|
|
161
173
|
ok: true,
|
|
162
174
|
fields: _extends({
|
|
@@ -166,7 +178,7 @@ const PRIORITIES = new Set([
|
|
|
166
178
|
dueAtMs,
|
|
167
179
|
assigneeUid: assigneeRaw || null,
|
|
168
180
|
notes
|
|
169
|
-
}, ids, remindAtMs !== undefined ? {
|
|
181
|
+
}, ids, labels, remindAtMs !== undefined ? {
|
|
170
182
|
remindAtMs
|
|
171
183
|
} : {})
|
|
172
184
|
};
|
|
@@ -178,7 +190,7 @@ const PRIORITIES = new Set([
|
|
|
178
190
|
* untouched is a no-op on every field the form owns.
|
|
179
191
|
*/ export function crmTaskFieldsOf(task) {
|
|
180
192
|
var _task_title, _task_kind, _task_priority, _task_notes;
|
|
181
|
-
return {
|
|
193
|
+
return _extends({
|
|
182
194
|
title: (_task_title = task.title) != null ? _task_title : '',
|
|
183
195
|
kind: (_task_kind = task.kind) != null ? _task_kind : 'todo',
|
|
184
196
|
priority: (_task_priority = task.priority) != null ? _task_priority : 'normal',
|
|
@@ -187,11 +199,18 @@ const PRIORITIES = new Set([
|
|
|
187
199
|
notes: (_task_notes = task.notes) != null ? _task_notes : '',
|
|
188
200
|
contactId: task.contactId || null,
|
|
189
201
|
companyId: task.companyId || null,
|
|
190
|
-
dealId: task.dealId || null
|
|
202
|
+
dealId: task.dealId || null
|
|
203
|
+
}, task.typeLabel ? {
|
|
204
|
+
typeLabel: task.typeLabel
|
|
205
|
+
} : {}, task.priorityLabel ? {
|
|
206
|
+
priorityLabel: task.priorityLabel
|
|
207
|
+
} : {}, task.statusLabel ? {
|
|
208
|
+
statusLabel: task.statusLabel
|
|
209
|
+
} : {}, {
|
|
191
210
|
// A stored task from before reminders existed carries no field, and
|
|
192
211
|
// reads as having no reminder — the same answer the route's rule gives.
|
|
193
212
|
remindAtMs: typeof task.remindAtMs === 'number' ? task.remindAtMs : null
|
|
194
|
-
};
|
|
213
|
+
});
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
//# sourceMappingURL=task-routes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/task-routes.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 type { CrmTask, CrmTaskKind, CrmTaskPriority } from '@aglyn/aglyn'\n\n/**\n * The contract between the tasks console and the plugin's two task routes\n * (AGL-2599), in one module both halves import so the field a drawer sends\n * and the field a handler reads cannot drift apart.\n *\n * Route PATHS are relative to the console's plugin API prefix: the handler\n * registers `crm/task-save` and the browser calls `/api/crm/task-save`.\n */\nexport const CRM_TASK_ROUTES = {\n save: 'crm/task-save',\n complete: 'crm/task-complete',\n} as const\n\n/** The browser-side address of a route: `/api/` + the registered path. */\nexport const crmTaskRouteUrl = (\n route: (typeof CRM_TASK_ROUTES)[keyof typeof CRM_TASK_ROUTES],\n): string => `/api/${route}`\n\n/**\n * What a person may set on a task. Everything else on the document — scope,\n * provenance, status, timestamps — is the server's to stamp.\n */\nexport interface CrmTaskFields {\n title: string\n kind: CrmTaskKind\n priority: CrmTaskPriority\n dueAtMs: number | null\n assigneeUid: string | null\n notes: string\n contactId: string | null\n companyId: string | null\n dealId: string | null\n /**\n * When the assignee is reminded (AGL-2659): a time, `null` for none, or\n * ABSENT for \"nothing said\" — which the route answers with the rule in\n * `crmTaskReminderAfterEdit`: a new task's reminder is its due time, and\n * an edit that moves the due date moves a reminder that still sat on it.\n * The drawer leaves it absent until the person touches the field.\n */\n remindAtMs?: number | null\n}\n\n/**\n * Which level a task route call runs at (AGL-2637), the way\n * `readCrmRouteScope` reads it off the body.\n *\n * `hostId` alone is the SITE variant every route has always had: the org is\n * resolved from the site, the caller's role checked on it, the task stamped\n * with the site's scope. `orgId` is the ORGANIZATION variant, authorized by\n * the org for an org-wide member; a `hostId` beside it names the site a NEW\n * task is filed from, and absent, the task is the organization's own — no\n * site, `hostId: null`, the org scope token alone.\n */\nexport type CrmTaskRouteScope =\n | { hostId: string; orgId?: undefined }\n | { orgId: string; hostId?: string | null }\n\n/**\n * The scope a surface calls with, from what it holds: the mounted site, or\n * at the organization level the org the scope hook resolved. `null` until\n * the org is known, which is when a surface has nothing to call with.\n */\nexport function crmTaskCallScope(\n hostId: string | null | undefined,\n orgId: string | null | undefined,\n): CrmTaskRouteScope | null {\n if (hostId) return { hostId }\n if (orgId) return { orgId }\n return null\n}\n\nexport type CrmTaskSaveRequest = CrmTaskRouteScope & {\n /** Absent on create. */\n taskId?: string\n task: CrmTaskFields\n}\n\nexport interface CrmTaskSaveResponse {\n ok: true\n taskId: string\n /** Whether the assignee was told — false when they assigned it to themselves. */\n notified: boolean\n}\n\nexport type CrmTaskCompleteRequest = CrmTaskRouteScope & { taskId: string }\n\nexport interface CrmTaskCompleteResponse {\n ok: true\n completedAtMs: number\n /** The task was already done; nothing was written and no event fired. */\n alreadyDone?: boolean\n}\n\n/**\n * THE BATCH FORMS, organization level only (AGL-2637).\n *\n * The org hub's bulk bar completes or reassigns a selection in ONE request\n * per action: the same two routes, with `taskIds` (complete) or `tasks`\n * (save) in place of one `taskId`, authorized once by the org. Each task is\n * answered on its own — a refusal names the task and carries the route's\n * sentence — so a selection that reaches a task that has since been deleted\n * still completes the rest. The site hub keeps one request per task, where\n * each is authorized against the site.\n *\n * Capped at the list's own window: a selection cannot be larger than what\n * the list showed.\n */\nexport const CRM_TASK_BATCH_MAX = 200\n\nexport interface CrmTasksCompleteRequest {\n orgId: string\n taskIds: string[]\n}\n\nexport type CrmTaskCompleteOutcome =\n | { taskId: string; ok: true; completedAtMs: number; alreadyDone?: boolean }\n | { taskId: string; ok: false; error: string }\n\nexport interface CrmTasksCompleteResponse {\n ok: true\n results: CrmTaskCompleteOutcome[]\n}\n\nexport interface CrmTasksSaveRequest {\n orgId: string\n /** Updates only — a batch never creates. */\n tasks: Array<{ taskId: string; task: CrmTaskFields }>\n}\n\nexport type CrmTaskSaveOutcome =\n | { taskId: string; ok: true; notified: boolean }\n | { taskId: string; ok: false; error: string }\n\nexport interface CrmTasksSaveResponse {\n ok: true\n results: CrmTaskSaveOutcome[]\n}\n\nexport const CRM_TASK_TITLE_MAX = 200\nexport const CRM_TASK_NOTES_MAX = 4000\nconst RECORD_ID_MAX = 200\n\nconst KINDS: ReadonlySet<string> = new Set(['call', 'email', 'meeting', 'todo'])\nconst PRIORITIES: ReadonlySet<string> = new Set(['low', 'normal', 'high'])\n\n/**\n * A typed record id, or `null` for anything that is not one.\n *\n * A Firestore id is opaque but bounded, and it never contains a slash — a\n * slash would make it a path. Refusing here rather than storing keeps a\n * malformed link from becoming a `where` clause nothing can match.\n */\nfunction recordId(value: unknown): string | null {\n const text = String(value ?? '').trim()\n if (!text || text.length > RECORD_ID_MAX || text.includes('/')) return null\n return text\n}\n\n/**\n * The request body as a task, or the reason it is not one.\n *\n * Coerces where a person could not have meant anything else (whitespace\n * around a title, a due date sent as a string of digits) and refuses where a\n * guess would store something the list cannot show (an unknown kind, a title\n * that is nothing but spaces). The refusal is a sentence because the drawer\n * puts it in front of the person who typed the value.\n */\nexport function readCrmTaskFields(\n input: unknown,\n): { ok: true; fields: CrmTaskFields } | { ok: false; error: string } {\n const raw = (input ?? {}) as Record<string, unknown>\n const title = String(raw['title'] ?? '').trim()\n if (!title) return { ok: false, error: 'A task needs a title.' }\n if (title.length > CRM_TASK_TITLE_MAX) {\n return {\n ok: false,\n error: `A title is at most ${CRM_TASK_TITLE_MAX} characters.`,\n }\n }\n const kind = String(raw['kind'] ?? 'todo')\n if (!KINDS.has(kind)) {\n return { ok: false, error: `\"${kind}\" is not a kind of task.` }\n }\n const priority = String(raw['priority'] ?? 'normal')\n if (!PRIORITIES.has(priority)) {\n return { ok: false, error: `\"${priority}\" is not a priority.` }\n }\n const dueRaw = raw['dueAtMs']\n let dueAtMs: number | null = null\n if (dueRaw !== null && dueRaw !== undefined && dueRaw !== '') {\n const ms = Number(dueRaw)\n if (!Number.isFinite(ms) || ms <= 0) {\n return { ok: false, error: 'The due date could not be read.' }\n }\n dueAtMs = Math.round(ms)\n }\n // Three answers, not two: a reminder the body leaves out is the route's\n // to decide, and only an explicit null or empty string means \"none\".\n const remindRaw = raw['remindAtMs']\n let remindAtMs: number | null | undefined\n if (remindRaw === null || remindRaw === '') {\n remindAtMs = null\n } else if (remindRaw !== undefined) {\n const ms = Number(remindRaw)\n if (!Number.isFinite(ms) || ms <= 0) {\n return { ok: false, error: 'The reminder time could not be read.' }\n }\n remindAtMs = Math.round(ms)\n }\n const assigneeRaw = String(raw['assigneeUid'] ?? '').trim()\n const notes = String(raw['notes'] ?? '').slice(0, CRM_TASK_NOTES_MAX)\n const ids = {\n contactId: raw['contactId'] == null || raw['contactId'] === ''\n ? null\n : recordId(raw['contactId']),\n companyId: raw['companyId'] == null || raw['companyId'] === ''\n ? null\n : recordId(raw['companyId']),\n dealId: raw['dealId'] == null || raw['dealId'] === ''\n ? null\n : recordId(raw['dealId']),\n }\n for (const [field, value] of Object.entries(ids)) {\n if (value === null && raw[field] != null && raw[field] !== '') {\n return { ok: false, error: `The linked ${field.replace('Id', '')} could not be read.` }\n }\n }\n return {\n ok: true,\n fields: {\n title,\n kind: kind as CrmTaskKind,\n priority: priority as CrmTaskPriority,\n dueAtMs,\n assigneeUid: assigneeRaw || null,\n notes,\n ...ids,\n ...(remindAtMs !== undefined ? { remindAtMs } : {}),\n },\n }\n}\n\n/**\n * The stored document's editable half, as the drawer's form reads it.\n *\n * The inverse of what the save route writes, so opening a task and saving it\n * untouched is a no-op on every field the form owns.\n */\nexport function crmTaskFieldsOf(task: Partial<CrmTask>): CrmTaskFields {\n return {\n title: task.title ?? '',\n kind: task.kind ?? 'todo',\n priority: task.priority ?? 'normal',\n dueAtMs: typeof task.dueAtMs === 'number' ? task.dueAtMs : null,\n assigneeUid: task.assigneeUid || null,\n notes: task.notes ?? '',\n contactId: task.contactId || null,\n companyId: task.companyId || null,\n dealId: task.dealId || null,\n // A stored task from before reminders existed carries no field, and\n // reads as having no reminder — the same answer the route's rule gives.\n remindAtMs: typeof task.remindAtMs === 'number' ? task.remindAtMs : null,\n }\n}\n"],"names":["CRM_TASK_ROUTES","save","complete","crmTaskRouteUrl","route","crmTaskCallScope","hostId","orgId","CRM_TASK_BATCH_MAX","CRM_TASK_TITLE_MAX","CRM_TASK_NOTES_MAX","RECORD_ID_MAX","KINDS","Set","PRIORITIES","recordId","value","text","String","trim","length","includes","readCrmTaskFields","input","raw","title","ok","error","kind","has","priority","dueRaw","dueAtMs","undefined","ms","Number","isFinite","Math","round","remindRaw","remindAtMs","assigneeRaw","notes","slice","ids","contactId","companyId","dealId","field","Object","entries","replace","fields","assigneeUid","crmTaskFieldsOf","task"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/task-routes.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 type { CrmTask, CrmTaskKind, CrmTaskPriority } from '@aglyn/aglyn'\n// The pure module, not the barrel: the server routes import this file.\nimport { normalizeCrmPicklistLabel } from '@aglyn/aglyn/app-utils/crm'\n\n/**\n * The contract between the tasks console and the plugin's two task routes\n * (AGL-2599), in one module both halves import so the field a drawer sends\n * and the field a handler reads cannot drift apart.\n *\n * Route PATHS are relative to the console's plugin API prefix: the handler\n * registers `crm/task-save` and the browser calls `/api/crm/task-save`.\n */\nexport const CRM_TASK_ROUTES = {\n save: 'crm/task-save',\n complete: 'crm/task-complete',\n} as const\n\n/** The browser-side address of a route: `/api/` + the registered path. */\nexport const crmTaskRouteUrl = (\n route: (typeof CRM_TASK_ROUTES)[keyof typeof CRM_TASK_ROUTES],\n): string => `/api/${route}`\n\n/**\n * What a person may set on a task. Everything else on the document — scope,\n * provenance, status, timestamps — is the server's to stamp.\n */\nexport interface CrmTaskFields {\n title: string\n kind: CrmTaskKind\n priority: CrmTaskPriority\n dueAtMs: number | null\n assigneeUid: string | null\n notes: string\n contactId: string | null\n companyId: string | null\n dealId: string | null\n /**\n * The org's labels for the kind, the priority and the status (AGL-3517)\n * — values of the `taskType`, `taskPriority` and `taskStatus` picklists.\n * A label wins over the meaning beside it: the route stores the meaning\n * the label's value carries. ABSENT means the route picks the label for\n * the meaning — the task's own when it still means that, else the one a\n * new task of that meaning starts with. A status label may name only a\n * value of the task's own status (open, or done); ticking and unticking\n * are what move a task between the two.\n */\n typeLabel?: string\n priorityLabel?: string\n statusLabel?: string\n /**\n * When the assignee is reminded (AGL-2659): a time, `null` for none, or\n * ABSENT for \"nothing said\" — which the route answers with the rule in\n * `crmTaskReminderAfterEdit`: a new task's reminder is its due time, and\n * an edit that moves the due date moves a reminder that still sat on it.\n * The drawer leaves it absent until the person touches the field.\n */\n remindAtMs?: number | null\n}\n\n/**\n * Which level a task route call runs at (AGL-2637), the way\n * `readCrmRouteScope` reads it off the body.\n *\n * `hostId` alone is the SITE variant every route has always had: the org is\n * resolved from the site, the caller's role checked on it, the task stamped\n * with the site's scope. `orgId` is the ORGANIZATION variant, authorized by\n * the org for an org-wide member; a `hostId` beside it names the site a NEW\n * task is filed from, and absent, the task is the organization's own — no\n * site, `hostId: null`, the org scope token alone.\n */\nexport type CrmTaskRouteScope =\n | { hostId: string; orgId?: undefined }\n | { orgId: string; hostId?: string | null }\n\n/**\n * The scope a surface calls with, from what it holds: the mounted site, or\n * at the organization level the org the scope hook resolved. `null` until\n * the org is known, which is when a surface has nothing to call with.\n */\nexport function crmTaskCallScope(\n hostId: string | null | undefined,\n orgId: string | null | undefined,\n): CrmTaskRouteScope | null {\n if (hostId) return { hostId }\n if (orgId) return { orgId }\n return null\n}\n\nexport type CrmTaskSaveRequest = CrmTaskRouteScope & {\n /** Absent on create. */\n taskId?: string\n task: CrmTaskFields\n}\n\nexport interface CrmTaskSaveResponse {\n ok: true\n taskId: string\n /** Whether the assignee was told — false when they assigned it to themselves. */\n notified: boolean\n}\n\nexport type CrmTaskCompleteRequest = CrmTaskRouteScope & { taskId: string }\n\nexport interface CrmTaskCompleteResponse {\n ok: true\n completedAtMs: number\n /** The task was already done; nothing was written and no event fired. */\n alreadyDone?: boolean\n}\n\n/**\n * THE BATCH FORMS, organization level only (AGL-2637).\n *\n * The org hub's bulk bar completes or reassigns a selection in ONE request\n * per action: the same two routes, with `taskIds` (complete) or `tasks`\n * (save) in place of one `taskId`, authorized once by the org. Each task is\n * answered on its own — a refusal names the task and carries the route's\n * sentence — so a selection that reaches a task that has since been deleted\n * still completes the rest. The site hub keeps one request per task, where\n * each is authorized against the site.\n *\n * Capped at the list's own window: a selection cannot be larger than what\n * the list showed.\n */\nexport const CRM_TASK_BATCH_MAX = 200\n\nexport interface CrmTasksCompleteRequest {\n orgId: string\n taskIds: string[]\n}\n\nexport type CrmTaskCompleteOutcome =\n | { taskId: string; ok: true; completedAtMs: number; alreadyDone?: boolean }\n | { taskId: string; ok: false; error: string }\n\nexport interface CrmTasksCompleteResponse {\n ok: true\n results: CrmTaskCompleteOutcome[]\n}\n\nexport interface CrmTasksSaveRequest {\n orgId: string\n /** Updates only — a batch never creates. */\n tasks: Array<{ taskId: string; task: CrmTaskFields }>\n}\n\nexport type CrmTaskSaveOutcome =\n | { taskId: string; ok: true; notified: boolean }\n | { taskId: string; ok: false; error: string }\n\nexport interface CrmTasksSaveResponse {\n ok: true\n results: CrmTaskSaveOutcome[]\n}\n\nexport const CRM_TASK_TITLE_MAX = 200\nexport const CRM_TASK_NOTES_MAX = 4000\nconst RECORD_ID_MAX = 200\n\nconst KINDS: ReadonlySet<string> = new Set(['call', 'email', 'meeting', 'todo'])\nconst PRIORITIES: ReadonlySet<string> = new Set(['low', 'normal', 'high'])\n\n/**\n * A typed record id, or `null` for anything that is not one.\n *\n * A Firestore id is opaque but bounded, and it never contains a slash — a\n * slash would make it a path. Refusing here rather than storing keeps a\n * malformed link from becoming a `where` clause nothing can match.\n */\nfunction recordId(value: unknown): string | null {\n const text = String(value ?? '').trim()\n if (!text || text.length > RECORD_ID_MAX || text.includes('/')) return null\n return text\n}\n\n/**\n * The request body as a task, or the reason it is not one.\n *\n * Coerces where a person could not have meant anything else (whitespace\n * around a title, a due date sent as a string of digits) and refuses where a\n * guess would store something the list cannot show (an unknown kind, a title\n * that is nothing but spaces). The refusal is a sentence because the drawer\n * puts it in front of the person who typed the value.\n */\nexport function readCrmTaskFields(\n input: unknown,\n): { ok: true; fields: CrmTaskFields } | { ok: false; error: string } {\n const raw = (input ?? {}) as Record<string, unknown>\n const title = String(raw['title'] ?? '').trim()\n if (!title) return { ok: false, error: 'A task needs a title.' }\n if (title.length > CRM_TASK_TITLE_MAX) {\n return {\n ok: false,\n error: `A title is at most ${CRM_TASK_TITLE_MAX} characters.`,\n }\n }\n const kind = String(raw['kind'] ?? 'todo')\n if (!KINDS.has(kind)) {\n return { ok: false, error: `\"${kind}\" is not a kind of task.` }\n }\n const priority = String(raw['priority'] ?? 'normal')\n if (!PRIORITIES.has(priority)) {\n return { ok: false, error: `\"${priority}\" is not a priority.` }\n }\n const dueRaw = raw['dueAtMs']\n let dueAtMs: number | null = null\n if (dueRaw !== null && dueRaw !== undefined && dueRaw !== '') {\n const ms = Number(dueRaw)\n if (!Number.isFinite(ms) || ms <= 0) {\n return { ok: false, error: 'The due date could not be read.' }\n }\n dueAtMs = Math.round(ms)\n }\n // Three answers, not two: a reminder the body leaves out is the route's\n // to decide, and only an explicit null or empty string means \"none\".\n const remindRaw = raw['remindAtMs']\n let remindAtMs: number | null | undefined\n if (remindRaw === null || remindRaw === '') {\n remindAtMs = null\n } else if (remindRaw !== undefined) {\n const ms = Number(remindRaw)\n if (!Number.isFinite(ms) || ms <= 0) {\n return { ok: false, error: 'The reminder time could not be read.' }\n }\n remindAtMs = Math.round(ms)\n }\n const assigneeRaw = String(raw['assigneeUid'] ?? '').trim()\n const notes = String(raw['notes'] ?? '').slice(0, CRM_TASK_NOTES_MAX)\n const ids = {\n contactId: raw['contactId'] == null || raw['contactId'] === ''\n ? null\n : recordId(raw['contactId']),\n companyId: raw['companyId'] == null || raw['companyId'] === ''\n ? null\n : recordId(raw['companyId']),\n dealId: raw['dealId'] == null || raw['dealId'] === ''\n ? null\n : recordId(raw['dealId']),\n }\n for (const [field, value] of Object.entries(ids)) {\n if (value === null && raw[field] != null && raw[field] !== '') {\n return { ok: false, error: `The linked ${field.replace('Id', '')} could not be read.` }\n }\n }\n // Judged against the org's lists by the route, which reads them.\n const labels: Pick<CrmTaskFields, 'typeLabel' | 'priorityLabel' | 'statusLabel'> = {}\n for (const key of ['typeLabel', 'priorityLabel', 'statusLabel'] as const) {\n const label = normalizeCrmPicklistLabel(raw[key])\n if (label) labels[key] = label\n }\n return {\n ok: true,\n fields: {\n title,\n kind: kind as CrmTaskKind,\n priority: priority as CrmTaskPriority,\n dueAtMs,\n assigneeUid: assigneeRaw || null,\n notes,\n ...ids,\n ...labels,\n ...(remindAtMs !== undefined ? { remindAtMs } : {}),\n },\n }\n}\n\n/**\n * The stored document's editable half, as the drawer's form reads it.\n *\n * The inverse of what the save route writes, so opening a task and saving it\n * untouched is a no-op on every field the form owns.\n */\nexport function crmTaskFieldsOf(task: Partial<CrmTask>): CrmTaskFields {\n return {\n title: task.title ?? '',\n kind: task.kind ?? 'todo',\n priority: task.priority ?? 'normal',\n dueAtMs: typeof task.dueAtMs === 'number' ? task.dueAtMs : null,\n assigneeUid: task.assigneeUid || null,\n notes: task.notes ?? '',\n contactId: task.contactId || null,\n companyId: task.companyId || null,\n dealId: task.dealId || null,\n // The stored labels, so a save that changes none keeps them; a task\n // from before the picklists carries none and shows its meaning's.\n ...(task.typeLabel ? { typeLabel: task.typeLabel } : {}),\n ...(task.priorityLabel ? { priorityLabel: task.priorityLabel } : {}),\n ...(task.statusLabel ? { statusLabel: task.statusLabel } : {}),\n // A stored task from before reminders existed carries no field, and\n // reads as having no reminder — the same answer the route's rule gives.\n remindAtMs: typeof task.remindAtMs === 'number' ? task.remindAtMs : null,\n }\n}\n"],"names":["normalizeCrmPicklistLabel","CRM_TASK_ROUTES","save","complete","crmTaskRouteUrl","route","crmTaskCallScope","hostId","orgId","CRM_TASK_BATCH_MAX","CRM_TASK_TITLE_MAX","CRM_TASK_NOTES_MAX","RECORD_ID_MAX","KINDS","Set","PRIORITIES","recordId","value","text","String","trim","length","includes","readCrmTaskFields","input","raw","title","ok","error","kind","has","priority","dueRaw","dueAtMs","undefined","ms","Number","isFinite","Math","round","remindRaw","remindAtMs","assigneeRaw","notes","slice","ids","contactId","companyId","dealId","field","Object","entries","replace","labels","key","label","fields","assigneeUid","crmTaskFieldsOf","task","typeLabel","priorityLabel","statusLabel"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,uEAAuE;AACvE,SAASA,yBAAyB,QAAQ,6BAA4B;AAEtE;;;;;;;CAOC,GACD,OAAO,MAAMC,kBAAkB;IAC7BC,MAAM;IACNC,UAAU;AACZ,EAAU;AAEV,wEAAwE,GACxE,OAAO,MAAMC,kBAAkB,CAC7BC,QACW,CAAC,KAAK,EAAEA,OAAO,CAAA;AAsD5B;;;;CAIC,GACD,OAAO,SAASC,iBACdC,MAAiC,EACjCC,KAAgC;IAEhC,IAAID,QAAQ,OAAO;QAAEA;IAAO;IAC5B,IAAIC,OAAO,OAAO;QAAEA;IAAM;IAC1B,OAAO;AACT;AAwBA;;;;;;;;;;;;;CAaC,GACD,OAAO,MAAMC,qBAAqB,IAAG;AA+BrC,OAAO,MAAMC,qBAAqB,IAAG;AACrC,OAAO,MAAMC,qBAAqB,KAAI;AACtC,MAAMC,gBAAgB;AAEtB,MAAMC,QAA6B,IAAIC,IAAI;IAAC;IAAQ;IAAS;IAAW;CAAO;AAC/E,MAAMC,aAAkC,IAAID,IAAI;IAAC;IAAO;IAAU;CAAO;AAEzE;;;;;;CAMC,GACD,SAASE,SAASC,KAAc;IAC9B,MAAMC,OAAOC,OAAOF,gBAAAA,QAAS,IAAIG,IAAI;IACrC,IAAI,CAACF,QAAQA,KAAKG,MAAM,GAAGT,iBAAiBM,KAAKI,QAAQ,CAAC,MAAM,OAAO;IACvE,OAAOJ;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASK,kBACdC,KAAc;QAGOC,YAQDA,WAIIA,eA0BGA,kBACNA;IAxCrB,MAAMA,MAAOD,gBAAAA,QAAS,CAAC;IACvB,MAAME,QAAQP,QAAOM,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB,IAAIL,IAAI;IAC7C,IAAI,CAACM,OAAO,OAAO;QAAEC,IAAI;QAAOC,OAAO;IAAwB;IAC/D,IAAIF,MAAML,MAAM,GAAGX,oBAAoB;QACrC,OAAO;YACLiB,IAAI;YACJC,OAAO,CAAC,mBAAmB,EAAElB,mBAAmB,YAAY,CAAC;QAC/D;IACF;IACA,MAAMmB,OAAOV,QAAOM,YAAAA,GAAG,CAAC,OAAO,YAAXA,YAAe;IACnC,IAAI,CAACZ,MAAMiB,GAAG,CAACD,OAAO;QACpB,OAAO;YAAEF,IAAI;YAAOC,OAAO,CAAC,CAAC,EAAEC,KAAK,wBAAwB,CAAC;QAAC;IAChE;IACA,MAAME,WAAWZ,QAAOM,gBAAAA,GAAG,CAAC,WAAW,YAAfA,gBAAmB;IAC3C,IAAI,CAACV,WAAWe,GAAG,CAACC,WAAW;QAC7B,OAAO;YAAEJ,IAAI;YAAOC,OAAO,CAAC,CAAC,EAAEG,SAAS,oBAAoB,CAAC;QAAC;IAChE;IACA,MAAMC,SAASP,GAAG,CAAC,UAAU;IAC7B,IAAIQ,UAAyB;IAC7B,IAAID,WAAW,QAAQA,WAAWE,aAAaF,WAAW,IAAI;QAC5D,MAAMG,KAAKC,OAAOJ;QAClB,IAAI,CAACI,OAAOC,QAAQ,CAACF,OAAOA,MAAM,GAAG;YACnC,OAAO;gBAAER,IAAI;gBAAOC,OAAO;YAAkC;QAC/D;QACAK,UAAUK,KAAKC,KAAK,CAACJ;IACvB;IACA,wEAAwE;IACxE,qEAAqE;IACrE,MAAMK,YAAYf,GAAG,CAAC,aAAa;IACnC,IAAIgB;IACJ,IAAID,cAAc,QAAQA,cAAc,IAAI;QAC1CC,aAAa;IACf,OAAO,IAAID,cAAcN,WAAW;QAClC,MAAMC,KAAKC,OAAOI;QAClB,IAAI,CAACJ,OAAOC,QAAQ,CAACF,OAAOA,MAAM,GAAG;YACnC,OAAO;gBAAER,IAAI;gBAAOC,OAAO;YAAuC;QACpE;QACAa,aAAaH,KAAKC,KAAK,CAACJ;IAC1B;IACA,MAAMO,cAAcvB,QAAOM,mBAAAA,GAAG,CAAC,cAAc,YAAlBA,mBAAsB,IAAIL,IAAI;IACzD,MAAMuB,QAAQxB,QAAOM,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB,IAAImB,KAAK,CAAC,GAAGjC;IAClD,MAAMkC,MAAM;QACVC,WAAWrB,GAAG,CAAC,YAAY,IAAI,QAAQA,GAAG,CAAC,YAAY,KAAK,KACxD,OACAT,SAASS,GAAG,CAAC,YAAY;QAC7BsB,WAAWtB,GAAG,CAAC,YAAY,IAAI,QAAQA,GAAG,CAAC,YAAY,KAAK,KACxD,OACAT,SAASS,GAAG,CAAC,YAAY;QAC7BuB,QAAQvB,GAAG,CAAC,SAAS,IAAI,QAAQA,GAAG,CAAC,SAAS,KAAK,KAC/C,OACAT,SAASS,GAAG,CAAC,SAAS;IAC5B;IACA,KAAK,MAAM,CAACwB,OAAOhC,MAAM,IAAIiC,OAAOC,OAAO,CAACN,KAAM;QAChD,IAAI5B,UAAU,QAAQQ,GAAG,CAACwB,MAAM,IAAI,QAAQxB,GAAG,CAACwB,MAAM,KAAK,IAAI;YAC7D,OAAO;gBAAEtB,IAAI;gBAAOC,OAAO,CAAC,WAAW,EAAEqB,MAAMG,OAAO,CAAC,MAAM,IAAI,mBAAmB,CAAC;YAAC;QACxF;IACF;IACA,iEAAiE;IACjE,MAAMC,SAA6E,CAAC;IACpF,KAAK,MAAMC,OAAO;QAAC;QAAa;QAAiB;KAAc,CAAW;QACxE,MAAMC,QAAQvD,0BAA0ByB,GAAG,CAAC6B,IAAI;QAChD,IAAIC,OAAOF,MAAM,CAACC,IAAI,GAAGC;IAC3B;IACA,OAAO;QACL5B,IAAI;QACJ6B,QAAQ;YACN9B;YACAG,MAAMA;YACNE,UAAUA;YACVE;YACAwB,aAAaf,eAAe;YAC5BC;WACGE,KACAQ,QACCZ,eAAeP,YAAY;YAAEO;QAAW,IAAI,CAAC;IAErD;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASiB,gBAAgBC,IAAsB;QAE3CA,aACDA,YACIA,gBAGHA;IANT,OAAO;QACLjC,KAAK,GAAEiC,cAAAA,KAAKjC,KAAK,YAAViC,cAAc;QACrB9B,IAAI,GAAE8B,aAAAA,KAAK9B,IAAI,YAAT8B,aAAa;QACnB5B,QAAQ,GAAE4B,iBAAAA,KAAK5B,QAAQ,YAAb4B,iBAAiB;QAC3B1B,SAAS,OAAO0B,KAAK1B,OAAO,KAAK,WAAW0B,KAAK1B,OAAO,GAAG;QAC3DwB,aAAaE,KAAKF,WAAW,IAAI;QACjCd,KAAK,GAAEgB,cAAAA,KAAKhB,KAAK,YAAVgB,cAAc;QACrBb,WAAWa,KAAKb,SAAS,IAAI;QAC7BC,WAAWY,KAAKZ,SAAS,IAAI;QAC7BC,QAAQW,KAAKX,MAAM,IAAI;OAGnBW,KAAKC,SAAS,GAAG;QAAEA,WAAWD,KAAKC,SAAS;IAAC,IAAI,CAAC,GAClDD,KAAKE,aAAa,GAAG;QAAEA,eAAeF,KAAKE,aAAa;IAAC,IAAI,CAAC,GAC9DF,KAAKG,WAAW,GAAG;QAAEA,aAAaH,KAAKG,WAAW;IAAC,IAAI,CAAC;QAC5D,oEAAoE;QACpE,wEAAwE;QACxErB,YAAY,OAAOkB,KAAKlB,UAAU,KAAK,WAAWkB,KAAKlB,UAAU,GAAG;;AAExE"}
|
|
@@ -25,10 +25,12 @@
|
|
|
25
25
|
// One-to-one email from a CRM record, per UTC day (AGL-2611). A hard daily
|
|
26
26
|
// pace rather than a monthly meter, sized so the whole day spent at $0.0009
|
|
27
27
|
// a message leaves the tier's CRM axis inside the 20% cost share the Drive
|
|
28
|
-
// pricing decision of 2026-09-05 sets.
|
|
29
|
-
// $16 annual price
|
|
30
|
-
//
|
|
31
|
-
|
|
28
|
+
// pricing decision of 2026-09-05 sets. 35 a day is $0.95 a month against a
|
|
29
|
+
// $16 annual price — 50 was, until the whole Starter tier had to cover its
|
|
30
|
+
// full-use cost plus 30% (AGL-3469). Every send still lands on the
|
|
31
|
+
// `emailSends` cost meter beside the transactional mail this tier already
|
|
32
|
+
// sends.
|
|
33
|
+
starter: 35,
|
|
32
34
|
pro: 150,
|
|
33
35
|
business: 200,
|
|
34
36
|
scale: 300,
|