@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.223
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +13 -13
- package/src/lib/components/activity-list.js +12 -5
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.d.ts +1 -4
- package/src/lib/components/companies-bulk-bar.js +7 -20
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.d.ts +3 -3
- package/src/lib/components/companies-section.js +149 -46
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +281 -20
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-properties-card.js +99 -8
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +3 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +33 -10
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +0 -69
- package/src/lib/components/contact-properties-card.js +350 -60
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-reports-to-field.d.ts +46 -0
- package/src/lib/components/contact-reports-to-field.js +67 -0
- package/src/lib/components/contact-reports-to-field.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
- package/src/lib/components/contacts-bulk-bar.js +7 -19
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +46 -33
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-call-actions.d.ts +10 -0
- package/src/lib/components/crm-call-actions.js +26 -9
- package/src/lib/components/crm-call-actions.js.map +1 -1
- package/src/lib/components/crm-next-activity-column.js +1 -1
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +15 -0
- package/src/lib/components/crm-record-picker.js +16 -10
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
- package/src/lib/components/crm-transfer-buttons.js +167 -0
- package/src/lib/components/crm-transfer-buttons.js.map +1 -0
- package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
- package/src/lib/components/deal-contact-roles-card.js +368 -0
- package/src/lib/components/deal-contact-roles-card.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +3 -2
- package/src/lib/components/deal-detail-page.js +25 -6
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.d.ts +10 -0
- package/src/lib/components/deal-edit-drawer.js +219 -10
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-properties-card.d.ts +7 -0
- package/src/lib/components/deal-properties-card.js +34 -3
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.d.ts +4 -0
- package/src/lib/components/deal-stage-card.js +10 -2
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.d.ts +0 -3
- package/src/lib/components/deals-bulk-bar.js +7 -20
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/deals-section.d.ts +3 -3
- package/src/lib/components/deals-section.js +123 -63
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +31 -17
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-convert-dialog.d.ts +4 -0
- package/src/lib/components/lead-convert-dialog.js +23 -1
- package/src/lib/components/lead-convert-dialog.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +8 -2
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-profile-fields.d.ts +71 -0
- package/src/lib/components/lead-profile-fields.js +386 -0
- package/src/lib/components/lead-profile-fields.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +2 -1
- package/src/lib/components/lead-properties-card.js +85 -176
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-source-select.d.ts +4 -23
- package/src/lib/components/lead-source-select.js +9 -73
- package/src/lib/components/lead-source-select.js.map +1 -1
- package/src/lib/components/lead-status-chip.d.ts +9 -3
- package/src/lib/components/lead-status-chip.js +8 -4
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-status-options.d.ts +20 -0
- package/src/lib/components/lead-status-options.js +51 -0
- package/src/lib/components/lead-status-options.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
- package/src/lib/components/lead-unqualify-dialog.js +50 -18
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.d.ts +0 -3
- package/src/lib/components/leads-bulk-bar.js +29 -29
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +120 -68
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.d.ts +4 -0
- package/src/lib/components/linked-deals-card.js +47 -3
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.js +43 -3
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +8 -0
- package/src/lib/components/new-contact-drawer.js +108 -6
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/new-lead-drawer.d.ts +9 -2
- package/src/lib/components/new-lead-drawer.js +88 -121
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/picklist-select.d.ts +36 -0
- package/src/lib/components/picklist-select.js +114 -0
- package/src/lib/components/picklist-select.js.map +1 -0
- package/src/lib/components/picklist-values-card.d.ts +34 -0
- package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
- package/src/lib/components/picklist-values-card.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
- package/src/lib/components/pipeline-stages-dialog.js +26 -4
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/pipelines-dialog.js +15 -6
- package/src/lib/components/pipelines-dialog.js.map +1 -1
- package/src/lib/components/recent-activity-feed.js +20 -5
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/recipes-card.js +4 -2
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-tasks-card.js +15 -8
- package/src/lib/components/record-tasks-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.d.ts +6 -0
- package/src/lib/components/reports/forecast-card.js +101 -3
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
- package/src/lib/components/reports/lead-funnel-card.js +9 -5
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.d.ts +23 -0
- package/src/lib/components/reports/lead-source-card.js +318 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -0
- package/src/lib/components/reports/pipeline-card.js +2 -1
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-export.js +1 -1
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports-section.js +8 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
- package/src/lib/components/sharing-rule-drawer.js +108 -19
- package/src/lib/components/sharing-rule-drawer.js.map +1 -1
- package/src/lib/components/sharing-rules-card.js +11 -3
- package/src/lib/components/sharing-rules-card.js.map +1 -1
- package/src/lib/components/task-cells.d.ts +9 -1
- package/src/lib/components/task-cells.js +9 -4
- package/src/lib/components/task-cells.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +111 -43
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
- package/src/lib/components/tasks-bulk-bar.js +7 -20
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
- package/src/lib/components/tasks-calendar.d.ts +3 -0
- package/src/lib/components/tasks-calendar.js +8 -1
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.d.ts +3 -3
- package/src/lib/components/tasks-section.js +58 -43
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +2 -9
- package/src/lib/constants/api-routes.js +1 -7
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +11 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +14 -0
- package/src/lib/constants/company-filters.js +51 -1
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +2 -0
- package/src/lib/constants/contact-filters.js +18 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +22 -1
- package/src/lib/constants/deal-filters.js +67 -4
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +3 -0
- package/src/lib/declarations.console-server.js +35 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +35 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +11 -0
- package/src/lib/declarations.server.js +97 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-company-picklists.d.ts +15 -0
- package/src/lib/hooks/use-company-picklists.js +55 -0
- package/src/lib/hooks/use-company-picklists.js.map +1 -0
- package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
- package/src/lib/hooks/use-crm-picklist.js +53 -0
- package/src/lib/hooks/use-crm-picklist.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
- package/src/lib/hooks/use-crm-record-names.js +45 -19
- package/src/lib/hooks/use-crm-record-names.js.map +1 -1
- package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
- package/src/lib/hooks/use-crm-task-picklists.js +45 -0
- package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +14 -0
- package/src/lib/hooks/use-deals.js +29 -0
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
- package/src/lib/hooks/use-lead-picklists.js +46 -0
- package/src/lib/hooks/use-lead-picklists.js.map +1 -0
- package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
- package/src/lib/hooks/use-lead-source-picklist.js +12 -22
- package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
- package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
- package/src/lib/hooks/use-lead-status-picklist.js +40 -0
- package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
- package/src/lib/model/companies.d.ts +32 -3
- package/src/lib/model/companies.js +133 -16
- package/src/lib/model/companies.js.map +1 -1
- package/src/lib/model/contact-merge.d.ts +27 -3
- package/src/lib/model/contact-merge.js +81 -7
- package/src/lib/model/contact-merge.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +17 -0
- package/src/lib/model/contact-record.js +17 -2
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +31 -2
- package/src/lib/model/contact-update.js +14 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +5 -0
- package/src/lib/model/crm-list-query.js +8 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/crm-next-activity.d.ts +107 -0
- package/src/lib/model/crm-next-activity.js +140 -0
- package/src/lib/model/crm-next-activity.js.map +1 -0
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +259 -0
- package/src/lib/model/crm-recipes.js.map +1 -0
- package/src/lib/model/crm-record-counts.d.ts +36 -0
- package/src/lib/model/crm-record-counts.js +54 -0
- package/src/lib/model/crm-record-counts.js.map +1 -0
- package/src/lib/model/crm-record-lists.d.ts +23 -0
- package/src/lib/model/crm-record-lists.js +161 -0
- package/src/lib/model/crm-record-lists.js.map +1 -0
- package/src/lib/model/crm-reports.d.ts +73 -3
- package/src/lib/model/crm-reports.js +120 -1
- package/src/lib/model/crm-reports.js.map +1 -1
- package/src/lib/model/crm-sharing.d.ts +21 -3
- package/src/lib/model/crm-sharing.js +27 -6
- package/src/lib/model/crm-sharing.js.map +1 -1
- package/src/lib/model/crm-task-reminders.d.ts +152 -0
- package/src/lib/model/crm-task-reminders.js +161 -0
- package/src/lib/model/crm-task-reminders.js.map +1 -0
- package/src/lib/model/deal-board-model.d.ts +8 -2
- package/src/lib/model/deal-board-model.js +15 -2
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +45 -0
- package/src/lib/model/deal-contact-roles.js +73 -0
- package/src/lib/model/deal-contact-roles.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +56 -0
- package/src/lib/model/deal-form-model.js +79 -7
- package/src/lib/model/deal-form-model.js.map +1 -1
- package/src/lib/model/download-text-file.d.ts +23 -0
- package/src/lib/model/download-text-file.js +34 -0
- package/src/lib/model/download-text-file.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +36 -2
- package/src/lib/model/lead-filters.js +123 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +38 -0
- package/src/lib/model/lead-source-direction.js +52 -0
- package/src/lib/model/lead-source-direction.js.map +1 -0
- package/src/lib/model/next-activity.d.ts +1 -1
- package/src/lib/model/next-activity.js.map +1 -1
- package/src/lib/model/picklist-values.d.ts +119 -0
- package/src/lib/model/picklist-values.js +256 -0
- package/src/lib/model/picklist-values.js.map +1 -0
- package/src/lib/model/record-facts-disclosed.d.ts +81 -0
- package/src/lib/model/record-facts-disclosed.js +281 -0
- package/src/lib/model/record-facts-disclosed.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +215 -34
- package/src/lib/model/record-facts.js +307 -128
- package/src/lib/model/record-facts.js.map +1 -1
- package/src/lib/model/task-picklists.d.ts +52 -0
- package/src/lib/model/task-picklists.js +83 -0
- package/src/lib/model/task-picklists.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +13 -0
- package/src/lib/model/task-routes.js +23 -4
- package/src/lib/model/task-routes.js.map +1 -1
- package/src/lib/plan-entitlements.js +6 -4
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts.js +220 -14
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +14 -1
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-companies.js +123 -14
- package/src/lib/server/api-v1/crm-companies.js.map +1 -1
- package/src/lib/server/api-v1/crm-deals.js +291 -26
- package/src/lib/server/api-v1/crm-deals.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +144 -38
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-tasks.js +78 -34
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +0 -11
- package/src/lib/server/api-v1/openapi.js +171 -16
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/assign-contact-owner.d.ts +188 -0
- package/src/lib/server/assign-contact-owner.js +446 -0
- package/src/lib/server/assign-contact-owner.js.map +1 -0
- package/src/lib/server/associate-company-by-domain.d.ts +70 -0
- package/src/lib/server/associate-company-by-domain.js +153 -0
- package/src/lib/server/associate-company-by-domain.js.map +1 -0
- package/src/lib/server/automation-steps.d.ts +140 -0
- package/src/lib/server/automation-steps.js +669 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.d.ts +17 -0
- package/src/lib/server/capture-contact.js +95 -26
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/capture-host-contact.d.ts +104 -0
- package/src/lib/server/capture-host-contact.js +151 -0
- package/src/lib/server/capture-host-contact.js.map +1 -0
- package/src/lib/server/company-children.d.ts +25 -0
- package/src/lib/server/company-children.js +42 -0
- package/src/lib/server/company-children.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +18 -0
- package/src/lib/server/company-delete.js +44 -21
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/contact-email-history.js +21 -19
- package/src/lib/server/contact-email-history.js.map +1 -1
- package/src/lib/server/contact-remove.js +27 -2
- package/src/lib/server/contact-remove.js.map +1 -1
- package/src/lib/server/contact-reports-to.d.ts +61 -0
- package/src/lib/server/contact-reports-to.js +114 -0
- package/src/lib/server/contact-reports-to.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +42 -1
- package/src/lib/server/contact-update.js +204 -23
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +46 -1
- package/src/lib/server/convert-host-lead.js +173 -15
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +12 -5
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-email-activity.d.ts +118 -0
- package/src/lib/server/crm-email-activity.js +148 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.js +1 -1
- package/src/lib/server/crm-inbound-email.js.map +1 -1
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- package/src/lib/server/crm-record-indexes.d.ts +45 -0
- package/src/lib/server/crm-record-indexes.js +78 -0
- package/src/lib/server/crm-record-indexes.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +9 -1
- package/src/lib/server/crm-sharing.js +23 -3
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/deal-contact-roles.d.ts +27 -0
- package/src/lib/server/deal-contact-roles.js +89 -0
- package/src/lib/server/deal-contact-roles.js.map +1 -0
- package/src/lib/server/email-send.js +2 -1
- package/src/lib/server/email-send.js.map +1 -1
- package/src/lib/server/lead-convert.d.ts +5 -0
- package/src/lib/server/lead-convert.js +19 -3
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +17 -2
- package/src/lib/server/lead-create.js +46 -14
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +6 -0
- package/src/lib/server/lead-nurturing.js +57 -20
- package/src/lib/server/lead-nurturing.js.map +1 -1
- package/src/lib/server/lead-picklists.d.ts +35 -0
- package/src/lib/server/lead-picklists.js +52 -0
- package/src/lib/server/lead-picklists.js.map +1 -0
- package/src/lib/server/lead-source-picklist.d.ts +1 -13
- package/src/lib/server/lead-source-picklist.js +6 -33
- package/src/lib/server/lead-source-picklist.js.map +1 -1
- package/src/lib/server/merge-contacts.js +23 -5
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/next-activity-routes.js +2 -1
- package/src/lib/server/next-activity-routes.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +53 -0
- package/src/lib/server/person-eraser.js +149 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/person-records.d.ts +69 -0
- package/src/lib/server/person-records.js +317 -0
- package/src/lib/server/person-records.js.map +1 -0
- package/src/lib/server/person-refund.d.ts +97 -0
- package/src/lib/server/person-refund.js +221 -0
- package/src/lib/server/person-refund.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +21 -0
- package/src/lib/server/picklist-values.js +308 -0
- package/src/lib/server/picklist-values.js.map +1 -0
- package/src/lib/server/read-picklist.d.ts +44 -0
- package/src/lib/server/read-picklist.js +65 -0
- package/src/lib/server/read-picklist.js.map +1 -0
- package/src/lib/server/recipe-routes.js +22 -8
- package/src/lib/server/recipe-routes.js.map +1 -1
- package/src/lib/server/record-email-state.js +7 -1
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.d.ts +3 -0
- package/src/lib/server/record-facts.js +233 -31
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-origin.d.ts +27 -0
- package/src/lib/server/record-origin.js +104 -0
- package/src/lib/server/record-origin.js.map +1 -0
- package/src/lib/server/record-timeline.js +40 -3
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/suite-gate.js +4 -3
- package/src/lib/server/suite-gate.js.map +1 -1
- package/src/lib/server/task-reminders-route.js +2 -1
- package/src/lib/server/task-reminders-route.js.map +1 -1
- package/src/lib/server/task-routes.js +33 -17
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -0,0 +1,317 @@
|
|
|
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 { containerMembershipField, contactContainerFieldPath, normalizeContainerIds } from "@aglyn/aglyn/app-utils/container-membership";
|
|
17
|
+
import { normalizeContactEmail } from "@aglyn/aglyn/app-utils/contacts";
|
|
18
|
+
import { CRM_COLLECTIONS, crmLeadStatus, crmViewIsListed, isCrmLeadOpen, normalizeCrmViewFilters } from "@aglyn/aglyn/app-utils/crm";
|
|
19
|
+
import { dynamicListDimensionsForCrmView } from "@aglyn/aglyn/app-utils/dynamic-list-rule";
|
|
20
|
+
import { personKey } from "@aglyn/aglyn/app-utils/person-key";
|
|
21
|
+
import { scopeTokensForHost, seenOnlyThroughGrant, visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
22
|
+
import { consentGroupForSite, firebaseAdmin, resolveOrgIdForHost } from "@aglyn/tenant-data-admin";
|
|
23
|
+
// The leaves, not the barrel: this plugin's specs substitute the barrel
|
|
24
|
+
// wholesale, and the lookup and the restamp must reach the real logic.
|
|
25
|
+
import { findContactByEmail } from "@aglyn/tenant-data-admin/server/contact-email-index";
|
|
26
|
+
import { restampCrmListFieldsAt } from "@aglyn/tenant-data-admin/server/crm-records";
|
|
27
|
+
import { collectDynamicListCandidates } from "@aglyn/tenant-data-admin/server/dynamic-list-materialize";
|
|
28
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
29
|
+
import { recordPersonRefund } from "./person-refund.js";
|
|
30
|
+
/** The person kinds, as `plugin-record-routes` addresses them. */ export const CRM_PERSON_KINDS = {
|
|
31
|
+
contact: 'contact',
|
|
32
|
+
lead: 'lead'
|
|
33
|
+
};
|
|
34
|
+
/** The collection each person kind is stored in, under the organization. */ const STORED_IN = {
|
|
35
|
+
[CRM_PERSON_KINDS.contact]: 'contacts',
|
|
36
|
+
[CRM_PERSON_KINDS.lead]: 'leads'
|
|
37
|
+
};
|
|
38
|
+
/** `getAll` takes this many references per call. */ const GET_ALL_CHUNK = 300;
|
|
39
|
+
/**
|
|
40
|
+
* The most leads a saved Leads view is taken from: the site's most recently
|
|
41
|
+
* seen, the Leads list's own window (AGL-3234).
|
|
42
|
+
*/ export const CRM_LEADS_VIEW_WINDOW = 200;
|
|
43
|
+
export function defaultCrmPersonRecordsDeps() {
|
|
44
|
+
return {
|
|
45
|
+
firestore: ()=>firebaseAdmin.app().firestore(),
|
|
46
|
+
orgIdForHost: async (hostId)=>{
|
|
47
|
+
var _ref;
|
|
48
|
+
return (_ref = await resolveOrgIdForHost(hostId)) != null ? _ref : null;
|
|
49
|
+
},
|
|
50
|
+
groupIdForHost: async (hostId)=>(await consentGroupForSite(hostId)).groupId,
|
|
51
|
+
contactViewEmails: async ({ hostId, viewId })=>{
|
|
52
|
+
const scan = await collectDynamicListCandidates({
|
|
53
|
+
hostId,
|
|
54
|
+
rule: {
|
|
55
|
+
sources: [
|
|
56
|
+
'contacts'
|
|
57
|
+
],
|
|
58
|
+
viewId
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
emails: scan.candidates.map((candidate)=>candidate.email),
|
|
63
|
+
complete: scan.complete
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function personOf(kind, snapshot) {
|
|
69
|
+
var _snapshot_data;
|
|
70
|
+
const data = (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {};
|
|
71
|
+
return {
|
|
72
|
+
kind,
|
|
73
|
+
id: snapshot.id,
|
|
74
|
+
email: normalizeContactEmail(data['email']),
|
|
75
|
+
data
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The organization a request names, or the one its site belongs to. A site
|
|
80
|
+
* with no organization THROWS, as the org-data lookup every caller used to
|
|
81
|
+
* make threw: each caller has already decided which way a failed read falls.
|
|
82
|
+
*/ async function orgOf(deps, request) {
|
|
83
|
+
var _request_orgId, _request_hostId;
|
|
84
|
+
const named = String((_request_orgId = request.orgId) != null ? _request_orgId : '').trim();
|
|
85
|
+
if (named) return named;
|
|
86
|
+
const hostId = String((_request_hostId = request.hostId) != null ? _request_hostId : '').trim();
|
|
87
|
+
const resolved = hostId ? await deps.orgIdForHost(hostId) : null;
|
|
88
|
+
if (!resolved) throw new Error(`[crm] no organization to find a person in for host ${hostId || '(none)'}`);
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
export function createCrmPersonRecords(deps) {
|
|
92
|
+
return {
|
|
93
|
+
/**
|
|
94
|
+
* The contact first, through the org's address index (AGL-2633) so an
|
|
95
|
+
* address a merge folded into another record still names the person who
|
|
96
|
+
* now holds it; then, for a caller that asked for any kind, the lead the
|
|
97
|
+
* address is keyed as. Narrowed to the site when asked, on the document
|
|
98
|
+
* the address names: a record the site cannot see is `null`.
|
|
99
|
+
*/ async find (request) {
|
|
100
|
+
var _request_hostId;
|
|
101
|
+
const email = normalizeContactEmail(request.email);
|
|
102
|
+
if (!email) return null;
|
|
103
|
+
const orgId = await orgOf(deps, request);
|
|
104
|
+
const org = deps.firestore().collection('orgs').doc(orgId);
|
|
105
|
+
const hostId = String((_request_hostId = request.hostId) != null ? _request_hostId : '').trim();
|
|
106
|
+
const narrowTo = request.onlyVisibleToSite && hostId ? hostId : null;
|
|
107
|
+
const contact = await findContactByEmail(org.collection(STORED_IN[CRM_PERSON_KINDS.contact]), email, narrowTo ? {
|
|
108
|
+
hostId: narrowTo
|
|
109
|
+
} : {});
|
|
110
|
+
if (contact) return personOf(CRM_PERSON_KINDS.contact, contact);
|
|
111
|
+
if (!request.anyKind) return null;
|
|
112
|
+
const key = personKey(email);
|
|
113
|
+
if (!key) return null;
|
|
114
|
+
const lead = await org.collection(STORED_IN[CRM_PERSON_KINDS.lead]).doc(key).get();
|
|
115
|
+
if (!lead.exists) return null;
|
|
116
|
+
if (narrowTo && !visibleToHost(lead.get('visibleTo'), narrowTo)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return personOf(CRM_PERSON_KINDS.lead, lead);
|
|
120
|
+
},
|
|
121
|
+
/** Every record named, read at once; a kind the CRM does not keep is `null`. */ async read (request) {
|
|
122
|
+
const firestore = deps.firestore();
|
|
123
|
+
const org = firestore.collection('orgs').doc(request.orgId);
|
|
124
|
+
const refs = request.records.map((record)=>{
|
|
125
|
+
var _record_id;
|
|
126
|
+
const collection = STORED_IN[record.kind];
|
|
127
|
+
const id = String((_record_id = record.id) != null ? _record_id : '').trim();
|
|
128
|
+
return collection && id ? org.collection(collection).doc(id) : null;
|
|
129
|
+
});
|
|
130
|
+
const wanted = refs.filter((ref)=>ref !== null);
|
|
131
|
+
const snapshots = [];
|
|
132
|
+
for(let start = 0; start < wanted.length; start += GET_ALL_CHUNK){
|
|
133
|
+
snapshots.push(...await firestore.getAll(...wanted.slice(start, start + GET_ALL_CHUNK)));
|
|
134
|
+
}
|
|
135
|
+
let at = 0;
|
|
136
|
+
return request.records.map((record, index)=>{
|
|
137
|
+
if (!refs[index]) return null;
|
|
138
|
+
const snapshot = snapshots[at++];
|
|
139
|
+
return (snapshot == null ? void 0 : snapshot.exists) ? personOf(record.kind, snapshot) : null;
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
/**
|
|
143
|
+
* Files the person under the containers as the site holds them: on a
|
|
144
|
+
* contact, inside the site's consent-group facet — which campaigns a
|
|
145
|
+
* merchant filed somebody under is that merchant's record, and written at
|
|
146
|
+
* the top it would be readable by every site of an agency's account; on a
|
|
147
|
+
* lead, in the lead's own membership field, whose list fields are then
|
|
148
|
+
* restamped so the Leads list's filter finds it (AGL-3321).
|
|
149
|
+
*
|
|
150
|
+
* `update`, never a merge-set: a dotted path in `set` is a literal field
|
|
151
|
+
* name, and the record must exist. A record the site cannot see is not
|
|
152
|
+
* filed.
|
|
153
|
+
*/ async fileUnder (request) {
|
|
154
|
+
var _request_record_id;
|
|
155
|
+
const ids = normalizeContainerIds(request.ids);
|
|
156
|
+
const collection = STORED_IN[request.record.kind];
|
|
157
|
+
const id = String((_request_record_id = request.record.id) != null ? _request_record_id : '').trim();
|
|
158
|
+
if (!ids.length || !collection || !id) return {
|
|
159
|
+
filed: false
|
|
160
|
+
};
|
|
161
|
+
const orgId = await orgOf(deps, request);
|
|
162
|
+
const ref = deps.firestore().collection('orgs').doc(orgId).collection(collection).doc(id);
|
|
163
|
+
const snapshot = await ref.get();
|
|
164
|
+
if (!snapshot.exists) return {
|
|
165
|
+
filed: false
|
|
166
|
+
};
|
|
167
|
+
if (!visibleToHost(snapshot.get('visibleTo'), request.hostId)) {
|
|
168
|
+
return {
|
|
169
|
+
filed: false
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (request.record.kind === CRM_PERSON_KINDS.lead) {
|
|
173
|
+
await ref.update({
|
|
174
|
+
[containerMembershipField(request.containerKind)]: FieldValue.arrayUnion(...ids),
|
|
175
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
176
|
+
});
|
|
177
|
+
await restampCrmListFieldsAt(ref, 'leads');
|
|
178
|
+
return {
|
|
179
|
+
filed: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const groupId = await deps.groupIdForHost(request.hostId);
|
|
183
|
+
await ref.update({
|
|
184
|
+
[contactContainerFieldPath(groupId, request.containerKind)]: FieldValue.arrayUnion(...ids),
|
|
185
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
filed: true
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
recordRefund: (request)=>recordPersonRefund(request),
|
|
192
|
+
/**
|
|
193
|
+
* The people a saved view selects for a site (AGL-3234). A colleague's
|
|
194
|
+
* private view is theirs: not listed for this member, so it does not
|
|
195
|
+
* exist for them here either.
|
|
196
|
+
*
|
|
197
|
+
* A Leads view takes the site's most recently seen leads —
|
|
198
|
+
* {@link CRM_LEADS_VIEW_WINDOW}, the Leads list's own window — narrowed by
|
|
199
|
+
* the view's status clause the way the list narrows it: open leads when
|
|
200
|
+
* the view names no status, one status when it does, everything for
|
|
201
|
+
* `all`. Held, not merely seen: a lead another site only SHARED with this
|
|
202
|
+
* one (AGL-3336) is not this site's to take.
|
|
203
|
+
*
|
|
204
|
+
* A Contacts view is read the way the dynamic-list sweep reads one, and
|
|
205
|
+
* each address it selects is resolved to the contact the site may see. A
|
|
206
|
+
* view filtering on something that reading cannot apply is refused whole:
|
|
207
|
+
* dropping the filter would select more people than the view shows.
|
|
208
|
+
*/ async peopleInView (request) {
|
|
209
|
+
var _view_data, _data_ownerUid;
|
|
210
|
+
const firestore = deps.firestore();
|
|
211
|
+
const org = firestore.collection('orgs').doc(request.orgId);
|
|
212
|
+
const view = await org.collection(CRM_COLLECTIONS.views).doc(request.viewId).get();
|
|
213
|
+
const data = view.exists ? (_view_data = view.data()) != null ? _view_data : {} : null;
|
|
214
|
+
if (!data || !crmViewIsListed({
|
|
215
|
+
shared: data['shared'] === true,
|
|
216
|
+
ownerUid: String((_data_ownerUid = data['ownerUid']) != null ? _data_ownerUid : '')
|
|
217
|
+
}, request.viewerUid)) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
reason: 'not-found'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const filters = normalizeCrmViewFilters(data['filters']);
|
|
224
|
+
if (data['section'] === 'leads') {
|
|
225
|
+
var _ref;
|
|
226
|
+
const statusClause = filters.find((clause)=>clause.field === 'status' && clause.op === 'equals');
|
|
227
|
+
const wanted = String((_ref = statusClause == null ? void 0 : statusClause.value) != null ? _ref : 'open');
|
|
228
|
+
// Narrowed to what the site may see before it is ordered (AGL-3275):
|
|
229
|
+
// the collection is org-wide, and an agency's sequence must not be
|
|
230
|
+
// offered another client's people.
|
|
231
|
+
const window = await org.collection(STORED_IN[CRM_PERSON_KINDS.lead]).where('visibleTo', 'array-contains-any', scopeTokensForHost(request.hostId)).orderBy('lastSeenAtMs', 'desc').limit(CRM_LEADS_VIEW_WINDOW + 1).get();
|
|
232
|
+
const matching = window.docs.slice(0, CRM_LEADS_VIEW_WINDOW).filter((doc)=>{
|
|
233
|
+
var _doc_data;
|
|
234
|
+
const lead = (_doc_data = doc.data()) != null ? _doc_data : {};
|
|
235
|
+
if (seenOnlyThroughGrant(lead, request.hostId)) return false;
|
|
236
|
+
if (wanted === 'all') return true;
|
|
237
|
+
if (wanted === 'open') return isCrmLeadOpen(lead) && !lead['convertedContactId'];
|
|
238
|
+
return crmLeadStatus(lead) === wanted;
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
people: matching.slice(0, request.limit).map((doc)=>({
|
|
243
|
+
kind: CRM_PERSON_KINDS.lead,
|
|
244
|
+
id: doc.id
|
|
245
|
+
})),
|
|
246
|
+
total: matching.length,
|
|
247
|
+
truncated: matching.length > request.limit || window.docs.length > CRM_LEADS_VIEW_WINDOW
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (data['section'] !== 'contacts') return {
|
|
251
|
+
ok: false,
|
|
252
|
+
reason: 'not-people'
|
|
253
|
+
};
|
|
254
|
+
const { unsupported } = dynamicListDimensionsForCrmView(filters);
|
|
255
|
+
if (unsupported.length) {
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
reason: 'unsupported',
|
|
259
|
+
unsupported: unsupported.map((clause)=>clause.label || clause.field)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const { emails, complete } = await deps.contactViewEmails({
|
|
263
|
+
hostId: request.hostId,
|
|
264
|
+
viewId: request.viewId
|
|
265
|
+
});
|
|
266
|
+
const ordered = [
|
|
267
|
+
...new Set(emails)
|
|
268
|
+
].sort();
|
|
269
|
+
const contacts = org.collection(STORED_IN[CRM_PERSON_KINDS.contact]);
|
|
270
|
+
const found = await Promise.all(ordered.slice(0, request.limit).map((email)=>findContactByEmail(contacts, email, {
|
|
271
|
+
hostId: request.hostId
|
|
272
|
+
})));
|
|
273
|
+
const ids = [
|
|
274
|
+
...new Set(found.filter((snapshot)=>snapshot).map((snapshot)=>String(snapshot == null ? void 0 : snapshot.id)))
|
|
275
|
+
];
|
|
276
|
+
return {
|
|
277
|
+
ok: true,
|
|
278
|
+
people: ids.map((id)=>({
|
|
279
|
+
kind: CRM_PERSON_KINDS.contact,
|
|
280
|
+
id
|
|
281
|
+
})),
|
|
282
|
+
total: ordered.length,
|
|
283
|
+
truncated: ordered.length > request.limit || !complete
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
/**
|
|
287
|
+
* Whether each person has ever written in: an inbound email on their
|
|
288
|
+
* timeline, filed on the contact, or on the lead while they were one
|
|
289
|
+
* (AGL-3234). One keyed query each; a failed one is unknown.
|
|
290
|
+
*/ async wroteIn (request) {
|
|
291
|
+
const activities = deps.firestore().collection('orgs').doc(request.orgId).collection(CRM_COLLECTIONS.activities);
|
|
292
|
+
return Promise.all(request.records.map(async (record)=>{
|
|
293
|
+
var _record_id;
|
|
294
|
+
const field = record.kind === CRM_PERSON_KINDS.contact ? 'contactId' : record.kind === CRM_PERSON_KINDS.lead ? 'leadId' : null;
|
|
295
|
+
const id = String((_record_id = record.id) != null ? _record_id : '').trim();
|
|
296
|
+
if (!field || !id) return null;
|
|
297
|
+
try {
|
|
298
|
+
const snapshot = await activities.where(field, '==', id).where('direction', '==', 'inbound').limit(1).get();
|
|
299
|
+
return !snapshot.empty;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error('[crm] inbound email lookup failed; reading as unknown', error);
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/** The service as the CRM registers it, its storage the admin app's. */ export const crmPersonRecords = {
|
|
309
|
+
find: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).find(request),
|
|
310
|
+
read: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).read(request),
|
|
311
|
+
fileUnder: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).fileUnder(request),
|
|
312
|
+
recordRefund: (request)=>recordPersonRefund(request),
|
|
313
|
+
peopleInView: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).peopleInView(request),
|
|
314
|
+
wroteIn: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).wroteIn(request)
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
//# sourceMappingURL=person-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/person-records.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 containerMembershipField,\n contactContainerFieldPath,\n normalizeContainerIds,\n} from '@aglyn/aglyn/app-utils/container-membership'\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport {\n CRM_COLLECTIONS,\n crmLeadStatus,\n crmViewIsListed,\n isCrmLeadOpen,\n normalizeCrmViewFilters,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { dynamicListDimensionsForCrmView } from '@aglyn/aglyn/app-utils/dynamic-list-rule'\nimport { personKey } from '@aglyn/aglyn/app-utils/person-key'\nimport {\n scopeTokensForHost,\n seenOnlyThroughGrant,\n visibleToHost,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type {\n PluginPersonFileRequest,\n PluginPersonFindRequest,\n PluginPersonReadRequest,\n PluginPersonRecord,\n PluginPersonRecordRef,\n PluginPersonRecords,\n PluginPersonViewPeople,\n PluginPersonViewRequest,\n PluginPersonWroteInRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-records'\nimport {\n consentGroupForSite,\n firebaseAdmin,\n resolveOrgIdForHost,\n} from '@aglyn/tenant-data-admin'\n// The leaves, not the barrel: this plugin's specs substitute the barrel\n// wholesale, and the lookup and the restamp must reach the real logic.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { restampCrmListFieldsAt } from '@aglyn/tenant-data-admin/server/crm-records'\nimport { collectDynamicListCandidates } from '@aglyn/tenant-data-admin/server/dynamic-list-materialize'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { recordPersonRefund } from './person-refund'\n\n/**\n * THE CRM AS THE PLUGIN THAT KEEPS PEOPLE, asked by the plugins that do not\n * (AGL-3080) — the CRM's side of `plugin-person-records`.\n *\n * Two kinds of record are a person here, in the words `plugin-record-routes`\n * addresses them by: a `contact`, the person once they are known, filed at\n * `orgs/{orgId}/contacts/{id}`; and a `lead`, the person before anybody has\n * qualified them, at `orgs/{orgId}/leads/{personKey}` (AGL-3275). One person\n * is one record (AGL-3232), so a lookup by address answers the contact when\n * there is one and the lead only for a caller that asked for any kind.\n *\n * The record's `data` is the stored document. Beside the fields the platform\n * defines (`marketingConsent…`, `emailState`, `visibleTo`), a contact keeps\n * what each consent group knows of the person in `facets.{groupId}` — the\n * sources, stage, owner, company, address and tags — and a lead keeps the\n * same facts at its top level.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The person kinds, as `plugin-record-routes` addresses them. */\nexport const CRM_PERSON_KINDS = { contact: 'contact', lead: 'lead' } as const\n\n/** The collection each person kind is stored in, under the organization. */\nconst STORED_IN: Readonly<Record<string, string>> = {\n [CRM_PERSON_KINDS.contact]: 'contacts',\n [CRM_PERSON_KINDS.lead]: 'leads',\n}\n\n/** `getAll` takes this many references per call. */\nconst GET_ALL_CHUNK = 300\n\n/**\n * The most leads a saved Leads view is taken from: the site's most recently\n * seen, the Leads list's own window (AGL-3234).\n */\nexport const CRM_LEADS_VIEW_WINDOW = 200\n\nexport interface CrmPersonRecordsDeps {\n firestore(): Firestore\n /** The organization a site belongs to, or `null` for one with none. */\n orgIdForHost(hostId: string): Promise<string | null>\n /** The consent group a site files its facet under. */\n groupIdForHost(hostId: string): Promise<string>\n /**\n * The addresses a saved Contacts view selects among one site's contacts,\n * read the way the dynamic-list sweep reads one, and whether the read\n * reached the whole view before its budget.\n */\n contactViewEmails(input: { hostId: string; viewId: string }): Promise<{ emails: string[]; complete: boolean }>\n}\n\nexport function defaultCrmPersonRecordsDeps(): CrmPersonRecordsDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n orgIdForHost: async (hostId) => (await resolveOrgIdForHost(hostId)) ?? null,\n groupIdForHost: async (hostId) => (await consentGroupForSite(hostId)).groupId,\n contactViewEmails: async ({ hostId, viewId }) => {\n const scan = await collectDynamicListCandidates({\n hostId,\n rule: { sources: ['contacts'], viewId },\n })\n return { emails: scan.candidates.map((candidate) => candidate.email), complete: scan.complete }\n },\n }\n}\n\nfunction personOf(kind: string, snapshot: FirebaseFirestore.DocumentSnapshot): PluginPersonRecord {\n const data = (snapshot.data() ?? {}) as Record<string, unknown>\n return { kind, id: snapshot.id, email: normalizeContactEmail(data['email']), data }\n}\n\n/**\n * The organization a request names, or the one its site belongs to. A site\n * with no organization THROWS, as the org-data lookup every caller used to\n * make threw: each caller has already decided which way a failed read falls.\n */\nasync function orgOf(\n deps: CrmPersonRecordsDeps,\n request: { orgId?: string | null; hostId?: string | null },\n): Promise<string> {\n const named = String(request.orgId ?? '').trim()\n if (named) return named\n const hostId = String(request.hostId ?? '').trim()\n const resolved = hostId ? await deps.orgIdForHost(hostId) : null\n if (!resolved) throw new Error(`[crm] no organization to find a person in for host ${hostId || '(none)'}`)\n return resolved\n}\n\nexport function createCrmPersonRecords(deps: CrmPersonRecordsDeps): PluginPersonRecords {\n return {\n /**\n * The contact first, through the org's address index (AGL-2633) so an\n * address a merge folded into another record still names the person who\n * now holds it; then, for a caller that asked for any kind, the lead the\n * address is keyed as. Narrowed to the site when asked, on the document\n * the address names: a record the site cannot see is `null`.\n */\n async find(request: PluginPersonFindRequest) {\n const email = normalizeContactEmail(request.email)\n if (!email) return null\n const orgId = await orgOf(deps, request)\n const org = deps.firestore().collection('orgs').doc(orgId)\n const hostId = String(request.hostId ?? '').trim()\n const narrowTo = request.onlyVisibleToSite && hostId ? hostId : null\n const contact = await findContactByEmail(\n org.collection(STORED_IN[CRM_PERSON_KINDS.contact]),\n email,\n narrowTo ? { hostId: narrowTo } : {},\n )\n if (contact) return personOf(CRM_PERSON_KINDS.contact, contact)\n if (!request.anyKind) return null\n const key = personKey(email)\n if (!key) return null\n const lead = await org.collection(STORED_IN[CRM_PERSON_KINDS.lead]).doc(key).get()\n if (!lead.exists) return null\n if (narrowTo && !visibleToHost(lead.get('visibleTo') as string[] | undefined, narrowTo)) {\n return null\n }\n return personOf(CRM_PERSON_KINDS.lead, lead)\n },\n\n /** Every record named, read at once; a kind the CRM does not keep is `null`. */\n async read(request: PluginPersonReadRequest) {\n const firestore = deps.firestore()\n const org = firestore.collection('orgs').doc(request.orgId)\n const refs = request.records.map((record) => {\n const collection = STORED_IN[record.kind]\n const id = String(record.id ?? '').trim()\n return collection && id ? org.collection(collection).doc(id) : null\n })\n const wanted = refs.filter((ref): ref is FirebaseFirestore.DocumentReference => ref !== null)\n const snapshots: FirebaseFirestore.DocumentSnapshot[] = []\n for (let start = 0; start < wanted.length; start += GET_ALL_CHUNK) {\n snapshots.push(...(await firestore.getAll(...wanted.slice(start, start + GET_ALL_CHUNK))))\n }\n let at = 0\n return request.records.map((record, index) => {\n if (!refs[index]) return null\n const snapshot = snapshots[at++]\n return snapshot?.exists ? personOf(record.kind, snapshot) : null\n })\n },\n\n /**\n * Files the person under the containers as the site holds them: on a\n * contact, inside the site's consent-group facet — which campaigns a\n * merchant filed somebody under is that merchant's record, and written at\n * the top it would be readable by every site of an agency's account; on a\n * lead, in the lead's own membership field, whose list fields are then\n * restamped so the Leads list's filter finds it (AGL-3321).\n *\n * `update`, never a merge-set: a dotted path in `set` is a literal field\n * name, and the record must exist. A record the site cannot see is not\n * filed.\n */\n async fileUnder(request: PluginPersonFileRequest) {\n const ids = normalizeContainerIds(request.ids)\n const collection = STORED_IN[request.record.kind]\n const id = String(request.record.id ?? '').trim()\n if (!ids.length || !collection || !id) return { filed: false }\n const orgId = await orgOf(deps, request)\n const ref = deps.firestore().collection('orgs').doc(orgId).collection(collection).doc(id)\n const snapshot = await ref.get()\n if (!snapshot.exists) return { filed: false }\n if (!visibleToHost(snapshot.get('visibleTo') as string[] | undefined, request.hostId)) {\n return { filed: false }\n }\n if (request.record.kind === CRM_PERSON_KINDS.lead) {\n await ref.update({\n [containerMembershipField(request.containerKind)]: FieldValue.arrayUnion(...ids),\n updatedAt: FieldValue.serverTimestamp(),\n })\n await restampCrmListFieldsAt(ref, 'leads')\n return { filed: true }\n }\n const groupId = await deps.groupIdForHost(request.hostId)\n await ref.update({\n [contactContainerFieldPath(groupId, request.containerKind)]: FieldValue.arrayUnion(...ids),\n updatedAt: FieldValue.serverTimestamp(),\n })\n return { filed: true }\n },\n\n recordRefund: (request) => recordPersonRefund(request),\n\n /**\n * The people a saved view selects for a site (AGL-3234). A colleague's\n * private view is theirs: not listed for this member, so it does not\n * exist for them here either.\n *\n * A Leads view takes the site's most recently seen leads —\n * {@link CRM_LEADS_VIEW_WINDOW}, the Leads list's own window — narrowed by\n * the view's status clause the way the list narrows it: open leads when\n * the view names no status, one status when it does, everything for\n * `all`. Held, not merely seen: a lead another site only SHARED with this\n * one (AGL-3336) is not this site's to take.\n *\n * A Contacts view is read the way the dynamic-list sweep reads one, and\n * each address it selects is resolved to the contact the site may see. A\n * view filtering on something that reading cannot apply is refused whole:\n * dropping the filter would select more people than the view shows.\n */\n async peopleInView(request: PluginPersonViewRequest): Promise<PluginPersonViewPeople> {\n const firestore = deps.firestore()\n const org = firestore.collection('orgs').doc(request.orgId)\n const view = await org.collection(CRM_COLLECTIONS.views).doc(request.viewId).get()\n const data = view.exists ? ((view.data() ?? {}) as Record<string, unknown>) : null\n if (\n !data ||\n !crmViewIsListed(\n { shared: data['shared'] === true, ownerUid: String(data['ownerUid'] ?? '') },\n request.viewerUid,\n )\n ) {\n return { ok: false, reason: 'not-found' }\n }\n const filters = normalizeCrmViewFilters(data['filters'])\n if (data['section'] === 'leads') {\n const statusClause = filters.find((clause) => clause.field === 'status' && clause.op === 'equals')\n const wanted = String(statusClause?.value ?? 'open')\n // Narrowed to what the site may see before it is ordered (AGL-3275):\n // the collection is org-wide, and an agency's sequence must not be\n // offered another client's people.\n const window = await org\n .collection(STORED_IN[CRM_PERSON_KINDS.lead])\n .where('visibleTo', 'array-contains-any', scopeTokensForHost(request.hostId))\n .orderBy('lastSeenAtMs', 'desc')\n .limit(CRM_LEADS_VIEW_WINDOW + 1)\n .get()\n const matching = window.docs.slice(0, CRM_LEADS_VIEW_WINDOW).filter((doc) => {\n const lead = (doc.data() ?? {}) as Record<string, unknown>\n if (seenOnlyThroughGrant(lead, request.hostId)) return false\n if (wanted === 'all') return true\n if (wanted === 'open') return isCrmLeadOpen(lead as never) && !lead['convertedContactId']\n return crmLeadStatus(lead as never) === wanted\n })\n return {\n ok: true,\n people: matching.slice(0, request.limit).map((doc) => ({ kind: CRM_PERSON_KINDS.lead, id: doc.id })),\n total: matching.length,\n truncated: matching.length > request.limit || window.docs.length > CRM_LEADS_VIEW_WINDOW,\n }\n }\n if (data['section'] !== 'contacts') return { ok: false, reason: 'not-people' }\n const { unsupported } = dynamicListDimensionsForCrmView(filters)\n if (unsupported.length) {\n return {\n ok: false,\n reason: 'unsupported',\n unsupported: unsupported.map((clause) => clause.label || clause.field),\n }\n }\n const { emails, complete } = await deps.contactViewEmails({\n hostId: request.hostId,\n viewId: request.viewId,\n })\n const ordered = [...new Set(emails)].sort()\n const contacts = org.collection(STORED_IN[CRM_PERSON_KINDS.contact])\n const found = await Promise.all(\n ordered\n .slice(0, request.limit)\n .map((email) => findContactByEmail(contacts, email, { hostId: request.hostId })),\n )\n const ids = [...new Set(found.filter((snapshot) => snapshot).map((snapshot) => String(snapshot?.id)))]\n return {\n ok: true,\n people: ids.map((id): PluginPersonRecordRef => ({ kind: CRM_PERSON_KINDS.contact, id })),\n total: ordered.length,\n truncated: ordered.length > request.limit || !complete,\n }\n },\n\n /**\n * Whether each person has ever written in: an inbound email on their\n * timeline, filed on the contact, or on the lead while they were one\n * (AGL-3234). One keyed query each; a failed one is unknown.\n */\n async wroteIn(request: PluginPersonWroteInRequest) {\n const activities = deps\n .firestore()\n .collection('orgs')\n .doc(request.orgId)\n .collection(CRM_COLLECTIONS.activities)\n return Promise.all(\n request.records.map(async (record) => {\n const field =\n record.kind === CRM_PERSON_KINDS.contact\n ? 'contactId'\n : record.kind === CRM_PERSON_KINDS.lead\n ? 'leadId'\n : null\n const id = String(record.id ?? '').trim()\n if (!field || !id) return null\n try {\n const snapshot = await activities\n .where(field, '==', id)\n .where('direction', '==', 'inbound')\n .limit(1)\n .get()\n return !snapshot.empty\n } catch (error) {\n console.error('[crm] inbound email lookup failed; reading as unknown', error)\n return null\n }\n }),\n )\n },\n }\n}\n\n/** The service as the CRM registers it, its storage the admin app's. */\nexport const crmPersonRecords: Required<PluginPersonRecords> = {\n find: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).find(request),\n read: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).read(request),\n fileUnder: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).fileUnder!(request),\n recordRefund: (request) => recordPersonRefund(request),\n peopleInView: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).peopleInView!(request),\n wroteIn: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).wroteIn!(request),\n}\n"],"names":["containerMembershipField","contactContainerFieldPath","normalizeContainerIds","normalizeContactEmail","CRM_COLLECTIONS","crmLeadStatus","crmViewIsListed","isCrmLeadOpen","normalizeCrmViewFilters","dynamicListDimensionsForCrmView","personKey","scopeTokensForHost","seenOnlyThroughGrant","visibleToHost","consentGroupForSite","firebaseAdmin","resolveOrgIdForHost","findContactByEmail","restampCrmListFieldsAt","collectDynamicListCandidates","FieldValue","recordPersonRefund","CRM_PERSON_KINDS","contact","lead","STORED_IN","GET_ALL_CHUNK","CRM_LEADS_VIEW_WINDOW","defaultCrmPersonRecordsDeps","firestore","app","orgIdForHost","hostId","groupIdForHost","groupId","contactViewEmails","viewId","scan","rule","sources","emails","candidates","map","candidate","email","complete","personOf","kind","snapshot","data","id","orgOf","deps","request","named","String","orgId","trim","resolved","Error","createCrmPersonRecords","find","org","collection","doc","narrowTo","onlyVisibleToSite","anyKind","key","get","exists","read","refs","records","record","wanted","filter","ref","snapshots","start","length","push","getAll","slice","at","index","fileUnder","ids","filed","update","containerKind","arrayUnion","updatedAt","serverTimestamp","recordRefund","peopleInView","view","views","shared","ownerUid","viewerUid","ok","reason","filters","statusClause","clause","field","op","value","window","where","orderBy","limit","matching","docs","people","total","truncated","unsupported","label","ordered","Set","sort","contacts","found","Promise","all","wroteIn","activities","empty","error","console","crmPersonRecords"],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { PluginPersonRefundOutcome, PluginPersonRefundRequest } from '@aglyn/aglyn/plugin-manager/plugin-person-records';
|
|
18
|
+
/**
|
|
19
|
+
* Money going BACK to a customer, recorded on their contact (AGL-1754).
|
|
20
|
+
*
|
|
21
|
+
* The CRM's answer to `plugin-person-records`' `recordRefund` (AGL-3080):
|
|
22
|
+
* commerce reports a refund or a lost dispute through the seam, and what it
|
|
23
|
+
* does to the person is decided here, by the plugin that keeps them. It used
|
|
24
|
+
* to be commerce's own module, writing these fields onto the CRM's contacts.
|
|
25
|
+
*
|
|
26
|
+
* `upsertHostContact` accumulates `ltvCents` and nothing anywhere decremented
|
|
27
|
+
* it, so a customer who bought $500 and returned all of it read identically to
|
|
28
|
+
* one who bought $500 and kept it — and the first surface built on the field
|
|
29
|
+
* (RFM segmentation, a lifetime-value column, a best-customers list) would have
|
|
30
|
+
* ranked a serial returner among the merchant's best customers.
|
|
31
|
+
*
|
|
32
|
+
* WHAT THIS WRITES, AND WHY IT IS NOT A DECREMENT
|
|
33
|
+
*
|
|
34
|
+
* `ltvCents` is left exactly as it is and the reversal is recorded BESIDE it:
|
|
35
|
+
*
|
|
36
|
+
* | field | meaning |
|
|
37
|
+
* | -- | -- |
|
|
38
|
+
* | `ltvCents` | gross money handed over, unchanged (AGL-1748) |
|
|
39
|
+
* | `refundedCents` | gross money handed back |
|
|
40
|
+
* | `ordersCount` | orders placed, unchanged |
|
|
41
|
+
* | `refundedOrdersCount` | of those, how many ended fully reversed |
|
|
42
|
+
* | `lastRefundAtMs` | when money last went back |
|
|
43
|
+
*
|
|
44
|
+
* This is the shape AGL-1747 chose two days earlier for the same question on
|
|
45
|
+
* the orders CSV — `amountUsd` stayed gross under its existing name and
|
|
46
|
+
* `refundedUsd`/`netUsd` were appended — and it is the shape AGL-1748 named
|
|
47
|
+
* when it recorded the gross semantics in `upsert-contact.ts`. Netting in
|
|
48
|
+
* place would make a stored number mean one thing for rows written before this
|
|
49
|
+
* commit and another for rows written after, with nothing on the document to
|
|
50
|
+
* tell the two apart. Two fields cost one number's worth of storage and keep
|
|
51
|
+
* both facts, and a reader that wants the net has `ltvCents - refundedCents`.
|
|
52
|
+
*
|
|
53
|
+
* It also answers "can it go negative?" by moving the question off storage.
|
|
54
|
+
* Both figures are monotonic counters of money that actually moved in one
|
|
55
|
+
* direction, so neither can go negative. The DERIVED net can, for exactly one
|
|
56
|
+
* reason: an order placed before AGL-1748 taught POS, drafts, reservations and
|
|
57
|
+
* bookings to pass `purchaseCents` at all, whose sale was never added and whose
|
|
58
|
+
* refund is subtracted here. That is a true statement about the data, and
|
|
59
|
+
* clamping it away at write time would erase the only evidence that a purchase
|
|
60
|
+
* is missing. AGL-1753 is the backfill that reconciles it, by SETTING both
|
|
61
|
+
* fields from the orders — which already carry `refundedCents`, so whatever a
|
|
62
|
+
* reader decides here costs the rebuild nothing. A merchant-facing reader
|
|
63
|
+
* should show gross and refunded, and clamp only the net it ranks on.
|
|
64
|
+
*
|
|
65
|
+
* `ordersCount` is deliberately NOT decremented. A fully refunded order still
|
|
66
|
+
* happened, and a decrement cannot be undone by a reader that would rather
|
|
67
|
+
* count it — while `refundedOrdersCount` beside it lets RFM's frequency term
|
|
68
|
+
* net the two, or not, at the point of display. Same argument as the amount,
|
|
69
|
+
* one field along.
|
|
70
|
+
*
|
|
71
|
+
* WHEN THE CONTACT IS MISSING, THIS REFUSES — and records the refusal.
|
|
72
|
+
*
|
|
73
|
+
* Refusing is right on AGL-1732's test, "does refusing discard money or work
|
|
74
|
+
* that already happened?", because it does not: the refund is durable on the
|
|
75
|
+
* order (`refundedCents`, the timeline entry, Stripe), and the contact is a
|
|
76
|
+
* derived aggregate that AGL-1753's rebuild reads back out of those same
|
|
77
|
+
* orders. Creating one here would be worse three ways — creation is audience
|
|
78
|
+
* BAND gated, so it hard-bands a Free org (raising the AGL-891 dropped-contacts
|
|
79
|
+
* alert for a record the merchant never had) or meters onto a paid invoice; the
|
|
80
|
+
* contact it created would hold a refund and no purchase, which is the negative
|
|
81
|
+
* lifetime value above with nothing to reconcile it against; and it is the
|
|
82
|
+
* phantom-document shape the `updateExisting` sweep spent yesterday closing.
|
|
83
|
+
*
|
|
84
|
+
* So the write is an `update()`, never `set(…, { merge: true })` — the contact
|
|
85
|
+
* may have been deleted between the query and the write, and a merge-set would
|
|
86
|
+
* resurrect it as a document holding nothing but a refund. Every refusal
|
|
87
|
+
* increments `hosts/{hostId}/counters/contactRefundsUnmatched`, the counter
|
|
88
|
+
* shape `contactsDropped` already uses, so a refund that reached no contact is
|
|
89
|
+
* countable rather than silent.
|
|
90
|
+
*
|
|
91
|
+
* CHARGEBACKS (AGL-1554) are a separate decision and are NOT handled here.
|
|
92
|
+
* This takes `kind` so the handler that eventually answers
|
|
93
|
+
* `charge.dispute.closed` can record a lost dispute through the same writer
|
|
94
|
+
* with the same fields: money reversed is money reversed, whichever door it
|
|
95
|
+
* left by, and only the wording of the timeline entry differs.
|
|
96
|
+
*/
|
|
97
|
+
export declare function recordPersonRefund(request: PluginPersonRefundRequest): Promise<PluginPersonRefundOutcome>;
|