@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.223
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +13 -13
- package/src/lib/components/activity-list.js +12 -5
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.d.ts +1 -4
- package/src/lib/components/companies-bulk-bar.js +7 -20
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.d.ts +3 -3
- package/src/lib/components/companies-section.js +149 -46
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +281 -20
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-properties-card.js +99 -8
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +3 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +33 -10
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +0 -69
- package/src/lib/components/contact-properties-card.js +350 -60
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-reports-to-field.d.ts +46 -0
- package/src/lib/components/contact-reports-to-field.js +67 -0
- package/src/lib/components/contact-reports-to-field.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
- package/src/lib/components/contacts-bulk-bar.js +7 -19
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +46 -33
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-call-actions.d.ts +10 -0
- package/src/lib/components/crm-call-actions.js +26 -9
- package/src/lib/components/crm-call-actions.js.map +1 -1
- package/src/lib/components/crm-next-activity-column.js +1 -1
- package/src/lib/components/crm-next-activity-column.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +15 -0
- package/src/lib/components/crm-record-picker.js +16 -10
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
- package/src/lib/components/crm-transfer-buttons.js +167 -0
- package/src/lib/components/crm-transfer-buttons.js.map +1 -0
- package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
- package/src/lib/components/deal-contact-roles-card.js +368 -0
- package/src/lib/components/deal-contact-roles-card.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +3 -2
- package/src/lib/components/deal-detail-page.js +25 -6
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.d.ts +10 -0
- package/src/lib/components/deal-edit-drawer.js +219 -10
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-properties-card.d.ts +7 -0
- package/src/lib/components/deal-properties-card.js +34 -3
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.d.ts +4 -0
- package/src/lib/components/deal-stage-card.js +10 -2
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.d.ts +0 -3
- package/src/lib/components/deals-bulk-bar.js +7 -20
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/deals-section.d.ts +3 -3
- package/src/lib/components/deals-section.js +123 -63
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +31 -17
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-convert-dialog.d.ts +4 -0
- package/src/lib/components/lead-convert-dialog.js +23 -1
- package/src/lib/components/lead-convert-dialog.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +8 -2
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-profile-fields.d.ts +71 -0
- package/src/lib/components/lead-profile-fields.js +386 -0
- package/src/lib/components/lead-profile-fields.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +2 -1
- package/src/lib/components/lead-properties-card.js +85 -176
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-source-select.d.ts +4 -23
- package/src/lib/components/lead-source-select.js +9 -73
- package/src/lib/components/lead-source-select.js.map +1 -1
- package/src/lib/components/lead-status-chip.d.ts +9 -3
- package/src/lib/components/lead-status-chip.js +8 -4
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-status-options.d.ts +20 -0
- package/src/lib/components/lead-status-options.js +51 -0
- package/src/lib/components/lead-status-options.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
- package/src/lib/components/lead-unqualify-dialog.js +50 -18
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.d.ts +0 -3
- package/src/lib/components/leads-bulk-bar.js +29 -29
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +120 -68
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.d.ts +4 -0
- package/src/lib/components/linked-deals-card.js +47 -3
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.js +43 -3
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +8 -0
- package/src/lib/components/new-contact-drawer.js +108 -6
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/new-lead-drawer.d.ts +9 -2
- package/src/lib/components/new-lead-drawer.js +88 -121
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/picklist-select.d.ts +36 -0
- package/src/lib/components/picklist-select.js +114 -0
- package/src/lib/components/picklist-select.js.map +1 -0
- package/src/lib/components/picklist-values-card.d.ts +34 -0
- package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
- package/src/lib/components/picklist-values-card.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
- package/src/lib/components/pipeline-stages-dialog.js +26 -4
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/pipelines-dialog.js +15 -6
- package/src/lib/components/pipelines-dialog.js.map +1 -1
- package/src/lib/components/recent-activity-feed.js +20 -5
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/recipes-card.js +4 -2
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-tasks-card.js +15 -8
- package/src/lib/components/record-tasks-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.d.ts +6 -0
- package/src/lib/components/reports/forecast-card.js +101 -3
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
- package/src/lib/components/reports/lead-funnel-card.js +9 -5
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.d.ts +23 -0
- package/src/lib/components/reports/lead-source-card.js +318 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -0
- package/src/lib/components/reports/pipeline-card.js +2 -1
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-export.js +1 -1
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports-section.js +8 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
- package/src/lib/components/sharing-rule-drawer.js +108 -19
- package/src/lib/components/sharing-rule-drawer.js.map +1 -1
- package/src/lib/components/sharing-rules-card.js +11 -3
- package/src/lib/components/sharing-rules-card.js.map +1 -1
- package/src/lib/components/task-cells.d.ts +9 -1
- package/src/lib/components/task-cells.js +9 -4
- package/src/lib/components/task-cells.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +111 -43
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
- package/src/lib/components/tasks-bulk-bar.js +7 -20
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
- package/src/lib/components/tasks-calendar.d.ts +3 -0
- package/src/lib/components/tasks-calendar.js +8 -1
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.d.ts +3 -3
- package/src/lib/components/tasks-section.js +58 -43
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +2 -9
- package/src/lib/constants/api-routes.js +1 -7
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +11 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +14 -0
- package/src/lib/constants/company-filters.js +51 -1
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +2 -0
- package/src/lib/constants/contact-filters.js +18 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +22 -1
- package/src/lib/constants/deal-filters.js +67 -4
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +3 -0
- package/src/lib/declarations.console-server.js +35 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +35 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +11 -0
- package/src/lib/declarations.server.js +97 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-company-picklists.d.ts +15 -0
- package/src/lib/hooks/use-company-picklists.js +55 -0
- package/src/lib/hooks/use-company-picklists.js.map +1 -0
- package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
- package/src/lib/hooks/use-crm-picklist.js +53 -0
- package/src/lib/hooks/use-crm-picklist.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
- package/src/lib/hooks/use-crm-record-names.js +45 -19
- package/src/lib/hooks/use-crm-record-names.js.map +1 -1
- package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
- package/src/lib/hooks/use-crm-task-picklists.js +45 -0
- package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +14 -0
- package/src/lib/hooks/use-deals.js +29 -0
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
- package/src/lib/hooks/use-lead-picklists.js +46 -0
- package/src/lib/hooks/use-lead-picklists.js.map +1 -0
- package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
- package/src/lib/hooks/use-lead-source-picklist.js +12 -22
- package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
- package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
- package/src/lib/hooks/use-lead-status-picklist.js +40 -0
- package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
- package/src/lib/model/companies.d.ts +32 -3
- package/src/lib/model/companies.js +133 -16
- package/src/lib/model/companies.js.map +1 -1
- package/src/lib/model/contact-merge.d.ts +27 -3
- package/src/lib/model/contact-merge.js +81 -7
- package/src/lib/model/contact-merge.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +17 -0
- package/src/lib/model/contact-record.js +17 -2
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +31 -2
- package/src/lib/model/contact-update.js +14 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +5 -0
- package/src/lib/model/crm-list-query.js +8 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/crm-next-activity.d.ts +107 -0
- package/src/lib/model/crm-next-activity.js +140 -0
- package/src/lib/model/crm-next-activity.js.map +1 -0
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +259 -0
- package/src/lib/model/crm-recipes.js.map +1 -0
- package/src/lib/model/crm-record-counts.d.ts +36 -0
- package/src/lib/model/crm-record-counts.js +54 -0
- package/src/lib/model/crm-record-counts.js.map +1 -0
- package/src/lib/model/crm-record-lists.d.ts +23 -0
- package/src/lib/model/crm-record-lists.js +161 -0
- package/src/lib/model/crm-record-lists.js.map +1 -0
- package/src/lib/model/crm-reports.d.ts +73 -3
- package/src/lib/model/crm-reports.js +120 -1
- package/src/lib/model/crm-reports.js.map +1 -1
- package/src/lib/model/crm-sharing.d.ts +21 -3
- package/src/lib/model/crm-sharing.js +27 -6
- package/src/lib/model/crm-sharing.js.map +1 -1
- package/src/lib/model/crm-task-reminders.d.ts +152 -0
- package/src/lib/model/crm-task-reminders.js +161 -0
- package/src/lib/model/crm-task-reminders.js.map +1 -0
- package/src/lib/model/deal-board-model.d.ts +8 -2
- package/src/lib/model/deal-board-model.js +15 -2
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +45 -0
- package/src/lib/model/deal-contact-roles.js +73 -0
- package/src/lib/model/deal-contact-roles.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +56 -0
- package/src/lib/model/deal-form-model.js +79 -7
- package/src/lib/model/deal-form-model.js.map +1 -1
- package/src/lib/model/download-text-file.d.ts +23 -0
- package/src/lib/model/download-text-file.js +34 -0
- package/src/lib/model/download-text-file.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +36 -2
- package/src/lib/model/lead-filters.js +123 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +38 -0
- package/src/lib/model/lead-source-direction.js +52 -0
- package/src/lib/model/lead-source-direction.js.map +1 -0
- package/src/lib/model/next-activity.d.ts +1 -1
- package/src/lib/model/next-activity.js.map +1 -1
- package/src/lib/model/picklist-values.d.ts +119 -0
- package/src/lib/model/picklist-values.js +256 -0
- package/src/lib/model/picklist-values.js.map +1 -0
- package/src/lib/model/record-facts-disclosed.d.ts +81 -0
- package/src/lib/model/record-facts-disclosed.js +281 -0
- package/src/lib/model/record-facts-disclosed.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +215 -34
- package/src/lib/model/record-facts.js +307 -128
- package/src/lib/model/record-facts.js.map +1 -1
- package/src/lib/model/task-picklists.d.ts +52 -0
- package/src/lib/model/task-picklists.js +83 -0
- package/src/lib/model/task-picklists.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +13 -0
- package/src/lib/model/task-routes.js +23 -4
- package/src/lib/model/task-routes.js.map +1 -1
- package/src/lib/plan-entitlements.js +6 -4
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts.js +220 -14
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +14 -1
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-companies.js +123 -14
- package/src/lib/server/api-v1/crm-companies.js.map +1 -1
- package/src/lib/server/api-v1/crm-deals.js +291 -26
- package/src/lib/server/api-v1/crm-deals.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +144 -38
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-tasks.js +78 -34
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +0 -11
- package/src/lib/server/api-v1/openapi.js +171 -16
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/assign-contact-owner.d.ts +188 -0
- package/src/lib/server/assign-contact-owner.js +446 -0
- package/src/lib/server/assign-contact-owner.js.map +1 -0
- package/src/lib/server/associate-company-by-domain.d.ts +70 -0
- package/src/lib/server/associate-company-by-domain.js +153 -0
- package/src/lib/server/associate-company-by-domain.js.map +1 -0
- package/src/lib/server/automation-steps.d.ts +140 -0
- package/src/lib/server/automation-steps.js +669 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.d.ts +17 -0
- package/src/lib/server/capture-contact.js +95 -26
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/capture-host-contact.d.ts +104 -0
- package/src/lib/server/capture-host-contact.js +151 -0
- package/src/lib/server/capture-host-contact.js.map +1 -0
- package/src/lib/server/company-children.d.ts +25 -0
- package/src/lib/server/company-children.js +42 -0
- package/src/lib/server/company-children.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +18 -0
- package/src/lib/server/company-delete.js +44 -21
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/contact-email-history.js +21 -19
- package/src/lib/server/contact-email-history.js.map +1 -1
- package/src/lib/server/contact-remove.js +27 -2
- package/src/lib/server/contact-remove.js.map +1 -1
- package/src/lib/server/contact-reports-to.d.ts +61 -0
- package/src/lib/server/contact-reports-to.js +114 -0
- package/src/lib/server/contact-reports-to.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +42 -1
- package/src/lib/server/contact-update.js +204 -23
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +46 -1
- package/src/lib/server/convert-host-lead.js +173 -15
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +12 -5
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-email-activity.d.ts +118 -0
- package/src/lib/server/crm-email-activity.js +148 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.js +1 -1
- package/src/lib/server/crm-inbound-email.js.map +1 -1
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- package/src/lib/server/crm-record-indexes.d.ts +45 -0
- package/src/lib/server/crm-record-indexes.js +78 -0
- package/src/lib/server/crm-record-indexes.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +9 -1
- package/src/lib/server/crm-sharing.js +23 -3
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/deal-contact-roles.d.ts +27 -0
- package/src/lib/server/deal-contact-roles.js +89 -0
- package/src/lib/server/deal-contact-roles.js.map +1 -0
- package/src/lib/server/email-send.js +2 -1
- package/src/lib/server/email-send.js.map +1 -1
- package/src/lib/server/lead-convert.d.ts +5 -0
- package/src/lib/server/lead-convert.js +19 -3
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +17 -2
- package/src/lib/server/lead-create.js +46 -14
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +6 -0
- package/src/lib/server/lead-nurturing.js +57 -20
- package/src/lib/server/lead-nurturing.js.map +1 -1
- package/src/lib/server/lead-picklists.d.ts +35 -0
- package/src/lib/server/lead-picklists.js +52 -0
- package/src/lib/server/lead-picklists.js.map +1 -0
- package/src/lib/server/lead-source-picklist.d.ts +1 -13
- package/src/lib/server/lead-source-picklist.js +6 -33
- package/src/lib/server/lead-source-picklist.js.map +1 -1
- package/src/lib/server/merge-contacts.js +23 -5
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/next-activity-routes.js +2 -1
- package/src/lib/server/next-activity-routes.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +53 -0
- package/src/lib/server/person-eraser.js +149 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/person-records.d.ts +69 -0
- package/src/lib/server/person-records.js +317 -0
- package/src/lib/server/person-records.js.map +1 -0
- package/src/lib/server/person-refund.d.ts +97 -0
- package/src/lib/server/person-refund.js +221 -0
- package/src/lib/server/person-refund.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +21 -0
- package/src/lib/server/picklist-values.js +308 -0
- package/src/lib/server/picklist-values.js.map +1 -0
- package/src/lib/server/read-picklist.d.ts +44 -0
- package/src/lib/server/read-picklist.js +65 -0
- package/src/lib/server/read-picklist.js.map +1 -0
- package/src/lib/server/recipe-routes.js +22 -8
- package/src/lib/server/recipe-routes.js.map +1 -1
- package/src/lib/server/record-email-state.js +7 -1
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.d.ts +3 -0
- package/src/lib/server/record-facts.js +233 -31
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-origin.d.ts +27 -0
- package/src/lib/server/record-origin.js +104 -0
- package/src/lib/server/record-origin.js.map +1 -0
- package/src/lib/server/record-timeline.js +40 -3
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/suite-gate.js +4 -3
- package/src/lib/server/suite-gate.js.map +1 -1
- package/src/lib/server/task-reminders-route.js +2 -1
- package/src/lib/server/task-reminders-route.js.map +1 -1
- package/src/lib/server/task-routes.js +33 -17
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/contact-filters.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 type ContactFieldDefinition,\n CRM_CONTACT_VIEW_FIELDS,\n CRM_FACET_KEY_ANY_GROUP,\n type CrmFacetKeyField,\n crmContactCustomColumn,\n crmFacetKey,\n crmFacetKeyValue,\n EMAIL_STATE_STATUSES,\n} from '@aglyn/aglyn'\nimport type {\n ListFilterField,\n ListFilterRequest,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n} from '../model/crm-list-query'\n\n/*==========================================\n * CONTACTS (`orgs/{orgId}/contacts`, read scoped to a host) — WHAT THE\n * PANEL OFFERS, AND WHAT THE QUERY ASKS (AGL-3321).\n *\n * Every clause and the search word are on the list's one Firestore query,\n * newest change first, paged by it. A contact is one row shared by every\n * holder, and most of what a holder filters by — the owner, the stage, the\n * sources, the company, the tags, a custom value, whether the person has\n * bought — lives on that holder's FACET, a path per holder no index can\n * reach. So every writer stamps `facetKeys` (`crmContactFacetKeys` in\n * `@aglyn/aglyn`): `{group}:{field}={value}` for each holder's value,\n * `{group}:{field}` for \"has one\", and the same under `*` for any holder.\n * Under a site a facet clause asks the viewing holder's key; at the\n * organization level, any holder's.\n *\n * What one query holds decides the rest:\n *\n * - ONE array clause. The scope clause (`visibleTo array-contains-any`),\n * the search (`searchTokens`), a name word (`nameTokens`), a form\n * (`formIds`) and a facet clause (`facetKeys`) all are one. The search\n * folds into the scope clause for a reader who may drop it; a facet or\n * form clause stands in the scope clause's place for that reader, since\n * only a record the viewing holder keeps carries its key. Two of them\n * at once is refused by name.\n * - ONE range. `updatedAt`, the field the list is ordered by, composes\n * with anything; Created and a name or address prefix order the list by\n * their own field and so stand alone beside the scope clause\n * (`contactSoloClause`), served by the `(visibleTo, field)` composites.\n * - equalities (the address, the site, the verdict, \"No next activity\")\n * compose freely, one `(field, updatedAt DESC)` composite each.\n *\n * The per-holder FIGURES (orders, lifetime value) live on each holder's\n * facet, where no range reaches — a top-level `ordersCount` was never\n * written — so they offer \"is not empty\" alone: \"has bought\".\n *\n * A collaborator's search is the start of the name (`CONTACT_PREFIX_SEARCH`).\n *=========================================*/\n\n/**\n * A word of the canonical name (`nameTokens`), the whole of it, or its start\n * or end — `nameLower`, which every contact carries, and `nameReversed`.\n */\nconst NAME_FIELD: ListFilterField = {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n tokensPath: 'nameTokens',\n reversedPath: 'nameReversed',\n operators: ['contains', 'equals', 'startsWith', 'endsWith'],\n}\n\n/**\n * The address. `normalizeContactEmail` lower-cases before every write, so the\n * stored value IS its own normalized key.\n */\nconst EMAIL_FIELD: ListFilterField = {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n}\n\n/** When the person was first captured — a range the list is then ordered by. */\nconst CREATED_FIELD: ListFilterField = {\n column: CRM_CONTACT_VIEW_FIELDS.createdAt,\n kind: 'date',\n path: 'createdAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n}\n\n/**\n * The forms a person came in through — the top-level `formIds` mirror of the\n * interactions' `formId` (AGL-2612), matched whole and as typed because a\n * form id is minted with mixed case. The form's own page links here with its\n * id; nobody types one.\n */\nconst FORM_FIELD: ListFilterField = {\n column: 'formIds',\n kind: 'text',\n path: 'formIds',\n tokensPath: 'formIds',\n verbatimTokens: true,\n operators: ['contains'],\n}\n\n/** What the Filters panel offers on a contact, in the grid's grammar. */\nexport const CONTACT_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n {\n // A holder's tags, matched whole and case-insensitively.\n column: CRM_CONTACT_VIEW_FIELDS.tags,\n kind: 'exact',\n path: 'tags',\n operators: ['contains', 'isAnyOf', 'isNotEmpty'],\n },\n FORM_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n // Per-holder figures: \"has bought\", never a range across holders.\n {\n column: CRM_CONTACT_VIEW_FIELDS.orders,\n kind: 'number',\n path: 'ordersCount',\n operators: ['isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.ltv,\n kind: 'number',\n path: 'ltvCents',\n operators: ['isNotEmpty'],\n },\n CREATED_FIELD,\n {\n column: CRM_CONTACT_VIEW_FIELDS.updatedAt,\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n /*\n * \"No next activity\" (AGL-2661): `nextTaskAtMs == null`, which every\n * contact carries from its creation until a task is scheduled against it.\n */\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n /*\n * THE FACET FIELDS (AGL-2617). The values are picked, not typed — a uid\n * from the roster, a stage from the fixed list, a source from its labels,\n * a company from the picker — so `equals` and `isAnyOf` are what they\n * offer; the section supplies the choices. The column names are the\n * contract `CRM_CONTACT_VIEW_FIELDS` states, because the dynamic-list\n * translator reads a view by them. The paths are the flattened ROW'S.\n */\n {\n column: CRM_CONTACT_VIEW_FIELDS.owner,\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.stage,\n kind: 'exact',\n path: 'lifecycleStage',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n // The `sources` presence map, matched on its keys — see `keysOf`.\n column: CRM_CONTACT_VIEW_FIELDS.source,\n kind: 'exact',\n path: 'sources',\n keysOf: true,\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.company,\n kind: 'exact',\n path: 'companyId',\n operators: ['equals', 'isNotEmpty'],\n },\n /*\n * The verdict on the address (AGL-3245), asked through `emailStatus` —\n * `none` for a person nothing has been said about, which is what \"is\n * empty\" asks.\n */\n {\n column: 'emailState',\n kind: 'exact',\n path: 'emailState.status',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty', 'isNotEmpty'],\n },\n]\n\n/**\n * One filter field per active custom contact field (AGL-2617). A custom\n * value lives under the holder's `custom` map, so it is asked through the\n * holder's facet key: equality on a picked, typed or ticked value, and\n * \"is not empty\". Its column is the one the table shows it under —\n * `crmContactCustomColumn(key)` — so a clause and a column agree on the name.\n */\nexport function contactCustomFilterFields(\n definitions: readonly Pick<\n ContactFieldDefinition,\n 'key' | 'type' | 'retiredAt'\n >[],\n): ListFilterField[] {\n return definitions\n .filter((definition) => !definition.retiredAt)\n .map((definition): ListFilterField => {\n const column = crmContactCustomColumn(definition.key)\n const path = `custom.${definition.key}`\n switch (definition.type) {\n case 'number':\n return {\n column,\n kind: 'number',\n path,\n operators: ['=', 'isNotEmpty'],\n }\n case 'date':\n return { column, kind: 'date', path, operators: ['isNotEmpty'] }\n case 'checkbox':\n return { column, kind: 'boolean', path }\n case 'select':\n return {\n column,\n kind: 'exact',\n path,\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n }\n default:\n return {\n column,\n kind: 'text',\n path,\n lowerPath: path,\n operators: ['equals', 'isNotEmpty'],\n }\n }\n })\n}\n\n/** The custom fields' headers, keyed by the column each filters as. */\nexport function contactCustomFilterHeaders(\n definitions: readonly Pick<ContactFieldDefinition, 'key' | 'label'>[],\n): Record<string, string> {\n return Object.fromEntries(\n definitions.map((definition) => [\n crmContactCustomColumn(definition.key),\n definition.label || definition.key,\n ]),\n )\n}\n\n/**\n * How every filterable contact field reads — on a chip, in the add-filter\n * picker, and as the header of a filter-only hidden column.\n */\nexport const CONTACT_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Contact',\n email: 'Email',\n tags: 'Tags',\n formIds: 'Form ID',\n hostId: 'Site ID',\n ordersCount: 'Orders',\n ltvCents: 'Lifetime value (cents)',\n createdAt: 'Created',\n updatedAt: 'Updated',\n nextTaskAtMs: 'Next activity',\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'Owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'Stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'Source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'Company',\n emailState: 'Email',\n}\n\n/*------------------------------------------\n * THE QUERY THE CONTACTS LIST RUNS.\n *-----------------------------------------*/\n\n/** The list's one order: newest change first. */\nexport const CONTACT_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc' },\n]\n\n/** The stored array a facet clause is asked of. */\nexport const CONTACT_FACET_KEYS_PATH = 'facetKeys'\n\n/** The fields the Contacts query asks — each a stored field, as named. */\nexport const CONTACT_QUERY_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n FORM_FIELD,\n CREATED_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n {\n column: 'updatedAt',\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n {\n column: 'emailStatus',\n kind: 'exact',\n path: 'emailStatus',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CONTACT_FACET_KEYS_PATH,\n kind: 'exact',\n path: CONTACT_FACET_KEYS_PATH,\n tokensPath: CONTACT_FACET_KEYS_PATH,\n operators: ['contains', 'isAnyOf'],\n },\n]\n\nexport const CONTACT_LIST_DECLARATION: ListQueryDeclaration = {\n fields: CONTACT_QUERY_FIELDS,\n sorts: CONTACT_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The grid fields a stored clause passes to the query unchanged. */\nconst AS_STORED = new Set([\n 'name',\n 'email',\n 'formIds',\n 'hostId',\n 'createdAt',\n 'updatedAt',\n 'nextTaskAtMs',\n])\n\n/**\n * The ranges that stand ALONE beside the scope clause: Created and a name\n * or address prefix each order the list by their own field, which the\n * `(visibleTo, field)` composites serve and nothing beside them. Updated is\n * not one: the list is ordered by it already, so it composes freely.\n */\nexport function contactSoloClause(clause: ListFilterRequest): boolean {\n if (clause.field === 'createdAt') return true\n if (clause.field === 'name') return clause.op === 'startsWith' || clause.op === 'endsWith'\n return clause.field === 'email' && clause.op === 'startsWith'\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the contact's name, beside the scope clause.\n */\nexport const CONTACT_PREFIX_SEARCH = {\n field: NAME_FIELD,\n notice: 'Search matches the start of a contact’s name for access limited to specific sites.',\n}\n\n/** The facet field each grid column is keyed under. */\nconst FACET_FIELDS: Readonly<Record<string, CrmFacetKeyField>> = {\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'company',\n [CRM_CONTACT_VIEW_FIELDS.tags]: 'tag',\n [CRM_CONTACT_VIEW_FIELDS.orders]: 'orders',\n [CRM_CONTACT_VIEW_FIELDS.ltv]: 'ltv',\n}\n\n/** Who is asking: the viewing holder, and whether their query may drop the scope clause. */\nexport interface ContactQueryReader {\n /** The viewing holder's group id under a site; `null` at the organization level. */\n groupId: string | null\n /**\n * Whether the reader's query may stand a facet or form clause in the\n * scope clause's place (`useCrmFoldsScope`) — always at the organization\n * level, where there is no scope clause.\n */\n foldsScope: boolean\n}\n\n/** Why a reader whose access is some sites cannot filter by a holder's field. */\nconst LIMITED_ACCESS =\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it'\n\n/** The facet field a grid column names, or null for one that is not a facet field. */\nfunction facetFieldOf(column: string): CrmFacetKeyField | null {\n if (FACET_FIELDS[column]) return FACET_FIELDS[column]\n if (column.startsWith(CRM_CONTACT_VIEW_FIELDS.custom)) {\n return `custom.${column.slice(CRM_CONTACT_VIEW_FIELDS.custom.length)}`\n }\n return null\n}\n\n/**\n * One grid clause as the clause the Contacts query asks — see the block at\n * the top. A clause a query cannot serve for this reader is refused by\n * name, never matched over the rows a page loaded.\n */\nexport function contactQueryClause(\n clause: ListFilterRequest,\n reader: ContactQueryReader,\n): CrmClauseAsked {\n if (AS_STORED.has(clause.field)) {\n if (clause.field === 'formIds' && !reader.foldsScope)\n return { refused: LIMITED_ACCESS }\n return { field: clause.field, op: clause.op, value: clause.value }\n }\n if (clause.field === 'emailState') {\n if (clause.op === 'isEmpty')\n return { field: 'emailStatus', op: 'equals', value: 'none' }\n if (clause.op === 'isNotEmpty')\n return crmAnyOf('emailStatus', EMAIL_STATE_STATUSES)\n const values = crmClauseValues(clause)\n if (\n values.some(\n (value) => !(EMAIL_STATE_STATUSES as readonly string[]).includes(value),\n )\n ) {\n return { refused: 'pick one of the choices' }\n }\n return crmAnyOf('emailStatus', values)\n }\n const facet = facetFieldOf(clause.field)\n if (!facet) return { refused: 'this list does not filter by that' }\n if (!reader.foldsScope) return { refused: LIMITED_ACCESS }\n const group = reader.groupId ?? CRM_FACET_KEY_ANY_GROUP\n if (clause.op === 'isNotEmpty') {\n return {\n field: CONTACT_FACET_KEYS_PATH,\n op: 'contains',\n value: crmFacetKey(group, facet),\n }\n }\n const keys: string[] = []\n for (const value of crmClauseValues(clause)) {\n const key = crmFacetKeyValue(clause.op === '=' ? Number(value) : value)\n if (key === null) return { refused: 'no value yet' }\n keys.push(crmFacetKey(group, facet, key))\n }\n const asked = crmAnyOf(CONTACT_FACET_KEYS_PATH, keys)\n // One key is an `array-contains`; several, an `array-contains-any`.\n return 'refused' in asked || asked.op === 'isAnyOf'\n ? asked\n : { ...asked, op: 'contains' }\n}\n\n/**\n * Whether an asked clause stands in for the scope clause: a facet key of\n * the viewing holder, which only a record that holder keeps carries, or a\n * form, which only its site's captures carry.\n */\nexport function contactClauseImpliesScope(groupId: string | null) {\n return (clause: ListFilterRequest): boolean => {\n if (clause.field === 'formIds') return true\n if (clause.field !== CONTACT_FACET_KEYS_PATH || !groupId) return false\n return crmClauseValues(clause).every((key) => key.startsWith(`${groupId}:`))\n }\n}\n\n/**\n * The query shapes the Contacts list can send, as declarations the index\n * pin enumerates (AGL-3321): the list's own, without its solo ranges, whose\n * every equality rides `(field, updatedAt DESC)`; and each solo range beside\n * the scope clause alone.\n */\nexport function contactIndexShapes(): ListQueryDeclaration[] {\n const soloOps = new Set(['startsWith', 'endsWith'])\n const main: ListQueryDeclaration = {\n ...CONTACT_LIST_DECLARATION,\n fields: CONTACT_QUERY_FIELDS.filter((field) => field !== CREATED_FIELD).map((field) =>\n field.operators\n ? { ...field, operators: field.operators.filter((op) => !soloOps.has(op)) }\n : field,\n ),\n }\n const solo: ListQueryDeclaration = {\n fields: [\n { ...NAME_FIELD, operators: ['startsWith', 'endsWith'] },\n { ...EMAIL_FIELD, operators: ['startsWith'] },\n CREATED_FIELD,\n ],\n sorts: [],\n }\n return [main, solo]\n}\n"],"names":["CRM_CONTACT_VIEW_FIELDS","CRM_FACET_KEY_ANY_GROUP","crmContactCustomColumn","crmFacetKey","crmFacetKeyValue","EMAIL_STATE_STATUSES","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","NAME_FIELD","column","kind","path","lowerPath","tokensPath","reversedPath","operators","EMAIL_FIELD","presence","CREATED_FIELD","createdAt","FORM_FIELD","verbatimTokens","CONTACT_LIST_FILTER_FIELDS","tags","orders","ltv","updatedAt","storedAs","owner","stage","source","keysOf","company","contactCustomFilterFields","definitions","filter","definition","retiredAt","map","key","type","contactCustomFilterHeaders","Object","fromEntries","label","CONTACT_LIST_FILTER_HEADERS","name","email","formIds","hostId","ordersCount","ltvCents","nextTaskAtMs","emailState","CONTACT_LIST_SORTS","direction","CONTACT_FACET_KEYS_PATH","CONTACT_QUERY_FIELDS","CONTACT_LIST_DECLARATION","fields","sorts","search","AS_STORED","Set","contactSoloClause","clause","field","op","CONTACT_PREFIX_SEARCH","notice","FACET_FIELDS","LIMITED_ACCESS","facetFieldOf","startsWith","custom","slice","length","contactQueryClause","reader","has","foldsScope","refused","value","values","some","includes","facet","group","groupId","keys","Number","push","asked","contactClauseImpliesScope","every","contactIndexShapes","soloOps","main","solo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/contact-filters.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 type ContactFieldDefinition,\n CRM_CONTACT_VIEW_FIELDS,\n CRM_FACET_KEY_ANY_GROUP,\n type CrmFacetKeyField,\n crmContactCustomColumn,\n crmFacetKey,\n crmFacetKeyValue,\n EMAIL_STATE_STATUSES,\n} from '@aglyn/aglyn'\nimport type {\n ListFilterField,\n ListFilterRequest,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n} from '../model/crm-list-query'\n\n/*==========================================\n * CONTACTS (`orgs/{orgId}/contacts`, read scoped to a host) — WHAT THE\n * PANEL OFFERS, AND WHAT THE QUERY ASKS (AGL-3321).\n *\n * Every clause and the search word are on the list's one Firestore query,\n * newest change first, paged by it. A contact is one row shared by every\n * holder, and most of what a holder filters by — the owner, the stage, the\n * sources, the company, the tags, a custom value, whether the person has\n * bought — lives on that holder's FACET, a path per holder no index can\n * reach. So every writer stamps `facetKeys` (`crmContactFacetKeys` in\n * `@aglyn/aglyn`): `{group}:{field}={value}` for each holder's value,\n * `{group}:{field}` for \"has one\", and the same under `*` for any holder.\n * Under a site a facet clause asks the viewing holder's key; at the\n * organization level, any holder's.\n *\n * What one query holds decides the rest:\n *\n * - ONE array clause. The scope clause (`visibleTo array-contains-any`),\n * the search (`searchTokens`), a name word (`nameTokens`), a form\n * (`formIds`) and a facet clause (`facetKeys`) all are one. The search\n * folds into the scope clause for a reader who may drop it; a facet or\n * form clause stands in the scope clause's place for that reader, since\n * only a record the viewing holder keeps carries its key. Two of them\n * at once is refused by name.\n * - ONE range. `updatedAt`, the field the list is ordered by, composes\n * with anything; Created and a name or address prefix order the list by\n * their own field and so stand alone beside the scope clause\n * (`contactSoloClause`), served by the `(visibleTo, field)` composites.\n * - equalities (the address, the site, the verdict, \"No next activity\")\n * compose freely, one `(field, updatedAt DESC)` composite each.\n *\n * The per-holder FIGURES (orders, lifetime value) live on each holder's\n * facet, where no range reaches — a top-level `ordersCount` was never\n * written — so they offer \"is not empty\" alone: \"has bought\".\n *\n * A collaborator's search is the start of the name (`CONTACT_PREFIX_SEARCH`).\n *=========================================*/\n\n/**\n * A word of the canonical name (`nameTokens`), the whole of it, or its start\n * or end — `nameLower`, which every contact carries, and `nameReversed`.\n */\nconst NAME_FIELD: ListFilterField = {\n column: 'name',\n kind: 'text',\n path: 'name',\n lowerPath: 'nameLower',\n tokensPath: 'nameTokens',\n reversedPath: 'nameReversed',\n operators: ['contains', 'equals', 'startsWith', 'endsWith'],\n}\n\n/**\n * The address. `normalizeContactEmail` lower-cases before every write, so the\n * stored value IS its own normalized key.\n */\nconst EMAIL_FIELD: ListFilterField = {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['equals', 'startsWith'],\n}\n\n/** When the person was first captured — a range the list is then ordered by. */\nconst CREATED_FIELD: ListFilterField = {\n column: CRM_CONTACT_VIEW_FIELDS.createdAt,\n kind: 'date',\n path: 'createdAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n}\n\n/**\n * The forms a person came in through — the top-level `formIds` mirror of the\n * interactions' `formId` (AGL-2612), matched whole and as typed because a\n * form id is minted with mixed case. The form's own page links here with its\n * id; nobody types one.\n */\nconst FORM_FIELD: ListFilterField = {\n column: 'formIds',\n kind: 'text',\n path: 'formIds',\n tokensPath: 'formIds',\n verbatimTokens: true,\n operators: ['contains'],\n}\n\n/** The column a contact's lead source filters as (AGL-3511). */\nexport const CONTACT_LEAD_SOURCE_COLUMN = 'leadSource'\n\n/** What the Filters panel offers on a contact, in the grid's grammar. */\nexport const CONTACT_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n {\n // A holder's tags, matched whole and case-insensitively.\n column: CRM_CONTACT_VIEW_FIELDS.tags,\n kind: 'exact',\n path: 'tags',\n operators: ['contains', 'isAnyOf', 'isNotEmpty'],\n },\n FORM_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n // Per-holder figures: \"has bought\", never a range across holders.\n {\n column: CRM_CONTACT_VIEW_FIELDS.orders,\n kind: 'number',\n path: 'ordersCount',\n operators: ['isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.ltv,\n kind: 'number',\n path: 'ltvCents',\n operators: ['isNotEmpty'],\n },\n CREATED_FIELD,\n {\n column: CRM_CONTACT_VIEW_FIELDS.updatedAt,\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n /*\n * \"No next activity\" (AGL-2661): `nextTaskAtMs == null`, which every\n * contact carries from its creation until a task is scheduled against it.\n */\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n /*\n * THE FACET FIELDS (AGL-2617). The values are picked, not typed — a uid\n * from the roster, a stage from the fixed list, a source from its labels,\n * a company from the picker — so `equals` and `isAnyOf` are what they\n * offer; the section supplies the choices. The column names are the\n * contract `CRM_CONTACT_VIEW_FIELDS` states, because the dynamic-list\n * translator reads a view by them. The paths are the flattened ROW'S.\n */\n {\n column: CRM_CONTACT_VIEW_FIELDS.owner,\n kind: 'exact',\n path: 'ownerUid',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.stage,\n kind: 'exact',\n path: 'lifecycleStage',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n {\n // The `sources` presence map, matched on its keys — see `keysOf`.\n column: CRM_CONTACT_VIEW_FIELDS.source,\n kind: 'exact',\n path: 'sources',\n keysOf: true,\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CRM_CONTACT_VIEW_FIELDS.company,\n kind: 'exact',\n path: 'companyId',\n operators: ['equals', 'isNotEmpty'],\n },\n /*\n * A holder's lead source (AGL-3511), picked from the org's Lead source\n * values and asked through its `leadSource` facet key — the label as the\n * picklist compares it.\n */\n {\n column: CONTACT_LEAD_SOURCE_COLUMN,\n kind: 'exact',\n path: 'leadSource',\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n },\n /*\n * The verdict on the address (AGL-3245), asked through `emailStatus` —\n * `none` for a person nothing has been said about, which is what \"is\n * empty\" asks.\n */\n {\n column: 'emailState',\n kind: 'exact',\n path: 'emailState.status',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty', 'isNotEmpty'],\n },\n]\n\n/**\n * One filter field per active custom contact field (AGL-2617). A custom\n * value lives under the holder's `custom` map, so it is asked through the\n * holder's facet key: equality on a picked, typed or ticked value, and\n * \"is not empty\". Its column is the one the table shows it under —\n * `crmContactCustomColumn(key)` — so a clause and a column agree on the name.\n */\nexport function contactCustomFilterFields(\n definitions: readonly Pick<\n ContactFieldDefinition,\n 'key' | 'type' | 'retiredAt'\n >[],\n): ListFilterField[] {\n return definitions\n .filter((definition) => !definition.retiredAt)\n .map((definition): ListFilterField => {\n const column = crmContactCustomColumn(definition.key)\n const path = `custom.${definition.key}`\n switch (definition.type) {\n case 'number':\n return {\n column,\n kind: 'number',\n path,\n operators: ['=', 'isNotEmpty'],\n }\n case 'date':\n return { column, kind: 'date', path, operators: ['isNotEmpty'] }\n case 'checkbox':\n return { column, kind: 'boolean', path }\n case 'select':\n return {\n column,\n kind: 'exact',\n path,\n operators: ['equals', 'isAnyOf', 'isNotEmpty'],\n }\n default:\n return {\n column,\n kind: 'text',\n path,\n lowerPath: path,\n operators: ['equals', 'isNotEmpty'],\n }\n }\n })\n}\n\n/** The custom fields' headers, keyed by the column each filters as. */\nexport function contactCustomFilterHeaders(\n definitions: readonly Pick<ContactFieldDefinition, 'key' | 'label'>[],\n): Record<string, string> {\n return Object.fromEntries(\n definitions.map((definition) => [\n crmContactCustomColumn(definition.key),\n definition.label || definition.key,\n ]),\n )\n}\n\n/**\n * How every filterable contact field reads — on a chip, in the add-filter\n * picker, and as the header of a filter-only hidden column.\n */\nexport const CONTACT_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Contact',\n email: 'Email',\n tags: 'Tags',\n formIds: 'Form ID',\n hostId: 'Site ID',\n ordersCount: 'Orders',\n ltvCents: 'Lifetime value (cents)',\n createdAt: 'Created',\n updatedAt: 'Updated',\n nextTaskAtMs: 'Next activity',\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'Owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'Stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'Source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'Company',\n [CONTACT_LEAD_SOURCE_COLUMN]: 'Lead source',\n emailState: 'Email',\n}\n\n/*------------------------------------------\n * THE QUERY THE CONTACTS LIST RUNS.\n *-----------------------------------------*/\n\n/** The list's one order: newest change first. */\nexport const CONTACT_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc' },\n]\n\n/** The stored array a facet clause is asked of. */\nexport const CONTACT_FACET_KEYS_PATH = 'facetKeys'\n\n/** The fields the Contacts query asks — each a stored field, as named. */\nexport const CONTACT_QUERY_FIELDS: readonly ListFilterField[] = [\n NAME_FIELD,\n EMAIL_FIELD,\n FORM_FIELD,\n CREATED_FIELD,\n { column: 'hostId', kind: 'exact', path: 'hostId', presence: 'always' },\n {\n column: 'updatedAt',\n kind: 'date',\n path: 'updatedAt',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n column: 'nextTaskAtMs',\n kind: 'date',\n path: 'nextTaskAtMs',\n presence: 'nullable',\n storedAs: 'millis',\n operators: ['isEmpty'],\n },\n {\n column: 'emailStatus',\n kind: 'exact',\n path: 'emailStatus',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: CONTACT_FACET_KEYS_PATH,\n kind: 'exact',\n path: CONTACT_FACET_KEYS_PATH,\n tokensPath: CONTACT_FACET_KEYS_PATH,\n operators: ['contains', 'isAnyOf'],\n },\n]\n\nexport const CONTACT_LIST_DECLARATION: ListQueryDeclaration = {\n fields: CONTACT_QUERY_FIELDS,\n sorts: CONTACT_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The grid fields a stored clause passes to the query unchanged. */\nconst AS_STORED = new Set([\n 'name',\n 'email',\n 'formIds',\n 'hostId',\n 'createdAt',\n 'updatedAt',\n 'nextTaskAtMs',\n])\n\n/**\n * The ranges that stand ALONE beside the scope clause: Created and a name\n * or address prefix each order the list by their own field, which the\n * `(visibleTo, field)` composites serve and nothing beside them. Updated is\n * not one: the list is ordered by it already, so it composes freely.\n */\nexport function contactSoloClause(clause: ListFilterRequest): boolean {\n if (clause.field === 'createdAt') return true\n if (clause.field === 'name') return clause.op === 'startsWith' || clause.op === 'endsWith'\n return clause.field === 'email' && clause.op === 'startsWith'\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the contact's name, beside the scope clause.\n */\nexport const CONTACT_PREFIX_SEARCH = {\n field: NAME_FIELD,\n notice: 'Search matches the start of a contact’s name for access limited to specific sites.',\n}\n\n/** The facet field each grid column is keyed under. */\nconst FACET_FIELDS: Readonly<Record<string, CrmFacetKeyField>> = {\n [CRM_CONTACT_VIEW_FIELDS.owner]: 'owner',\n [CRM_CONTACT_VIEW_FIELDS.stage]: 'stage',\n [CRM_CONTACT_VIEW_FIELDS.source]: 'source',\n [CRM_CONTACT_VIEW_FIELDS.company]: 'company',\n [CRM_CONTACT_VIEW_FIELDS.tags]: 'tag',\n [CRM_CONTACT_VIEW_FIELDS.orders]: 'orders',\n [CRM_CONTACT_VIEW_FIELDS.ltv]: 'ltv',\n [CONTACT_LEAD_SOURCE_COLUMN]: 'leadSource',\n}\n\n/** Who is asking: the viewing holder, and whether their query may drop the scope clause. */\nexport interface ContactQueryReader {\n /** The viewing holder's group id under a site; `null` at the organization level. */\n groupId: string | null\n /**\n * Whether the reader's query may stand a facet or form clause in the\n * scope clause's place (`useCrmFoldsScope`) — always at the organization\n * level, where there is no scope clause.\n */\n foldsScope: boolean\n}\n\n/** Why a reader whose access is some sites cannot filter by a holder's field. */\nconst LIMITED_ACCESS =\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it'\n\n/** The facet field a grid column names, or null for one that is not a facet field. */\nfunction facetFieldOf(column: string): CrmFacetKeyField | null {\n if (FACET_FIELDS[column]) return FACET_FIELDS[column]\n if (column.startsWith(CRM_CONTACT_VIEW_FIELDS.custom)) {\n return `custom.${column.slice(CRM_CONTACT_VIEW_FIELDS.custom.length)}`\n }\n return null\n}\n\n/**\n * One grid clause as the clause the Contacts query asks — see the block at\n * the top. A clause a query cannot serve for this reader is refused by\n * name, never matched over the rows a page loaded.\n */\nexport function contactQueryClause(\n clause: ListFilterRequest,\n reader: ContactQueryReader,\n): CrmClauseAsked {\n if (AS_STORED.has(clause.field)) {\n if (clause.field === 'formIds' && !reader.foldsScope)\n return { refused: LIMITED_ACCESS }\n return { field: clause.field, op: clause.op, value: clause.value }\n }\n if (clause.field === 'emailState') {\n if (clause.op === 'isEmpty')\n return { field: 'emailStatus', op: 'equals', value: 'none' }\n if (clause.op === 'isNotEmpty')\n return crmAnyOf('emailStatus', EMAIL_STATE_STATUSES)\n const values = crmClauseValues(clause)\n if (\n values.some(\n (value) => !(EMAIL_STATE_STATUSES as readonly string[]).includes(value),\n )\n ) {\n return { refused: 'pick one of the choices' }\n }\n return crmAnyOf('emailStatus', values)\n }\n const facet = facetFieldOf(clause.field)\n if (!facet) return { refused: 'this list does not filter by that' }\n if (!reader.foldsScope) return { refused: LIMITED_ACCESS }\n const group = reader.groupId ?? CRM_FACET_KEY_ANY_GROUP\n if (clause.op === 'isNotEmpty') {\n return {\n field: CONTACT_FACET_KEYS_PATH,\n op: 'contains',\n value: crmFacetKey(group, facet),\n }\n }\n const keys: string[] = []\n for (const value of crmClauseValues(clause)) {\n const key = crmFacetKeyValue(clause.op === '=' ? Number(value) : value)\n if (key === null) return { refused: 'no value yet' }\n keys.push(crmFacetKey(group, facet, key))\n }\n const asked = crmAnyOf(CONTACT_FACET_KEYS_PATH, keys)\n // One key is an `array-contains`; several, an `array-contains-any`.\n return 'refused' in asked || asked.op === 'isAnyOf'\n ? asked\n : { ...asked, op: 'contains' }\n}\n\n/**\n * Whether an asked clause stands in for the scope clause: a facet key of\n * the viewing holder, which only a record that holder keeps carries, or a\n * form, which only its site's captures carry.\n */\nexport function contactClauseImpliesScope(groupId: string | null) {\n return (clause: ListFilterRequest): boolean => {\n if (clause.field === 'formIds') return true\n if (clause.field !== CONTACT_FACET_KEYS_PATH || !groupId) return false\n return crmClauseValues(clause).every((key) => key.startsWith(`${groupId}:`))\n }\n}\n\n/**\n * The query shapes the Contacts list can send, as declarations the index\n * pin enumerates (AGL-3321): the list's own, without its solo ranges, whose\n * every equality rides `(field, updatedAt DESC)`; and each solo range beside\n * the scope clause alone.\n */\nexport function contactIndexShapes(): ListQueryDeclaration[] {\n const soloOps = new Set(['startsWith', 'endsWith'])\n const main: ListQueryDeclaration = {\n ...CONTACT_LIST_DECLARATION,\n fields: CONTACT_QUERY_FIELDS.filter((field) => field !== CREATED_FIELD).map((field) =>\n field.operators\n ? { ...field, operators: field.operators.filter((op) => !soloOps.has(op)) }\n : field,\n ),\n }\n const solo: ListQueryDeclaration = {\n fields: [\n { ...NAME_FIELD, operators: ['startsWith', 'endsWith'] },\n { ...EMAIL_FIELD, operators: ['startsWith'] },\n CREATED_FIELD,\n ],\n sorts: [],\n }\n return [main, solo]\n}\n"],"names":["CRM_CONTACT_VIEW_FIELDS","CRM_FACET_KEY_ANY_GROUP","crmContactCustomColumn","crmFacetKey","crmFacetKeyValue","EMAIL_STATE_STATUSES","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","NAME_FIELD","column","kind","path","lowerPath","tokensPath","reversedPath","operators","EMAIL_FIELD","presence","CREATED_FIELD","createdAt","FORM_FIELD","verbatimTokens","CONTACT_LEAD_SOURCE_COLUMN","CONTACT_LIST_FILTER_FIELDS","tags","orders","ltv","updatedAt","storedAs","owner","stage","source","keysOf","company","contactCustomFilterFields","definitions","filter","definition","retiredAt","map","key","type","contactCustomFilterHeaders","Object","fromEntries","label","CONTACT_LIST_FILTER_HEADERS","name","email","formIds","hostId","ordersCount","ltvCents","nextTaskAtMs","emailState","CONTACT_LIST_SORTS","direction","CONTACT_FACET_KEYS_PATH","CONTACT_QUERY_FIELDS","CONTACT_LIST_DECLARATION","fields","sorts","search","AS_STORED","Set","contactSoloClause","clause","field","op","CONTACT_PREFIX_SEARCH","notice","FACET_FIELDS","LIMITED_ACCESS","facetFieldOf","startsWith","custom","slice","length","contactQueryClause","reader","has","foldsScope","refused","value","values","some","includes","facet","group","groupId","keys","Number","push","asked","contactClauseImpliesScope","every","contactIndexShapes","soloOps","main","solo"],"mappings":"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"}
|
|
@@ -14,13 +14,34 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter';
|
|
17
|
+
import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter';
|
|
18
|
+
import type { ListGridFilterCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
|
|
18
19
|
import type { ListQueryDeclaration, ListQueryFilter, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
|
|
20
|
+
import { type CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm';
|
|
21
|
+
import { type CrmClauseAsked } from '../model/crm-list-query';
|
|
19
22
|
/** The table's one order: newest change first. */
|
|
20
23
|
export declare const DEAL_LIST_SORTS: readonly ListQuerySort[];
|
|
24
|
+
/**
|
|
25
|
+
* The Type and Lead source controls' "none" choice (AGL-3516). Held in the
|
|
26
|
+
* saved view as an `isEmpty` clause, never as this string.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEAL_PICKLIST_FILTER_NONE = "\0none";
|
|
29
|
+
/**
|
|
30
|
+
* The Type and Lead source clauses' codec: "none" is an `isEmpty` clause,
|
|
31
|
+
* shown as a choice of the select — the Leads list's own.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEAL_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>>;
|
|
21
34
|
/** What the deals table's Filters panel offers — every one asked of the query. */
|
|
22
35
|
export declare const DEAL_LIST_FILTER_FIELDS: readonly ListFilterField[];
|
|
36
|
+
/** The fields the Deals query asks — each a stored field, as named. */
|
|
37
|
+
export declare const DEAL_QUERY_FIELDS: readonly ListFilterField[];
|
|
23
38
|
export declare const DEAL_LIST_DECLARATION: ListQueryDeclaration;
|
|
39
|
+
/**
|
|
40
|
+
* One grid clause as the clause the Deals query asks (AGL-3516): a Type or
|
|
41
|
+
* a Lead source by its key — "none" as `isEmpty` — and every other clause
|
|
42
|
+
* as it is stored.
|
|
43
|
+
*/
|
|
44
|
+
export declare function dealQueryClause(clause: CrmViewFilterClause | ListFilterRequest): CrmClauseAsked;
|
|
24
45
|
/**
|
|
25
46
|
* A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the
|
|
26
47
|
* start of the title, `titleLower`, beside the scope clause.
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/ import {
|
|
17
|
-
import {
|
|
16
|
+
*/ import { crmPicklistKey } from "@aglyn/aglyn/app-utils/crm";
|
|
17
|
+
import { CRM_NEXT_ACTIVITY_FILTER_FIELD } from "../components/crm-next-activity-column.js";
|
|
18
|
+
import { CRM_LIST_SEARCH, crmAnyOf, crmClauseValues, crmSelectField } from "../model/crm-list-query.js";
|
|
19
|
+
import { LEAD_SOURCE_CODEC, LEAD_SOURCE_FILTER_NONE } from "../model/lead-filters.js";
|
|
18
20
|
/*
|
|
19
21
|
* THE DEALS TABLE'S QUERY (AGL-3321).
|
|
20
22
|
*
|
|
@@ -29,21 +31,82 @@ import { CRM_LIST_SEARCH, crmSelectField } from "../model/crm-list-query.js";
|
|
|
29
31
|
*
|
|
30
32
|
* The board is not this: it draws a pipeline's open deals whole, bounded,
|
|
31
33
|
* and filters nothing.
|
|
34
|
+
*
|
|
35
|
+
* Type and Lead source (AGL-3516) are asked through the keys every deal
|
|
36
|
+
* writer stores beside the labels (`typeKey`, `leadSourceKey` in
|
|
37
|
+
* `crmDealListFields`) — `null` for none, so "No type" is `== null` — the
|
|
38
|
+
* way the Leads list asks its lead source (`dealQueryClause`).
|
|
32
39
|
*/ /** The table's one order: newest change first. */ export const DEAL_LIST_SORTS = [
|
|
33
40
|
{
|
|
34
41
|
path: 'updatedAt',
|
|
35
42
|
direction: 'desc'
|
|
36
43
|
}
|
|
37
44
|
];
|
|
45
|
+
/**
|
|
46
|
+
* The Type and Lead source controls' "none" choice (AGL-3516). Held in the
|
|
47
|
+
* saved view as an `isEmpty` clause, never as this string.
|
|
48
|
+
*/ export const DEAL_PICKLIST_FILTER_NONE = LEAD_SOURCE_FILTER_NONE;
|
|
49
|
+
/**
|
|
50
|
+
* The Type and Lead source clauses' codec: "none" is an `isEmpty` clause,
|
|
51
|
+
* shown as a choice of the select — the Leads list's own.
|
|
52
|
+
*/ export const DEAL_FILTER_CODECS = {
|
|
53
|
+
type: LEAD_SOURCE_CODEC,
|
|
54
|
+
leadSource: LEAD_SOURCE_CODEC
|
|
55
|
+
};
|
|
38
56
|
/** What the deals table's Filters panel offers — every one asked of the query. */ export const DEAL_LIST_FILTER_FIELDS = [
|
|
39
57
|
crmSelectField('status'),
|
|
40
|
-
CRM_NEXT_ACTIVITY_FILTER_FIELD
|
|
58
|
+
CRM_NEXT_ACTIVITY_FILTER_FIELD,
|
|
59
|
+
crmSelectField('type'),
|
|
60
|
+
crmSelectField('leadSource')
|
|
61
|
+
];
|
|
62
|
+
/** A picklist key the query asks: `null` for none, as every deal writer stores it. */ const keyField = (column)=>({
|
|
63
|
+
column,
|
|
64
|
+
kind: 'exact',
|
|
65
|
+
path: column,
|
|
66
|
+
presence: 'nullable',
|
|
67
|
+
operators: [
|
|
68
|
+
'equals',
|
|
69
|
+
'isAnyOf',
|
|
70
|
+
'isEmpty'
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
/** The fields the Deals query asks — each a stored field, as named. */ export const DEAL_QUERY_FIELDS = [
|
|
74
|
+
crmSelectField('status'),
|
|
75
|
+
CRM_NEXT_ACTIVITY_FILTER_FIELD,
|
|
76
|
+
keyField('typeKey'),
|
|
77
|
+
keyField('leadSourceKey')
|
|
41
78
|
];
|
|
42
79
|
export const DEAL_LIST_DECLARATION = {
|
|
43
|
-
fields:
|
|
80
|
+
fields: DEAL_QUERY_FIELDS,
|
|
44
81
|
sorts: DEAL_LIST_SORTS,
|
|
45
82
|
search: CRM_LIST_SEARCH
|
|
46
83
|
};
|
|
84
|
+
/** The picklist fields the panel shows by label and the query asks by key. */ const DEAL_KEYED_FIELDS = {
|
|
85
|
+
type: 'typeKey',
|
|
86
|
+
leadSource: 'leadSourceKey'
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* One grid clause as the clause the Deals query asks (AGL-3516): a Type or
|
|
90
|
+
* a Lead source by its key — "none" as `isEmpty` — and every other clause
|
|
91
|
+
* as it is stored.
|
|
92
|
+
*/ export function dealQueryClause(clause) {
|
|
93
|
+
const key = DEAL_KEYED_FIELDS[clause.field];
|
|
94
|
+
if (!key) return clause;
|
|
95
|
+
const values = crmClauseValues(clause);
|
|
96
|
+
if (clause.op === 'isEmpty' || values.length === 1 && values[0] === DEAL_PICKLIST_FILTER_NONE) {
|
|
97
|
+
return {
|
|
98
|
+
field: key,
|
|
99
|
+
op: 'isEmpty',
|
|
100
|
+
value: ''
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (values.includes(DEAL_PICKLIST_FILTER_NONE)) {
|
|
104
|
+
return {
|
|
105
|
+
refused: '"None" cannot be picked beside a value'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return crmAnyOf(key, values.map((value)=>crmPicklistKey(value)).filter((entry)=>Boolean(entry)));
|
|
109
|
+
}
|
|
47
110
|
/**
|
|
48
111
|
* A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the
|
|
49
112
|
* start of the title, `titleLower`, beside the scope clause.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/deal-filters.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 { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { CRM_NEXT_ACTIVITY_FILTER_FIELD } from '../components/crm-next-activity-column'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/deal-filters.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 { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type { ListGridFilterCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { crmPicklistKey, type CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm'\nimport { CRM_NEXT_ACTIVITY_FILTER_FIELD } from '../components/crm-next-activity-column'\nimport {\n CRM_LIST_SEARCH,\n crmAnyOf,\n type CrmClauseAsked,\n crmClauseValues,\n crmSelectField,\n} from '../model/crm-list-query'\nimport { LEAD_SOURCE_CODEC, LEAD_SOURCE_FILTER_NONE } from '../model/lead-filters'\n\n/*\n * THE DEALS TABLE'S QUERY (AGL-3321).\n *\n * `orgs/{orgId}/deals` in the chosen pipeline — the pipeline is the query's\n * base beside the scope clause — newest change first, paged by that query.\n * The status, \"No next activity\" (`nextTaskAtMs == null`, which every deal\n * is created carrying) and the search word are predicates on the same\n * query. The search reads `searchTokens`, the word prefixes of the title\n * every deal writer stamps (`crmDealListFields`), so \"renewal\" finds \"Acme\n * renewal\" on any page. Under a site the scope clause is the query's one\n * array clause and the search folds into it for a reader who may drop it.\n *\n * The board is not this: it draws a pipeline's open deals whole, bounded,\n * and filters nothing.\n *\n * Type and Lead source (AGL-3516) are asked through the keys every deal\n * writer stores beside the labels (`typeKey`, `leadSourceKey` in\n * `crmDealListFields`) — `null` for none, so \"No type\" is `== null` — the\n * way the Leads list asks its lead source (`dealQueryClause`).\n */\n\n/** The table's one order: newest change first. */\nexport const DEAL_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'updatedAt', direction: 'desc' },\n]\n\n/**\n * The Type and Lead source controls' \"none\" choice (AGL-3516). Held in the\n * saved view as an `isEmpty` clause, never as this string.\n */\nexport const DEAL_PICKLIST_FILTER_NONE = LEAD_SOURCE_FILTER_NONE\n\n/**\n * The Type and Lead source clauses' codec: \"none\" is an `isEmpty` clause,\n * shown as a choice of the select — the Leads list's own.\n */\nexport const DEAL_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>> = {\n type: LEAD_SOURCE_CODEC,\n leadSource: LEAD_SOURCE_CODEC,\n}\n\n/** What the deals table's Filters panel offers — every one asked of the query. */\nexport const DEAL_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n crmSelectField('type'),\n crmSelectField('leadSource'),\n]\n\n/** A picklist key the query asks: `null` for none, as every deal writer stores it. */\nconst keyField = (column: string): ListFilterField => ({\n column,\n kind: 'exact',\n path: column,\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty'],\n})\n\n/** The fields the Deals query asks — each a stored field, as named. */\nexport const DEAL_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n CRM_NEXT_ACTIVITY_FILTER_FIELD,\n keyField('typeKey'),\n keyField('leadSourceKey'),\n]\n\nexport const DEAL_LIST_DECLARATION: ListQueryDeclaration = {\n fields: DEAL_QUERY_FIELDS,\n sorts: DEAL_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/** The picklist fields the panel shows by label and the query asks by key. */\nconst DEAL_KEYED_FIELDS: Readonly<Record<string, string>> = {\n type: 'typeKey',\n leadSource: 'leadSourceKey',\n}\n\n/**\n * One grid clause as the clause the Deals query asks (AGL-3516): a Type or\n * a Lead source by its key — \"none\" as `isEmpty` — and every other clause\n * as it is stored.\n */\nexport function dealQueryClause(clause: CrmViewFilterClause | ListFilterRequest): CrmClauseAsked {\n const key = DEAL_KEYED_FIELDS[clause.field]\n if (!key) return clause\n const values = crmClauseValues(clause)\n if (clause.op === 'isEmpty' || (values.length === 1 && values[0] === DEAL_PICKLIST_FILTER_NONE)) {\n return { field: key, op: 'isEmpty', value: '' }\n }\n if (values.includes(DEAL_PICKLIST_FILTER_NONE)) {\n return { refused: '\"None\" cannot be picked beside a value' }\n }\n return crmAnyOf(\n key,\n values.map((value) => crmPicklistKey(value)).filter((entry): entry is string => Boolean(entry)),\n )\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the title, `titleLower`, beside the scope clause.\n */\nexport const DEAL_PREFIX_SEARCH = {\n field: {\n column: 'title',\n kind: 'text',\n path: 'title',\n lowerPath: 'titleLower',\n presence: 'always',\n operators: ['startsWith'],\n } satisfies ListFilterField,\n notice: 'Search matches the start of a deal’s title for access limited to specific sites.',\n}\n\n/** The chosen pipeline, as the query's base beside the scope clause. */\nexport function dealPipelineBase(pipelineId: string | null): ListQueryFilter[] {\n return pipelineId ? [{ path: 'pipelineId', op: '==', value: pipelineId }] : []\n}\n\n/** The base's shape for `listQueryIndexes`: the pipeline, an equality. */\nexport const DEAL_LIST_PIPELINE_BASE_INDEX = [{ path: 'pipelineId' }] as const\n"],"names":["crmPicklistKey","CRM_NEXT_ACTIVITY_FILTER_FIELD","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","crmSelectField","LEAD_SOURCE_CODEC","LEAD_SOURCE_FILTER_NONE","DEAL_LIST_SORTS","path","direction","DEAL_PICKLIST_FILTER_NONE","DEAL_FILTER_CODECS","type","leadSource","DEAL_LIST_FILTER_FIELDS","keyField","column","kind","presence","operators","DEAL_QUERY_FIELDS","DEAL_LIST_DECLARATION","fields","sorts","search","DEAL_KEYED_FIELDS","dealQueryClause","clause","key","field","values","op","length","value","includes","refused","map","filter","entry","Boolean","DEAL_PREFIX_SEARCH","lowerPath","notice","dealPipelineBase","pipelineId","DEAL_LIST_PIPELINE_BASE_INDEX"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GASD,SAASA,cAAc,QAAkC,6BAA4B;AACrF,SAASC,8BAA8B,QAAQ,4CAAwC;AACvF,SACEC,eAAe,EACfC,QAAQ,EAERC,eAAe,EACfC,cAAc,QACT,6BAAyB;AAChC,SAASC,iBAAiB,EAAEC,uBAAuB,QAAQ,2BAAuB;AAElF;;;;;;;;;;;;;;;;;;;CAmBC,GAED,gDAAgD,GAChD,OAAO,MAAMC,kBAA4C;IACvD;QAAEC,MAAM;QAAaC,WAAW;IAAO;CACxC,CAAA;AAED;;;CAGC,GACD,OAAO,MAAMC,4BAA4BJ,wBAAuB;AAEhE;;;CAGC,GACD,OAAO,MAAMK,qBAAoE;IAC/EC,MAAMP;IACNQ,YAAYR;AACd,EAAC;AAED,gFAAgF,GAChF,OAAO,MAAMS,0BAAsD;IACjEV,eAAe;IACfJ;IACAI,eAAe;IACfA,eAAe;CAChB,CAAA;AAED,oFAAoF,GACpF,MAAMW,WAAW,CAACC,SAAqC,CAAA;QACrDA;QACAC,MAAM;QACNT,MAAMQ;QACNE,UAAU;QACVC,WAAW;YAAC;YAAU;YAAW;SAAU;IAC7C,CAAA;AAEA,qEAAqE,GACrE,OAAO,MAAMC,oBAAgD;IAC3DhB,eAAe;IACfJ;IACAe,SAAS;IACTA,SAAS;CACV,CAAA;AAED,OAAO,MAAMM,wBAA8C;IACzDC,QAAQF;IACRG,OAAOhB;IACPiB,QAAQvB;AACV,EAAC;AAED,4EAA4E,GAC5E,MAAMwB,oBAAsD;IAC1Db,MAAM;IACNC,YAAY;AACd;AAEA;;;;CAIC,GACD,OAAO,SAASa,gBAAgBC,MAA+C;IAC7E,MAAMC,MAAMH,iBAAiB,CAACE,OAAOE,KAAK,CAAC;IAC3C,IAAI,CAACD,KAAK,OAAOD;IACjB,MAAMG,SAAS3B,gBAAgBwB;IAC/B,IAAIA,OAAOI,EAAE,KAAK,aAAcD,OAAOE,MAAM,KAAK,KAAKF,MAAM,CAAC,EAAE,KAAKpB,2BAA4B;QAC/F,OAAO;YAAEmB,OAAOD;YAAKG,IAAI;YAAWE,OAAO;QAAG;IAChD;IACA,IAAIH,OAAOI,QAAQ,CAACxB,4BAA4B;QAC9C,OAAO;YAAEyB,SAAS;QAAyC;IAC7D;IACA,OAAOjC,SACL0B,KACAE,OAAOM,GAAG,CAAC,CAACH,QAAUlC,eAAekC,QAAQI,MAAM,CAAC,CAACC,QAA2BC,QAAQD;AAE5F;AAEA;;;CAGC,GACD,OAAO,MAAME,qBAAqB;IAChCX,OAAO;QACLb,QAAQ;QACRC,MAAM;QACNT,MAAM;QACNiC,WAAW;QACXvB,UAAU;QACVC,WAAW;YAAC;SAAa;IAC3B;IACAuB,QAAQ;AACV,EAAC;AAED,sEAAsE,GACtE,OAAO,SAASC,iBAAiBC,UAAyB;IACxD,OAAOA,aAAa;QAAC;YAAEpC,MAAM;YAAcuB,IAAI;YAAME,OAAOW;QAAW;KAAE,GAAG,EAAE;AAChF;AAEA,wEAAwE,GACxE,OAAO,MAAMC,gCAAgC;IAAC;QAAErC,MAAM;IAAa;CAAE,CAAS"}
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
* records band and collection sizes join `GET /v1/usage`. The API is the
|
|
29
29
|
* console's alone, so the tenant runtime does not register them.
|
|
30
30
|
*
|
|
31
|
+
* It registers the server half of the CRM's import and export resources
|
|
32
|
+
* (`transfer/register.ts`).
|
|
33
|
+
*
|
|
31
34
|
* It also clears a removed container off the CRM's records: a campaign is
|
|
32
35
|
* deleted from the console, and its owner asks every plugin's membership
|
|
33
36
|
* detacher first (`server/container-detach.ts`).
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
// registry, not the whole server surface.
|
|
18
18
|
import { registerApiV1Resource, registerApiV1UsageFigures } from "@aglyn/tenant-data-admin/server/api-v1-resources";
|
|
19
19
|
import { listPluginMembershipDetachers, registerPluginMembershipDetacher } from "@aglyn/aglyn/plugin-manager/plugin-membership-detach";
|
|
20
|
+
import { registerPluginPersonRecordsEraser } from "@aglyn/aglyn/plugin-manager/plugin-person-erasure";
|
|
21
|
+
import { registerPluginTransferResource } from "@aglyn/aglyn/plugin-manager/plugin-transfer-resources";
|
|
20
22
|
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
23
|
+
import { crmTransferPlanGate, registerCrmTransferResources } from "./transfer/register.js";
|
|
24
|
+
import { CRM_EMAIL_TEMPLATE_PACKAGE_RULES, CRM_EMAIL_TEMPLATES_TRANSFER_KEY, crmEmailTemplateDependencies, remapCrmEmailTemplateIds } from "./transfer/email-templates-package.js";
|
|
25
|
+
/** The email templates package's server half (AGL-3535), loaded when an import or export first asks. */ const templatesPackage = async ()=>(await import("./transfer/email-templates-package.server.js")).createCrmEmailTemplatesPackage();
|
|
21
26
|
/**
|
|
22
27
|
* The plan feature every CRM resource of `/v1` needs, and the sentence its
|
|
23
28
|
* refusal says (AGL-2611, AGL-2851): the whole CRM is included from
|
|
@@ -81,6 +86,9 @@ import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
|
81
86
|
* records band and collection sizes join `GET /v1/usage`. The API is the
|
|
82
87
|
* console's alone, so the tenant runtime does not register them.
|
|
83
88
|
*
|
|
89
|
+
* It registers the server half of the CRM's import and export resources
|
|
90
|
+
* (`transfer/register.ts`).
|
|
91
|
+
*
|
|
84
92
|
* It also clears a removed container off the CRM's records: a campaign is
|
|
85
93
|
* deleted from the console, and its owner asks every plugin's membership
|
|
86
94
|
* detacher first (`server/container-detach.ts`).
|
|
@@ -91,6 +99,30 @@ import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
|
91
99
|
* Registering again replaces this plugin's own entries, so a second call (a
|
|
92
100
|
* hot reload, a spec) is harmless.
|
|
93
101
|
*/ export function registerCrmConsoleServerDeclarations() {
|
|
102
|
+
// Email templates in a workspace package (AGL-3535).
|
|
103
|
+
registerPluginTransferResource(CRM_EMAIL_TEMPLATES_TRANSFER_KEY, {
|
|
104
|
+
items: async (ctx)=>(await templatesPackage()).items(ctx),
|
|
105
|
+
dependencies: (item)=>crmEmailTemplateDependencies(item),
|
|
106
|
+
remapIds: (item, idMap)=>remapCrmEmailTemplateIds(item, idMap),
|
|
107
|
+
readItems: async (ctx, ids)=>(await templatesPackage()).readItems(ctx, ids),
|
|
108
|
+
writeItems: async (ctx, items, writer)=>(await templatesPackage()).writeItems(ctx, items, writer),
|
|
109
|
+
revertItems: async (ctx, steps)=>(await templatesPackage()).revertItems(ctx, steps),
|
|
110
|
+
problems: async (ctx, write)=>(await templatesPackage()).problems(ctx, write),
|
|
111
|
+
rules: CRM_EMAIL_TEMPLATE_PACKAGE_RULES,
|
|
112
|
+
planGate: crmTransferPlanGate('email templates')
|
|
113
|
+
}, {
|
|
114
|
+
pluginId: BUNDLE_ID
|
|
115
|
+
});
|
|
116
|
+
// The record system's share of a person erasure (AGL-2623, AGL-3080): it
|
|
117
|
+
// names the person's contacts before anybody erases, and erases them, their
|
|
118
|
+
// satellites and their lead after everybody else. Required, and loaded with
|
|
119
|
+
// the first erasure.
|
|
120
|
+
registerPluginPersonRecordsEraser({
|
|
121
|
+
locate: async (target)=>(await import("./server/person-eraser.js")).crmPersonEraser().locate(target),
|
|
122
|
+
erase: async (request)=>(await import("./server/person-eraser.js")).crmPersonEraser().erase(request)
|
|
123
|
+
}, {
|
|
124
|
+
pluginId: BUNDLE_ID
|
|
125
|
+
});
|
|
94
126
|
for (const [resource, handle] of CRM_API_V1_RESOURCES){
|
|
95
127
|
registerApiV1Resource(resource, {
|
|
96
128
|
handle,
|
|
@@ -103,6 +135,9 @@ import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
|
103
135
|
registerApiV1UsageFigures(async (ctx)=>(await import("./server/api-v1/usage.js")).crmUsageFigures(ctx), {
|
|
104
136
|
pluginId: BUNDLE_ID
|
|
105
137
|
});
|
|
138
|
+
// What the CRM imports and exports (AGL-3527): each resource's module
|
|
139
|
+
// loads with the first transfer that asks for it.
|
|
140
|
+
registerCrmTransferResources(BUNDLE_ID);
|
|
106
141
|
// A removed container — a deleted campaign — comes off every lead and every
|
|
107
142
|
// contact facet naming it, before its owner removes it (AGL-3080).
|
|
108
143
|
if (!listPluginMembershipDetachers().includes(BUNDLE_ID)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry's own module, not the data layer's barrel: boot needs one\n// registry, not the whole server surface.\nimport {\n type ApiV1ResourceHandler,\n registerApiV1Resource,\n registerApiV1UsageFigures,\n} from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport {\n listPluginMembershipDetachers,\n registerPluginMembershipDetacher,\n} from '@aglyn/aglyn/plugin-manager/plugin-membership-detach'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/**\n * The plan feature every CRM resource of `/v1` needs, and the sentence its\n * refusal says (AGL-2611, AGL-2851): the whole CRM is included from\n * Starter, so a Free workspace reads and changes none of it over the API. A\n * capture still records people on every plan; working them is the CRM.\n */\nconst CRM_SUITE = {\n feature: 'crm',\n message:\n 'The CRM — contacts, leads, companies, deals, tasks, pipelines, ' +\n 'activities and email templates — is not included in this organization’s plan',\n} as const\n\n/**\n * The CRM's resources on the customer REST API, each handler loaded with the\n * first request for it. In the order the API root lists them: the people\n * first, then the records a team keeps about them, then a site's leads\n * (`/v1/leads?siteId=`, a top-level path with the site as a parameter), then\n * the letters sent from a record (AGL-2658).\n */\nconst CRM_API_V1_RESOURCES: ReadonlyArray<readonly [string, ApiV1ResourceHandler]> = [\n ['contacts', async (...args) => (await import('./server/api-v1/contacts')).handleContacts(...args)],\n ['companies', async (...args) => (await import('./server/api-v1/crm-companies')).handleCompanies(...args)],\n ['pipelines', async (...args) => (await import('./server/api-v1/crm-pipelines')).handlePipelines(...args)],\n ['deals', async (...args) => (await import('./server/api-v1/crm-deals')).handleDeals(...args)],\n ['tasks', async (...args) => (await import('./server/api-v1/crm-tasks')).handleTasks(...args)],\n ['activities', async (...args) => (await import('./server/api-v1/crm-activities')).handleActivities(...args)],\n ['leads', async (...args) => (await import('./server/api-v1/crm-leads')).handleLeads(...args)],\n [\n 'email-templates',\n async (...args) => (await import('./server/api-v1/crm-email-templates')).handleEmailTemplates(...args),\n ],\n]\n\n/**\n * The CRM's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot.\n *\n * It serves the CRM's resources of `/v1` (AGL-2606): the console's router\n * owns the pipeline in front of them — the key, the plan's API access, the\n * quota, the rate limit, the error envelope — and hands every request under\n * `/v1/contacts`, `/v1/deals` and the rest to the CRM once the key is\n * authenticated and the organization's plan carries the CRM. Each resource\n * brings its description for the API's OpenAPI document, and the CRM's\n * records band and collection sizes join `GET /v1/usage`. The API is the\n * console's alone, so the tenant runtime does not register them.\n *\n * It also clears a removed container off the CRM's records: a campaign is\n * deleted from the console, and its owner asks every plugin's membership\n * detacher first (`server/container-detach.ts`).\n *\n * Light at boot: each resource's module is imported with its first request,\n * the descriptions when the document is first built, and the usage reader\n * with the first usage call.\n * Registering again replaces this plugin's own entries, so a second call (a\n * hot reload, a spec) is harmless.\n */\nexport function registerCrmConsoleServerDeclarations(): void {\n for (const [resource, handle] of CRM_API_V1_RESOURCES) {\n registerApiV1Resource(\n resource,\n {\n handle,\n entitlement: CRM_SUITE,\n describe: async () =>\n (await import('./server/api-v1/openapi')).CRM_API_V1_DESCRIPTIONS[resource],\n },\n { pluginId: BUNDLE_ID },\n )\n }\n registerApiV1UsageFigures(\n async (ctx) => (await import('./server/api-v1/usage')).crmUsageFigures(ctx),\n { pluginId: BUNDLE_ID },\n )\n // A removed container — a deleted campaign — comes off every lead and every\n // contact facet naming it, before its owner removes it (AGL-3080).\n if (!listPluginMembershipDetachers().includes(BUNDLE_ID)) {\n registerPluginMembershipDetacher(\n async (request) => (await import('./server/container-detach')).crmContainerDetacher(request),\n { pluginId: BUNDLE_ID },\n )\n }\n}\n"],"names":["registerApiV1Resource","registerApiV1UsageFigures","listPluginMembershipDetachers","registerPluginMembershipDetacher","BUNDLE_ID","CRM_SUITE","feature","message","CRM_API_V1_RESOURCES","args","handleContacts","handleCompanies","handlePipelines","handleDeals","handleTasks","handleActivities","handleLeads","handleEmailTemplates","registerCrmConsoleServerDeclarations","resource","handle","entitlement","describe","CRM_API_V1_DESCRIPTIONS","pluginId","ctx","crmUsageFigures","includes","request","crmContainerDetacher"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,0CAA0C;AAC1C,SAEEA,qBAAqB,EACrBC,yBAAyB,QACpB,mDAAkD;AACzD,SACEC,6BAA6B,EAC7BC,gCAAgC,QAC3B,uDAAsD;AAC7D,SAASC,SAAS,QAAQ,+BAA2B;AAErD;;;;;CAKC,GACD,MAAMC,YAAY;IAChBC,SAAS;IACTC,SACE,oEACA;AACJ;AAEA;;;;;;CAMC,GACD,MAAMC,uBAA+E;IACnF;QAAC;QAAY,OAAO,GAAGC,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,8BAA0B,EAAGC,cAAc,IAAID;KAAM;IACnG;QAAC;QAAa,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGE,eAAe,IAAIF;KAAM;IAC1G;QAAC;QAAa,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGG,eAAe,IAAIH;KAAM;IAC1G;QAAC;QAAS,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGI,WAAW,IAAIJ;KAAM;IAC9F;QAAC;QAAS,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGK,WAAW,IAAIL;KAAM;IAC9F;QAAC;QAAc,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,oCAAgC,EAAGM,gBAAgB,IAAIN;KAAM;IAC7G;QAAC;QAAS,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGO,WAAW,IAAIP;KAAM;IAC9F;QACE;QACA,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGQ,oBAAoB,IAAIR;KAClG;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GACD,OAAO,SAASS;IACd,KAAK,MAAM,CAACC,UAAUC,OAAO,IAAIZ,qBAAsB;QACrDR,sBACEmB,UACA;YACEC;YACAC,aAAahB;YACbiB,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,uBAAuB,CAACJ,SAAS;QAC/E,GACA;YAAEK,UAAUpB;QAAU;IAE1B;IACAH,0BACE,OAAOwB,MAAQ,AAAC,CAAA,MAAM,MAAM,CAAC,2BAAuB,EAAGC,eAAe,CAACD,MACvE;QAAED,UAAUpB;IAAU;IAExB,4EAA4E;IAC5E,mEAAmE;IACnE,IAAI,CAACF,gCAAgCyB,QAAQ,CAACvB,YAAY;QACxDD,iCACE,OAAOyB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGC,oBAAoB,CAACD,UACpF;YAAEJ,UAAUpB;QAAU;IAE1B;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry's own module, not the data layer's barrel: boot needs one\n// registry, not the whole server surface.\nimport {\n type ApiV1ResourceHandler,\n registerApiV1Resource,\n registerApiV1UsageFigures,\n} from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport {\n listPluginMembershipDetachers,\n registerPluginMembershipDetacher,\n} from '@aglyn/aglyn/plugin-manager/plugin-membership-detach'\nimport { registerPluginPersonRecordsEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { registerPluginTransferResource } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { crmTransferPlanGate, registerCrmTransferResources } from './transfer/register'\nimport {\n CRM_EMAIL_TEMPLATE_PACKAGE_RULES,\n CRM_EMAIL_TEMPLATES_TRANSFER_KEY,\n crmEmailTemplateDependencies,\n remapCrmEmailTemplateIds,\n type CrmEmailTemplatePackageContent,\n} from './transfer/email-templates-package'\n\n/** The email templates package's server half (AGL-3535), loaded when an import or export first asks. */\nconst templatesPackage = async () =>\n (await import('./transfer/email-templates-package.server')).createCrmEmailTemplatesPackage()\n\n/**\n * The plan feature every CRM resource of `/v1` needs, and the sentence its\n * refusal says (AGL-2611, AGL-2851): the whole CRM is included from\n * Starter, so a Free workspace reads and changes none of it over the API. A\n * capture still records people on every plan; working them is the CRM.\n */\nconst CRM_SUITE = {\n feature: 'crm',\n message:\n 'The CRM — contacts, leads, companies, deals, tasks, pipelines, ' +\n 'activities and email templates — is not included in this organization’s plan',\n} as const\n\n/**\n * The CRM's resources on the customer REST API, each handler loaded with the\n * first request for it. In the order the API root lists them: the people\n * first, then the records a team keeps about them, then a site's leads\n * (`/v1/leads?siteId=`, a top-level path with the site as a parameter), then\n * the letters sent from a record (AGL-2658).\n */\nconst CRM_API_V1_RESOURCES: ReadonlyArray<readonly [string, ApiV1ResourceHandler]> = [\n ['contacts', async (...args) => (await import('./server/api-v1/contacts')).handleContacts(...args)],\n ['companies', async (...args) => (await import('./server/api-v1/crm-companies')).handleCompanies(...args)],\n ['pipelines', async (...args) => (await import('./server/api-v1/crm-pipelines')).handlePipelines(...args)],\n ['deals', async (...args) => (await import('./server/api-v1/crm-deals')).handleDeals(...args)],\n ['tasks', async (...args) => (await import('./server/api-v1/crm-tasks')).handleTasks(...args)],\n ['activities', async (...args) => (await import('./server/api-v1/crm-activities')).handleActivities(...args)],\n ['leads', async (...args) => (await import('./server/api-v1/crm-leads')).handleLeads(...args)],\n [\n 'email-templates',\n async (...args) => (await import('./server/api-v1/crm-email-templates')).handleEmailTemplates(...args),\n ],\n]\n\n/**\n * The CRM's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot.\n *\n * It serves the CRM's resources of `/v1` (AGL-2606): the console's router\n * owns the pipeline in front of them — the key, the plan's API access, the\n * quota, the rate limit, the error envelope — and hands every request under\n * `/v1/contacts`, `/v1/deals` and the rest to the CRM once the key is\n * authenticated and the organization's plan carries the CRM. Each resource\n * brings its description for the API's OpenAPI document, and the CRM's\n * records band and collection sizes join `GET /v1/usage`. The API is the\n * console's alone, so the tenant runtime does not register them.\n *\n * It registers the server half of the CRM's import and export resources\n * (`transfer/register.ts`).\n *\n * It also clears a removed container off the CRM's records: a campaign is\n * deleted from the console, and its owner asks every plugin's membership\n * detacher first (`server/container-detach.ts`).\n *\n * Light at boot: each resource's module is imported with its first request,\n * the descriptions when the document is first built, and the usage reader\n * with the first usage call.\n * Registering again replaces this plugin's own entries, so a second call (a\n * hot reload, a spec) is harmless.\n */\nexport function registerCrmConsoleServerDeclarations(): void {\n // Email templates in a workspace package (AGL-3535).\n registerPluginTransferResource(\n CRM_EMAIL_TEMPLATES_TRANSFER_KEY,\n {\n items: async (ctx) => (await templatesPackage()).items(ctx),\n dependencies: (item) => crmEmailTemplateDependencies(item as CrmEmailTemplatePackageContent),\n remapIds: (item, idMap) => remapCrmEmailTemplateIds(item as CrmEmailTemplatePackageContent, idMap),\n readItems: async (ctx, ids) => (await templatesPackage()).readItems(ctx, ids),\n writeItems: async (ctx, items, writer) => (await templatesPackage()).writeItems(ctx, items as never, writer),\n revertItems: async (ctx, steps) => (await templatesPackage()).revertItems(ctx, steps as never),\n problems: async (ctx, write) => (await templatesPackage()).problems(ctx, write as never),\n rules: CRM_EMAIL_TEMPLATE_PACKAGE_RULES,\n planGate: crmTransferPlanGate('email templates'),\n },\n { pluginId: BUNDLE_ID },\n )\n // The record system's share of a person erasure (AGL-2623, AGL-3080): it\n // names the person's contacts before anybody erases, and erases them, their\n // satellites and their lead after everybody else. Required, and loaded with\n // the first erasure.\n registerPluginPersonRecordsEraser(\n {\n locate: async (target) => (await import('./server/person-eraser')).crmPersonEraser().locate(target),\n erase: async (request) => (await import('./server/person-eraser')).crmPersonEraser().erase(request),\n },\n { pluginId: BUNDLE_ID },\n )\n for (const [resource, handle] of CRM_API_V1_RESOURCES) {\n registerApiV1Resource(\n resource,\n {\n handle,\n entitlement: CRM_SUITE,\n describe: async () =>\n (await import('./server/api-v1/openapi')).CRM_API_V1_DESCRIPTIONS[resource],\n },\n { pluginId: BUNDLE_ID },\n )\n }\n registerApiV1UsageFigures(\n async (ctx) => (await import('./server/api-v1/usage')).crmUsageFigures(ctx),\n { pluginId: BUNDLE_ID },\n )\n // What the CRM imports and exports (AGL-3527): each resource's module\n // loads with the first transfer that asks for it.\n registerCrmTransferResources(BUNDLE_ID)\n // A removed container — a deleted campaign — comes off every lead and every\n // contact facet naming it, before its owner removes it (AGL-3080).\n if (!listPluginMembershipDetachers().includes(BUNDLE_ID)) {\n registerPluginMembershipDetacher(\n async (request) => (await import('./server/container-detach')).crmContainerDetacher(request),\n { pluginId: BUNDLE_ID },\n )\n }\n}\n"],"names":["registerApiV1Resource","registerApiV1UsageFigures","listPluginMembershipDetachers","registerPluginMembershipDetacher","registerPluginPersonRecordsEraser","registerPluginTransferResource","BUNDLE_ID","crmTransferPlanGate","registerCrmTransferResources","CRM_EMAIL_TEMPLATE_PACKAGE_RULES","CRM_EMAIL_TEMPLATES_TRANSFER_KEY","crmEmailTemplateDependencies","remapCrmEmailTemplateIds","templatesPackage","createCrmEmailTemplatesPackage","CRM_SUITE","feature","message","CRM_API_V1_RESOURCES","args","handleContacts","handleCompanies","handlePipelines","handleDeals","handleTasks","handleActivities","handleLeads","handleEmailTemplates","registerCrmConsoleServerDeclarations","items","ctx","dependencies","item","remapIds","idMap","readItems","ids","writeItems","writer","revertItems","steps","problems","write","rules","planGate","pluginId","locate","target","crmPersonEraser","erase","request","resource","handle","entitlement","describe","CRM_API_V1_DESCRIPTIONS","crmUsageFigures","includes","crmContainerDetacher"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The CRM's DECLARATIONS: what another surface reads of this plugin before any
|
|
19
|
+
* of its own surfaces load, in both apps and in the console's browser.
|
|
20
|
+
*
|
|
21
|
+
* Its recipes (AGL-2626), so the automation editor's Recipes menu offers
|
|
22
|
+
* "Welcome a new lead" on a site whose CRM pages were never opened, and the
|
|
23
|
+
* automation it builds names the recipe it began as. Light by construction:
|
|
24
|
+
* the recipes are definitions, and nothing here reaches Firestore or React.
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerCrmDeclarations(): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
*/ // The seam from its own module, not the plugin-manager barrel: both apps load
|
|
17
|
+
// this at boot, and the barrel reaches the client contexts.
|
|
18
|
+
import { registerInteractionRecipes } from "@aglyn/aglyn/plugin-manager/interaction-recipes";
|
|
19
|
+
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
20
|
+
import { CRM_ACTION_RECIPES } from "./model/crm-recipes.js";
|
|
21
|
+
/**
|
|
22
|
+
* The CRM's DECLARATIONS: what another surface reads of this plugin before any
|
|
23
|
+
* of its own surfaces load, in both apps and in the console's browser.
|
|
24
|
+
*
|
|
25
|
+
* Its recipes (AGL-2626), so the automation editor's Recipes menu offers
|
|
26
|
+
* "Welcome a new lead" on a site whose CRM pages were never opened, and the
|
|
27
|
+
* automation it builds names the recipe it began as. Light by construction:
|
|
28
|
+
* the recipes are definitions, and nothing here reaches Firestore or React.
|
|
29
|
+
*/ export function registerCrmDeclarations() {
|
|
30
|
+
registerInteractionRecipes(CRM_ACTION_RECIPES, {
|
|
31
|
+
pluginId: BUNDLE_ID
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=declarations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The seam from its own module, not the plugin-manager barrel: both apps load\n// this at boot, and the barrel reaches the client contexts.\nimport { registerInteractionRecipes } from '@aglyn/aglyn/plugin-manager/interaction-recipes'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { CRM_ACTION_RECIPES } from './model/crm-recipes'\n\n/**\n * The CRM's DECLARATIONS: what another surface reads of this plugin before any\n * of its own surfaces load, in both apps and in the console's browser.\n *\n * Its recipes (AGL-2626), so the automation editor's Recipes menu offers\n * \"Welcome a new lead\" on a site whose CRM pages were never opened, and the\n * automation it builds names the recipe it began as. Light by construction:\n * the recipes are definitions, and nothing here reaches Firestore or React.\n */\nexport function registerCrmDeclarations(): void {\n registerInteractionRecipes(CRM_ACTION_RECIPES, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerInteractionRecipes","BUNDLE_ID","CRM_ACTION_RECIPES","registerCrmDeclarations","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,4DAA4D;AAC5D,SAASA,0BAA0B,QAAQ,kDAAiD;AAC5F,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,kBAAkB,QAAQ,yBAAqB;AAExD;;;;;;;;CAQC,GACD,OAAO,SAASC;IACdH,2BAA2BE,oBAAoB;QAAEE,UAAUH;IAAU;AACvE"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import { type PluginContactCaptureWriter } from '@aglyn/aglyn/plugin-manager/plugin-contact-capture';
|
|
18
|
+
import { type PluginPersonRecords } from '@aglyn/aglyn/plugin-manager/plugin-person-records';
|
|
18
19
|
import { type PluginRecordIndex } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
|
|
19
20
|
import { type PluginRecordTimelineWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline';
|
|
20
21
|
/**
|
|
@@ -27,11 +28,21 @@ import { type PluginRecordTimelineWriter } from '@aglyn/aglyn/plugin-manager/plu
|
|
|
27
28
|
* including ones that will never touch the CRM.
|
|
28
29
|
*/
|
|
29
30
|
export declare const crmContactCaptureWriter: PluginContactCaptureWriter;
|
|
31
|
+
/**
|
|
32
|
+
* The CRM as the plugin other plugins ask for people (AGL-3080), deferred
|
|
33
|
+
* like the capture: `server/person-records.ts` and the Admin SDK it brings
|
|
34
|
+
* load with the first question, not when the process starts.
|
|
35
|
+
*/
|
|
36
|
+
export declare const crmPersonRecordsService: PluginPersonRecords;
|
|
30
37
|
/**
|
|
31
38
|
* The `pipeline` record index, its reads loaded with the first one — see
|
|
32
39
|
* `server/pipeline-record-index.ts` for what it answers.
|
|
33
40
|
*/
|
|
34
41
|
export declare const crmPipelineRecordIndex: PluginRecordIndex;
|
|
42
|
+
/** The companies a person works for, as another plugin reads them (AGL-3080). */
|
|
43
|
+
export declare const crmCompanyRecordIndex: PluginRecordIndex;
|
|
44
|
+
/** The email templates and snippets reps write, as another plugin reads them (AGL-3080). */
|
|
45
|
+
export declare const crmMessageTemplateRecordIndex: PluginRecordIndex;
|
|
35
46
|
/**
|
|
36
47
|
* The CRM's writer on the record-timeline seam (AGL-2981), deferred: the
|
|
37
48
|
* writer and the Admin SDK it brings load with the first entry filed.
|