@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
|
@@ -21,7 +21,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
21
21
|
* Kept out of the component so the translation can be tested without a
|
|
22
22
|
* Firestore or a DOM, and so the contact card and the company card, which
|
|
23
23
|
* open the same drawer with a link preselected, build a deal the same way.
|
|
24
|
-
*/ import { contactDisplayName, nameSearchKey, nameSearchToken } from "@aglyn/aglyn";
|
|
24
|
+
*/ import { contactDisplayName, crmPicklistDefaultLabel, DEAL_NEXT_STEP_MAX, dealContactRolesOf, dealContactRolesWithPrimary, dealStageForecastCategory, isCrmForecastCategory, judgeCrmPicklistValue, nameSearchKey, nameSearchToken, readDealProbability } from "@aglyn/aglyn";
|
|
25
25
|
import { amountInputValue, dateInputMs, dateInputValue, DEFAULT_DEAL_CURRENCY, openStages, parseAmountInput } from "./deal-board-model.js";
|
|
26
26
|
export const DEAL_TITLE_MAX = 120;
|
|
27
27
|
export const DEAL_NOTES_MAX = 4000;
|
|
@@ -47,11 +47,17 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
47
47
|
contactName: '',
|
|
48
48
|
companyId: '',
|
|
49
49
|
companyName: '',
|
|
50
|
-
notes: ''
|
|
50
|
+
notes: '',
|
|
51
|
+
type: '',
|
|
52
|
+
leadSource: '',
|
|
53
|
+
nextStep: '',
|
|
54
|
+
probability: '',
|
|
55
|
+
forecastCategory: '',
|
|
56
|
+
campaignId: ''
|
|
51
57
|
}, defaults);
|
|
52
58
|
}
|
|
53
59
|
/** The form an existing deal opens as. */ export function dealFormFromDoc(deal) {
|
|
54
|
-
var _deal_title, _deal_pipelineId, _deal_stageId, _deal_ownerUid, _deal_contactId, _deal_contactName, _deal_companyId, _deal_companyName, _deal_notes;
|
|
60
|
+
var _deal_title, _deal_pipelineId, _deal_stageId, _deal_ownerUid, _deal_contactId, _deal_contactName, _deal_companyId, _deal_companyName, _deal_notes, _deal_type, _deal_leadSource, _deal_nextStep, _deal_campaignId;
|
|
55
61
|
return {
|
|
56
62
|
title: String((_deal_title = deal.title) != null ? _deal_title : ''),
|
|
57
63
|
pipelineId: String((_deal_pipelineId = deal.pipelineId) != null ? _deal_pipelineId : ''),
|
|
@@ -64,7 +70,13 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
64
70
|
contactName: String((_deal_contactName = deal.contactName) != null ? _deal_contactName : ''),
|
|
65
71
|
companyId: String((_deal_companyId = deal.companyId) != null ? _deal_companyId : ''),
|
|
66
72
|
companyName: String((_deal_companyName = deal.companyName) != null ? _deal_companyName : ''),
|
|
67
|
-
notes: String((_deal_notes = deal.notes) != null ? _deal_notes : '')
|
|
73
|
+
notes: String((_deal_notes = deal.notes) != null ? _deal_notes : ''),
|
|
74
|
+
type: String((_deal_type = deal.type) != null ? _deal_type : ''),
|
|
75
|
+
leadSource: String((_deal_leadSource = deal.leadSource) != null ? _deal_leadSource : ''),
|
|
76
|
+
nextStep: String((_deal_nextStep = deal.nextStep) != null ? _deal_nextStep : ''),
|
|
77
|
+
probability: typeof deal.probability === 'number' ? String(deal.probability) : '',
|
|
78
|
+
forecastCategory: isCrmForecastCategory(deal.forecastCategory) ? deal.forecastCategory : '',
|
|
79
|
+
campaignId: String((_deal_campaignId = deal.campaignId) != null ? _deal_campaignId : '')
|
|
68
80
|
};
|
|
69
81
|
}
|
|
70
82
|
/**
|
|
@@ -84,11 +96,41 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
84
96
|
if (values.expectedClose.trim() && dateInputMs(values.expectedClose) === null) {
|
|
85
97
|
return 'The expected close has to be a date.';
|
|
86
98
|
}
|
|
99
|
+
if (readDealProbability(values.probability) === undefined) {
|
|
100
|
+
return 'The probability has to be a whole number from 0 to 100.';
|
|
101
|
+
}
|
|
87
102
|
if (mode === 'create' && (!values.pipelineId || !values.stageId)) {
|
|
88
103
|
return 'Pick the stage the deal starts in.';
|
|
89
104
|
}
|
|
90
105
|
return null;
|
|
91
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The form's two picklist fields judged against the org's lists (AGL-3516),
|
|
109
|
+
* the way every server door judges them: an active value is stored as the
|
|
110
|
+
* list spells it, a blank clears, the deal's own current value is kept, and
|
|
111
|
+
* anything else is refused naming what the list allows. A NEW deal with no
|
|
112
|
+
* Type takes the list's default, as Salesforce's does; the lead source is
|
|
113
|
+
* never defaulted — it records where a deal came from.
|
|
114
|
+
*/ export function judgeDealFormPicklists(values, lists, options) {
|
|
115
|
+
var _crmPicklistDefaultLabel, _type_value, _leadSource_value;
|
|
116
|
+
var _options_current, _options_current1;
|
|
117
|
+
const typed = values.type.trim() || (options.created ? (_crmPicklistDefaultLabel = crmPicklistDefaultLabel(lists.type)) != null ? _crmPicklistDefaultLabel : '' : '');
|
|
118
|
+
const type = judgeCrmPicklistValue('opportunityType', lists.type, typed, (_options_current = options.current) == null ? void 0 : _options_current.type);
|
|
119
|
+
if (type.ok === false) return type;
|
|
120
|
+
const leadSource = judgeCrmPicklistValue('leadSource', lists.leadSource, values.leadSource, (_options_current1 = options.current) == null ? void 0 : _options_current1.leadSource);
|
|
121
|
+
if (leadSource.ok === false) return leadSource;
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
values: _extends({}, values, {
|
|
125
|
+
type: (_type_value = type.value) != null ? _type_value : '',
|
|
126
|
+
leadSource: (_leadSource_value = leadSource.value) != null ? _leadSource_value : ''
|
|
127
|
+
})
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** The forecast category a save stamps: the form's, else the stage's, else none. */ function forecastCategoryFor(values, stage) {
|
|
131
|
+
if (isCrmForecastCategory(values.forecastCategory)) return values.forecastCategory;
|
|
132
|
+
return stage ? dealStageForecastCategory(stage) : null;
|
|
133
|
+
}
|
|
92
134
|
/**
|
|
93
135
|
* The optional fields, as the values to SET and the keys to CLEAR.
|
|
94
136
|
*
|
|
@@ -99,6 +141,7 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
99
141
|
* either. On a create the cleared keys are simply not written; on an edit
|
|
100
142
|
* the component turns them into field deletes.
|
|
101
143
|
*/ function optionalFields(values) {
|
|
144
|
+
var _readDealProbability;
|
|
102
145
|
const set = {};
|
|
103
146
|
const clear = [];
|
|
104
147
|
const put = (key, value)=>{
|
|
@@ -113,6 +156,11 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
113
156
|
put('companyId', values.companyId.trim());
|
|
114
157
|
put('companyName', values.companyId.trim() ? values.companyName.trim() : '');
|
|
115
158
|
put('notes', values.notes.trim().slice(0, DEAL_NOTES_MAX));
|
|
159
|
+
put('type', values.type.trim());
|
|
160
|
+
put('leadSource', values.leadSource.trim());
|
|
161
|
+
put('nextStep', values.nextStep.trim().replace(/\s+/g, ' ').slice(0, DEAL_NEXT_STEP_MAX));
|
|
162
|
+
put('probability', (_readDealProbability = readDealProbability(values.probability)) != null ? _readDealProbability : null);
|
|
163
|
+
put('campaignId', values.campaignId.trim());
|
|
116
164
|
return {
|
|
117
165
|
set,
|
|
118
166
|
clear
|
|
@@ -124,15 +172,27 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
124
172
|
*/ export function dealDocumentFromForm(values, context) {
|
|
125
173
|
const title = values.title.trim().slice(0, DEAL_TITLE_MAX);
|
|
126
174
|
const { set } = optionalFields(values);
|
|
175
|
+
const forecastCategory = forecastCategoryFor(values, context.stage);
|
|
176
|
+
const contactId = values.contactId.trim();
|
|
127
177
|
return _extends({
|
|
128
178
|
title,
|
|
129
179
|
titleLower: nameSearchKey(title),
|
|
130
180
|
pipelineId: values.pipelineId,
|
|
131
181
|
stageId: values.stageId,
|
|
132
|
-
status: 'open'
|
|
182
|
+
status: 'open'
|
|
183
|
+
}, contactId ? {
|
|
184
|
+
contactRoles: [
|
|
185
|
+
{
|
|
186
|
+
contactId,
|
|
187
|
+
primary: true
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
} : {}, {
|
|
133
191
|
currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),
|
|
134
192
|
stageChangedAtMs: context.nowMs
|
|
135
|
-
},
|
|
193
|
+
}, forecastCategory ? {
|
|
194
|
+
forecastCategory
|
|
195
|
+
} : {}, set, {
|
|
136
196
|
visibleTo: context.visibleTo,
|
|
137
197
|
hostId: context.hostId,
|
|
138
198
|
createdByUid: context.uid,
|
|
@@ -151,8 +211,20 @@ export const DEAL_NOTES_MAX = 4000;
|
|
|
151
211
|
* form shows them read-only, and a patch that re-wrote the number it was
|
|
152
212
|
* seeded with would overwrite a line added from another tab meanwhile.
|
|
153
213
|
*/ export function dealPatchFromForm(values, nowMs, options = {}) {
|
|
214
|
+
var _options_current_contactId;
|
|
154
215
|
const title = values.title.trim().slice(0, DEAL_TITLE_MAX);
|
|
155
|
-
const
|
|
216
|
+
const optional = optionalFields(values);
|
|
217
|
+
const set = optional.set;
|
|
218
|
+
const { clear } = optional;
|
|
219
|
+
const contactId = values.contactId.trim();
|
|
220
|
+
if (options.current && contactId !== ((_options_current_contactId = options.current.contactId) != null ? _options_current_contactId : '')) {
|
|
221
|
+
set['contactRoles'] = dealContactRolesWithPrimary(dealContactRolesOf(options.current), contactId || null);
|
|
222
|
+
}
|
|
223
|
+
// The category picked, else the stage's again; with neither, the field
|
|
224
|
+
// goes and every reader derives it from the stage.
|
|
225
|
+
const forecastCategory = forecastCategoryFor(values, options.stage);
|
|
226
|
+
if (forecastCategory) set['forecastCategory'] = forecastCategory;
|
|
227
|
+
else clear.push('forecastCategory');
|
|
156
228
|
if (options.amountDerived) {
|
|
157
229
|
delete set['amountCents'];
|
|
158
230
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/deal-form-model.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The deal drawer's values and the documents they become (AGL-2598), as pure\n * functions — the drawer holds strings the way inputs do, and this module is\n * the one place those strings turn into the typed fields a `CrmDeal` stores.\n * Kept out of the component so the translation can be tested without a\n * Firestore or a DOM, and so the contact card and the company card, which\n * open the same drawer with a link preselected, build a deal the same way.\n */\n\nimport {\n contactDisplayName,\n type CrmDeal,\n nameSearchKey,\n nameSearchToken,\n} from '@aglyn/aglyn'\nimport {\n amountInputValue,\n dateInputMs,\n dateInputValue,\n DEFAULT_DEAL_CURRENCY,\n type DealDoc,\n openStages,\n parseAmountInput,\n type PipelineDoc,\n} from './deal-board-model'\n\n/** What the drawer edits — strings as the inputs hold them. */\nexport interface DealFormValues {\n title: string\n pipelineId: string\n stageId: string\n /** As typed: `1,250.00`; parsed at save. */\n amount: string\n currency: string\n /** `YYYY-MM-DD` from a date input, or empty. */\n expectedClose: string\n ownerUid: string\n contactId: string\n contactName: string\n companyId: string\n companyName: string\n notes: string\n}\n\nexport const DEAL_TITLE_MAX = 120\nexport const DEAL_NOTES_MAX = 4000\n\n/**\n * A blank form aimed at a pipeline's first open stage.\n *\n * `defaults` is how a contact's page or a company's page starts a deal\n * already linked to itself: the caller passes the ids and names it knows and\n * the drawer opens with those fields filled. Anything it does not pass stays\n * blank rather than being guessed.\n */\nexport function emptyDealForm(\n pipeline: Pick<PipelineDoc, '$id' | 'stages'> | null | undefined,\n defaults: Partial<DealFormValues> = {},\n): DealFormValues {\n const firstOpen = openStages(pipeline)[0]\n return {\n title: '',\n pipelineId: pipeline?.$id ?? '',\n stageId: firstOpen?.id ?? '',\n amount: '',\n currency: DEFAULT_DEAL_CURRENCY,\n expectedClose: '',\n ownerUid: '',\n contactId: '',\n contactName: '',\n companyId: '',\n companyName: '',\n notes: '',\n ...defaults,\n }\n}\n\n/** The form an existing deal opens as. */\nexport function dealFormFromDoc(deal: DealDoc): DealFormValues {\n return {\n title: String(deal.title ?? ''),\n pipelineId: String(deal.pipelineId ?? ''),\n stageId: String(deal.stageId ?? ''),\n amount: amountInputValue(deal.amountCents),\n currency: String(deal.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n expectedClose: dateInputValue(deal.expectedCloseAtMs),\n ownerUid: String(deal.ownerUid ?? ''),\n contactId: String(deal.contactId ?? ''),\n contactName: String(deal.contactName ?? ''),\n companyId: String(deal.companyId ?? ''),\n companyName: String(deal.companyName ?? ''),\n notes: String(deal.notes ?? ''),\n }\n}\n\n/**\n * Why the form cannot be saved, or `null` when it can.\n *\n * A title is the one thing a deal needs — a card with no title is a card\n * nobody can find on the board. The amount is optional but, if typed, has to\n * parse: a deal saved with an amount the merchant typed and the model dropped\n * would forecast at zero without saying so. The stage is only checked on a\n * new deal, because an existing deal's stage is moved by the stage route and\n * is not this form's to write.\n */\nexport function dealFormProblem(\n values: DealFormValues,\n mode: 'create' | 'edit',\n): string | null {\n if (!values.title.trim()) return 'A deal needs a title.'\n if (values.amount.trim() && parseAmountInput(values.amount) === null) {\n return 'The amount has to be a number of zero or more.'\n }\n if (values.expectedClose.trim() && dateInputMs(values.expectedClose) === null) {\n return 'The expected close has to be a date.'\n }\n if (mode === 'create' && (!values.pipelineId || !values.stageId)) {\n return 'Pick the stage the deal starts in.'\n }\n return null\n}\n\n/** What every deal write stamps besides the fields the form edits. */\nexport interface DealWriteContext {\n visibleTo: string[]\n hostId: string\n uid: string\n nowMs: number\n}\n\n/**\n * The optional fields, as the values to SET and the keys to CLEAR.\n *\n * Split rather than written as `null` because a deal's optional ids are\n * typed as absent-or-string, and the indexes on `contactId` and `companyId`\n * are what the contact and company pages query — a `null` there would be a\n * row those pages cannot see and a filter on \"no contact\" could not find\n * either. On a create the cleared keys are simply not written; on an edit\n * the component turns them into field deletes.\n */\nfunction optionalFields(values: DealFormValues): {\n set: Record<string, string | number>\n clear: string[]\n} {\n const set: Record<string, string | number> = {}\n const clear: string[] = []\n const put = (key: string, value: string | number | null) => {\n if (value === null || value === '') clear.push(key)\n else set[key] = value\n }\n put('amountCents', parseAmountInput(values.amount))\n put('expectedCloseAtMs', dateInputMs(values.expectedClose))\n put('ownerUid', values.ownerUid.trim())\n put('contactId', values.contactId.trim())\n put('contactName', values.contactId.trim() ? values.contactName.trim() : '')\n put('companyId', values.companyId.trim())\n put('companyName', values.companyId.trim() ? values.companyName.trim() : '')\n put('notes', values.notes.trim().slice(0, DEAL_NOTES_MAX))\n return { set, clear }\n}\n\n/**\n * The document a new deal is created as. Open, in the chosen stage, with\n * the stage clock started now so \"days in stage\" counts from creation.\n */\nexport function dealDocumentFromForm(\n values: DealFormValues,\n context: DealWriteContext,\n): Record<string, unknown> {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const { set } = optionalFields(values)\n return {\n title,\n titleLower: nameSearchKey(title),\n pipelineId: values.pipelineId,\n stageId: values.stageId,\n status: 'open' satisfies CrmDeal['status'],\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n stageChangedAtMs: context.nowMs,\n ...set,\n visibleTo: context.visibleTo,\n hostId: context.hostId,\n createdByUid: context.uid,\n createdAt: new Date(context.nowMs),\n updatedAt: new Date(context.nowMs),\n }\n}\n\n/**\n * The patch an edit writes. Never the stage, the status or the scope: the\n * stage goes through the server route so the automations hear it, and the\n * scope is changed only by an org-wide member on purpose.\n *\n * Nor the amount or the currency of a deal whose amount is DERIVED\n * (`amountDerived`, AGL-2620): with line items on the deal the amount is\n * their sum and the currency is theirs, both owned by the products card. The\n * form shows them read-only, and a patch that re-wrote the number it was\n * seeded with would overwrite a line added from another tab meanwhile.\n */\nexport function dealPatchFromForm(\n values: DealFormValues,\n nowMs: number,\n options: { amountDerived?: boolean } = {},\n): { set: Record<string, unknown>; clear: string[] } {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const { set, clear } = optionalFields(values)\n if (options.amountDerived) {\n delete set['amountCents']\n return {\n set: { title, titleLower: nameSearchKey(title), ...set, updatedAt: new Date(nowMs) },\n clear: clear.filter((key) => key !== 'amountCents'),\n }\n }\n return {\n set: {\n title,\n titleLower: nameSearchKey(title),\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n ...set,\n updatedAt: new Date(nowMs),\n },\n clear,\n }\n}\n\n/** A contact as the picker offers it. */\nexport interface ContactChoice {\n id: string\n name: string\n email: string\n}\n\n/**\n * The contacts a typed query matches, from a window of recent contact rows,\n * in the list's own grammar.\n *\n * In memory rather than by query because the contacts collection has no\n * composite index over `visibleTo` with `nameLower` or `email`, and a\n * scoped list can carry only one array clause — so the picker reads the\n * newest rows the viewer may see (bounded, ordered) and matches here. The\n * match is what the list's search does: the normalized query as a prefix of\n * the email, or as a prefix of any word of the name (`nameTokens` holds\n * every word prefix a write produced; `nameLower` covers rows written before\n * the tokens existed). The holder's own name for the person wins over the\n * canonical one, as everywhere else.\n */\nexport function contactChoicesFor(\n queryText: string,\n rows: readonly Record<string, unknown>[],\n /**\n * The holder each row is named through: one group for every row under a\n * site, or a resolver per row at the organization level (AGL-2630), where\n * each contact reads through its own primary holder.\n */\n groupId: string | ((row: Record<string, unknown>) => string),\n max = 8,\n): ContactChoice[] {\n const key = nameSearchKey(queryText)\n const token = nameSearchToken(queryText)\n const choices: ContactChoice[] = []\n for (const row of rows) {\n const email = String(row['email'] ?? '').toLowerCase()\n const name = contactDisplayName(\n row,\n typeof groupId === 'function' ? groupId(row) : groupId,\n )\n const nameLower = nameSearchKey(name)\n const tokens = Array.isArray(row['nameTokens'])\n ? (row['nameTokens'] as unknown[]).map(String)\n : []\n const matches =\n !key ||\n email.startsWith(key) ||\n nameLower.startsWith(key) ||\n nameLower.includes(` ${key}`) ||\n (token ? tokens.includes(token) : false)\n if (!matches) continue\n choices.push({ id: String(row['$id'] ?? ''), name, email })\n if (choices.length >= max) break\n }\n return choices\n}\n"],"names":["contactDisplayName","nameSearchKey","nameSearchToken","amountInputValue","dateInputMs","dateInputValue","DEFAULT_DEAL_CURRENCY","openStages","parseAmountInput","DEAL_TITLE_MAX","DEAL_NOTES_MAX","emptyDealForm","pipeline","defaults","firstOpen","title","pipelineId","$id","stageId","id","amount","currency","expectedClose","ownerUid","contactId","contactName","companyId","companyName","notes","dealFormFromDoc","deal","String","amountCents","toLowerCase","expectedCloseAtMs","dealFormProblem","values","mode","trim","optionalFields","set","clear","put","key","value","push","slice","dealDocumentFromForm","context","titleLower","status","stageChangedAtMs","nowMs","visibleTo","hostId","createdByUid","uid","createdAt","Date","updatedAt","dealPatchFromForm","options","amountDerived","filter","contactChoicesFor","queryText","rows","groupId","max","token","choices","row","email","name","nameLower","tokens","Array","isArray","map","matches","startsWith","includes","length"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/deal-form-model.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The deal drawer's values and the documents they become (AGL-2598), as pure\n * functions — the drawer holds strings the way inputs do, and this module is\n * the one place those strings turn into the typed fields a `CrmDeal` stores.\n * Kept out of the component so the translation can be tested without a\n * Firestore or a DOM, and so the contact card and the company card, which\n * open the same drawer with a link preselected, build a deal the same way.\n */\n\nimport {\n contactDisplayName,\n type CrmDeal,\n type CrmDealStage,\n type CrmForecastCategory,\n type CrmPicklist,\n crmPicklistDefaultLabel,\n DEAL_NEXT_STEP_MAX,\n dealContactRolesOf,\n dealContactRolesWithPrimary,\n dealStageForecastCategory,\n isCrmForecastCategory,\n judgeCrmPicklistValue,\n nameSearchKey,\n nameSearchToken,\n readDealProbability,\n} from '@aglyn/aglyn'\nimport {\n amountInputValue,\n dateInputMs,\n dateInputValue,\n DEFAULT_DEAL_CURRENCY,\n type DealDoc,\n openStages,\n parseAmountInput,\n type PipelineDoc,\n} from './deal-board-model'\n\n/** What the drawer edits — strings as the inputs hold them. */\nexport interface DealFormValues {\n title: string\n pipelineId: string\n stageId: string\n /** As typed: `1,250.00`; parsed at save. */\n amount: string\n currency: string\n /** `YYYY-MM-DD` from a date input, or empty. */\n expectedClose: string\n ownerUid: string\n contactId: string\n contactName: string\n companyId: string\n companyName: string\n notes: string\n /*\n * Salesforce's Opportunity fields (AGL-3516).\n */\n /** The `opportunityType` label, or empty. */\n type: string\n /** The `leadSource` label, or empty. */\n leadSource: string\n nextStep: string\n /** The override as typed, `35`; empty means the stage's. */\n probability: string\n /** Empty means the stage's — what a save stamps when nobody picked one. */\n forecastCategory: '' | CrmForecastCategory\n /** One of the org's campaigns, or empty. */\n campaignId: string\n}\n\nexport const DEAL_TITLE_MAX = 120\nexport const DEAL_NOTES_MAX = 4000\n\n/**\n * A blank form aimed at a pipeline's first open stage.\n *\n * `defaults` is how a contact's page or a company's page starts a deal\n * already linked to itself: the caller passes the ids and names it knows and\n * the drawer opens with those fields filled. Anything it does not pass stays\n * blank rather than being guessed.\n */\nexport function emptyDealForm(\n pipeline: Pick<PipelineDoc, '$id' | 'stages'> | null | undefined,\n defaults: Partial<DealFormValues> = {},\n): DealFormValues {\n const firstOpen = openStages(pipeline)[0]\n return {\n title: '',\n pipelineId: pipeline?.$id ?? '',\n stageId: firstOpen?.id ?? '',\n amount: '',\n currency: DEFAULT_DEAL_CURRENCY,\n expectedClose: '',\n ownerUid: '',\n contactId: '',\n contactName: '',\n companyId: '',\n companyName: '',\n notes: '',\n type: '',\n leadSource: '',\n nextStep: '',\n probability: '',\n forecastCategory: '',\n campaignId: '',\n ...defaults,\n }\n}\n\n/** The form an existing deal opens as. */\nexport function dealFormFromDoc(deal: DealDoc): DealFormValues {\n return {\n title: String(deal.title ?? ''),\n pipelineId: String(deal.pipelineId ?? ''),\n stageId: String(deal.stageId ?? ''),\n amount: amountInputValue(deal.amountCents),\n currency: String(deal.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n expectedClose: dateInputValue(deal.expectedCloseAtMs),\n ownerUid: String(deal.ownerUid ?? ''),\n contactId: String(deal.contactId ?? ''),\n contactName: String(deal.contactName ?? ''),\n companyId: String(deal.companyId ?? ''),\n companyName: String(deal.companyName ?? ''),\n notes: String(deal.notes ?? ''),\n type: String(deal.type ?? ''),\n leadSource: String(deal.leadSource ?? ''),\n nextStep: String(deal.nextStep ?? ''),\n probability: typeof deal.probability === 'number' ? String(deal.probability) : '',\n forecastCategory: isCrmForecastCategory(deal.forecastCategory) ? deal.forecastCategory : '',\n campaignId: String(deal.campaignId ?? ''),\n }\n}\n\n/**\n * Why the form cannot be saved, or `null` when it can.\n *\n * A title is the one thing a deal needs — a card with no title is a card\n * nobody can find on the board. The amount is optional but, if typed, has to\n * parse: a deal saved with an amount the merchant typed and the model dropped\n * would forecast at zero without saying so. The stage is only checked on a\n * new deal, because an existing deal's stage is moved by the stage route and\n * is not this form's to write.\n */\nexport function dealFormProblem(\n values: DealFormValues,\n mode: 'create' | 'edit',\n): string | null {\n if (!values.title.trim()) return 'A deal needs a title.'\n if (values.amount.trim() && parseAmountInput(values.amount) === null) {\n return 'The amount has to be a number of zero or more.'\n }\n if (values.expectedClose.trim() && dateInputMs(values.expectedClose) === null) {\n return 'The expected close has to be a date.'\n }\n if (readDealProbability(values.probability) === undefined) {\n return 'The probability has to be a whole number from 0 to 100.'\n }\n if (mode === 'create' && (!values.pipelineId || !values.stageId)) {\n return 'Pick the stage the deal starts in.'\n }\n return null\n}\n\n/** What every deal write stamps besides the fields the form edits. */\nexport interface DealWriteContext {\n visibleTo: string[]\n hostId: string\n uid: string\n nowMs: number\n /**\n * The stage the deal is in, whose forecast category a form left on\n * \"from the stage\" stamps (AGL-3516). Absent, the field is left unset and\n * every reader derives it from the stage.\n */\n stage?: Pick<CrmDealStage, 'kind' | 'forecastCategory'> | null\n}\n\n/**\n * The form's two picklist fields judged against the org's lists (AGL-3516),\n * the way every server door judges them: an active value is stored as the\n * list spells it, a blank clears, the deal's own current value is kept, and\n * anything else is refused naming what the list allows. A NEW deal with no\n * Type takes the list's default, as Salesforce's does; the lead source is\n * never defaulted — it records where a deal came from.\n */\nexport function judgeDealFormPicklists(\n values: DealFormValues,\n lists: { type: CrmPicklist; leadSource: CrmPicklist },\n options: { current?: Partial<Pick<CrmDeal, 'type' | 'leadSource'>> | null; created: boolean },\n): { ok: true; values: DealFormValues } | { ok: false; error: string } {\n const typed = values.type.trim() || (options.created ? (crmPicklistDefaultLabel(lists.type) ?? '') : '')\n const type = judgeCrmPicklistValue('opportunityType', lists.type, typed, options.current?.type)\n if (type.ok === false) return type\n const leadSource = judgeCrmPicklistValue(\n 'leadSource',\n lists.leadSource,\n values.leadSource,\n options.current?.leadSource,\n )\n if (leadSource.ok === false) return leadSource\n return {\n ok: true,\n values: { ...values, type: type.value ?? '', leadSource: leadSource.value ?? '' },\n }\n}\n\n/** The forecast category a save stamps: the form's, else the stage's, else none. */\nfunction forecastCategoryFor(\n values: Pick<DealFormValues, 'forecastCategory'>,\n stage: DealWriteContext['stage'],\n): CrmForecastCategory | null {\n if (isCrmForecastCategory(values.forecastCategory)) return values.forecastCategory\n return stage ? dealStageForecastCategory(stage) : null\n}\n\n/**\n * The optional fields, as the values to SET and the keys to CLEAR.\n *\n * Split rather than written as `null` because a deal's optional ids are\n * typed as absent-or-string, and the indexes on `contactId` and `companyId`\n * are what the contact and company pages query — a `null` there would be a\n * row those pages cannot see and a filter on \"no contact\" could not find\n * either. On a create the cleared keys are simply not written; on an edit\n * the component turns them into field deletes.\n */\nfunction optionalFields(values: DealFormValues): {\n set: Record<string, string | number>\n clear: string[]\n} {\n const set: Record<string, string | number> = {}\n const clear: string[] = []\n const put = (key: string, value: string | number | null) => {\n if (value === null || value === '') clear.push(key)\n else set[key] = value\n }\n put('amountCents', parseAmountInput(values.amount))\n put('expectedCloseAtMs', dateInputMs(values.expectedClose))\n put('ownerUid', values.ownerUid.trim())\n put('contactId', values.contactId.trim())\n put('contactName', values.contactId.trim() ? values.contactName.trim() : '')\n put('companyId', values.companyId.trim())\n put('companyName', values.companyId.trim() ? values.companyName.trim() : '')\n put('notes', values.notes.trim().slice(0, DEAL_NOTES_MAX))\n put('type', values.type.trim())\n put('leadSource', values.leadSource.trim())\n put('nextStep', values.nextStep.trim().replace(/\\s+/g, ' ').slice(0, DEAL_NEXT_STEP_MAX))\n put('probability', readDealProbability(values.probability) ?? null)\n put('campaignId', values.campaignId.trim())\n return { set, clear }\n}\n\n/**\n * The document a new deal is created as. Open, in the chosen stage, with\n * the stage clock started now so \"days in stage\" counts from creation.\n */\nexport function dealDocumentFromForm(\n values: DealFormValues,\n context: DealWriteContext,\n): Record<string, unknown> {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const { set } = optionalFields(values)\n const forecastCategory = forecastCategoryFor(values, context.stage)\n const contactId = values.contactId.trim()\n return {\n title,\n titleLower: nameSearchKey(title),\n pipelineId: values.pipelineId,\n stageId: values.stageId,\n status: 'open' satisfies CrmDeal['status'],\n // The contact picked is the deal's Primary contact role (AGL-3521).\n ...(contactId ? { contactRoles: [{ contactId, primary: true }] } : {}),\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n stageChangedAtMs: context.nowMs,\n ...(forecastCategory ? { forecastCategory } : {}),\n ...set,\n visibleTo: context.visibleTo,\n hostId: context.hostId,\n createdByUid: context.uid,\n createdAt: new Date(context.nowMs),\n updatedAt: new Date(context.nowMs),\n }\n}\n\n/**\n * The patch an edit writes. Never the stage, the status or the scope: the\n * stage goes through the server route so the automations hear it, and the\n * scope is changed only by an org-wide member on purpose.\n *\n * Nor the amount or the currency of a deal whose amount is DERIVED\n * (`amountDerived`, AGL-2620): with line items on the deal the amount is\n * their sum and the currency is theirs, both owned by the products card. The\n * form shows them read-only, and a patch that re-wrote the number it was\n * seeded with would overwrite a line added from another tab meanwhile.\n */\nexport function dealPatchFromForm(\n values: DealFormValues,\n nowMs: number,\n options: {\n amountDerived?: boolean\n stage?: DealWriteContext['stage']\n /**\n * The deal as stored, whose contact roles a changed contact moves\n * (AGL-3521): the contact picked becomes the Primary — added with no\n * role when the deal did not name them — and a cleared contact leaves\n * the deal with no Primary. An unchanged contact writes no roles, so a\n * role edited on the deal's page meanwhile is not overwritten.\n */\n current?: Pick<CrmDeal, 'contactId' | 'contactRoles'> | null\n } = {},\n): { set: Record<string, unknown>; clear: string[] } {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const optional = optionalFields(values)\n const set: Record<string, unknown> = optional.set\n const { clear } = optional\n const contactId = values.contactId.trim()\n if (options.current && contactId !== (options.current.contactId ?? '')) {\n set['contactRoles'] = dealContactRolesWithPrimary(dealContactRolesOf(options.current), contactId || null)\n }\n // The category picked, else the stage's again; with neither, the field\n // goes and every reader derives it from the stage.\n const forecastCategory = forecastCategoryFor(values, options.stage)\n if (forecastCategory) set['forecastCategory'] = forecastCategory\n else clear.push('forecastCategory')\n if (options.amountDerived) {\n delete set['amountCents']\n return {\n set: { title, titleLower: nameSearchKey(title), ...set, updatedAt: new Date(nowMs) },\n clear: clear.filter((key) => key !== 'amountCents'),\n }\n }\n return {\n set: {\n title,\n titleLower: nameSearchKey(title),\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n ...set,\n updatedAt: new Date(nowMs),\n },\n clear,\n }\n}\n\n/** A contact as the picker offers it. */\nexport interface ContactChoice {\n id: string\n name: string\n email: string\n}\n\n/**\n * The contacts a typed query matches, from a window of recent contact rows,\n * in the list's own grammar.\n *\n * In memory rather than by query because the contacts collection has no\n * composite index over `visibleTo` with `nameLower` or `email`, and a\n * scoped list can carry only one array clause — so the picker reads the\n * newest rows the viewer may see (bounded, ordered) and matches here. The\n * match is what the list's search does: the normalized query as a prefix of\n * the email, or as a prefix of any word of the name (`nameTokens` holds\n * every word prefix a write produced; `nameLower` covers rows written before\n * the tokens existed). The holder's own name for the person wins over the\n * canonical one, as everywhere else.\n */\nexport function contactChoicesFor(\n queryText: string,\n rows: readonly Record<string, unknown>[],\n /**\n * The holder each row is named through: one group for every row under a\n * site, or a resolver per row at the organization level (AGL-2630), where\n * each contact reads through its own primary holder.\n */\n groupId: string | ((row: Record<string, unknown>) => string),\n max = 8,\n): ContactChoice[] {\n const key = nameSearchKey(queryText)\n const token = nameSearchToken(queryText)\n const choices: ContactChoice[] = []\n for (const row of rows) {\n const email = String(row['email'] ?? '').toLowerCase()\n const name = contactDisplayName(\n row,\n typeof groupId === 'function' ? groupId(row) : groupId,\n )\n const nameLower = nameSearchKey(name)\n const tokens = Array.isArray(row['nameTokens'])\n ? (row['nameTokens'] as unknown[]).map(String)\n : []\n const matches =\n !key ||\n email.startsWith(key) ||\n nameLower.startsWith(key) ||\n nameLower.includes(` ${key}`) ||\n (token ? tokens.includes(token) : false)\n if (!matches) continue\n choices.push({ id: String(row['$id'] ?? ''), name, email })\n if (choices.length >= max) break\n }\n return choices\n}\n"],"names":["contactDisplayName","crmPicklistDefaultLabel","DEAL_NEXT_STEP_MAX","dealContactRolesOf","dealContactRolesWithPrimary","dealStageForecastCategory","isCrmForecastCategory","judgeCrmPicklistValue","nameSearchKey","nameSearchToken","readDealProbability","amountInputValue","dateInputMs","dateInputValue","DEFAULT_DEAL_CURRENCY","openStages","parseAmountInput","DEAL_TITLE_MAX","DEAL_NOTES_MAX","emptyDealForm","pipeline","defaults","firstOpen","title","pipelineId","$id","stageId","id","amount","currency","expectedClose","ownerUid","contactId","contactName","companyId","companyName","notes","type","leadSource","nextStep","probability","forecastCategory","campaignId","dealFormFromDoc","deal","String","amountCents","toLowerCase","expectedCloseAtMs","dealFormProblem","values","mode","trim","undefined","judgeDealFormPicklists","lists","options","typed","created","current","ok","value","forecastCategoryFor","stage","optionalFields","set","clear","put","key","push","slice","replace","dealDocumentFromForm","context","titleLower","status","contactRoles","primary","stageChangedAtMs","nowMs","visibleTo","hostId","createdByUid","uid","createdAt","Date","updatedAt","dealPatchFromForm","optional","amountDerived","filter","contactChoicesFor","queryText","rows","groupId","max","token","choices","row","email","name","nameLower","tokens","Array","isArray","map","matches","startsWith","includes","length"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAED,SACEA,kBAAkB,EAKlBC,uBAAuB,EACvBC,kBAAkB,EAClBC,kBAAkB,EAClBC,2BAA2B,EAC3BC,yBAAyB,EACzBC,qBAAqB,EACrBC,qBAAqB,EACrBC,aAAa,EACbC,eAAe,EACfC,mBAAmB,QACd,eAAc;AACrB,SACEC,gBAAgB,EAChBC,WAAW,EACXC,cAAc,EACdC,qBAAqB,EAErBC,UAAU,EACVC,gBAAgB,QAEX,wBAAoB;AAkC3B,OAAO,MAAMC,iBAAiB,IAAG;AACjC,OAAO,MAAMC,iBAAiB,KAAI;AAElC;;;;;;;CAOC,GACD,OAAO,SAASC,cACdC,QAAgE,EAChEC,WAAoC,CAAC,CAAC;;IAEtC,MAAMC,YAAYP,WAAWK,SAAS,CAAC,EAAE;IACzC,OAAO;QACLG,OAAO;QACPC,UAAU,UAAEJ,4BAAAA,SAAUK,GAAG,mBAAI;QAC7BC,OAAO,WAAEJ,6BAAAA,UAAWK,EAAE,oBAAI;QAC1BC,QAAQ;QACRC,UAAUf;QACVgB,eAAe;QACfC,UAAU;QACVC,WAAW;QACXC,aAAa;QACbC,WAAW;QACXC,aAAa;QACbC,OAAO;QACPC,MAAM;QACNC,YAAY;QACZC,UAAU;QACVC,aAAa;QACbC,kBAAkB;QAClBC,YAAY;OACTrB;AAEP;AAEA,wCAAwC,GACxC,OAAO,SAASsB,gBAAgBC,IAAa;QAE3BA,aACKA,kBACHA,eAICA,gBACCA,iBACEA,mBACFA,iBACEA,mBACNA,aACDA,YACMA,kBACFA,gBAGEA;IAlBrB,OAAO;QACLrB,OAAOsB,QAAOD,cAAAA,KAAKrB,KAAK,YAAVqB,cAAc;QAC5BpB,YAAYqB,QAAOD,mBAAAA,KAAKpB,UAAU,YAAfoB,mBAAmB;QACtClB,SAASmB,QAAOD,gBAAAA,KAAKlB,OAAO,YAAZkB,gBAAgB;QAChChB,QAAQjB,iBAAiBiC,KAAKE,WAAW;QACzCjB,UAAUgB,OAAOD,KAAKf,QAAQ,IAAIf,uBAAuBiC,WAAW;QACpEjB,eAAejB,eAAe+B,KAAKI,iBAAiB;QACpDjB,UAAUc,QAAOD,iBAAAA,KAAKb,QAAQ,YAAba,iBAAiB;QAClCZ,WAAWa,QAAOD,kBAAAA,KAAKZ,SAAS,YAAdY,kBAAkB;QACpCX,aAAaY,QAAOD,oBAAAA,KAAKX,WAAW,YAAhBW,oBAAoB;QACxCV,WAAWW,QAAOD,kBAAAA,KAAKV,SAAS,YAAdU,kBAAkB;QACpCT,aAAaU,QAAOD,oBAAAA,KAAKT,WAAW,YAAhBS,oBAAoB;QACxCR,OAAOS,QAAOD,cAAAA,KAAKR,KAAK,YAAVQ,cAAc;QAC5BP,MAAMQ,QAAOD,aAAAA,KAAKP,IAAI,YAATO,aAAa;QAC1BN,YAAYO,QAAOD,mBAAAA,KAAKN,UAAU,YAAfM,mBAAmB;QACtCL,UAAUM,QAAOD,iBAAAA,KAAKL,QAAQ,YAAbK,iBAAiB;QAClCJ,aAAa,OAAOI,KAAKJ,WAAW,KAAK,WAAWK,OAAOD,KAAKJ,WAAW,IAAI;QAC/EC,kBAAkBnC,sBAAsBsC,KAAKH,gBAAgB,IAAIG,KAAKH,gBAAgB,GAAG;QACzFC,YAAYG,QAAOD,mBAAAA,KAAKF,UAAU,YAAfE,mBAAmB;IACxC;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASK,gBACdC,MAAsB,EACtBC,IAAuB;IAEvB,IAAI,CAACD,OAAO3B,KAAK,CAAC6B,IAAI,IAAI,OAAO;IACjC,IAAIF,OAAOtB,MAAM,CAACwB,IAAI,MAAMpC,iBAAiBkC,OAAOtB,MAAM,MAAM,MAAM;QACpE,OAAO;IACT;IACA,IAAIsB,OAAOpB,aAAa,CAACsB,IAAI,MAAMxC,YAAYsC,OAAOpB,aAAa,MAAM,MAAM;QAC7E,OAAO;IACT;IACA,IAAIpB,oBAAoBwC,OAAOV,WAAW,MAAMa,WAAW;QACzD,OAAO;IACT;IACA,IAAIF,SAAS,YAAa,CAAA,CAACD,OAAO1B,UAAU,IAAI,CAAC0B,OAAOxB,OAAO,AAAD,GAAI;QAChE,OAAO;IACT;IACA,OAAO;AACT;AAgBA;;;;;;;CAOC,GACD,OAAO,SAAS4B,uBACdJ,MAAsB,EACtBK,KAAqD,EACrDC,OAA6F;QAErCvD,0BAY3BoC,aAA8BC;QAXckB,kBAMvEA;IAPF,MAAMC,QAAQP,OAAOb,IAAI,CAACe,IAAI,MAAOI,CAAAA,QAAQE,OAAO,IAAIzD,2BAAAA,wBAAwBsD,MAAMlB,IAAI,aAAlCpC,2BAAuC,KAAM,EAAC;IACtG,MAAMoC,OAAO9B,sBAAsB,mBAAmBgD,MAAMlB,IAAI,EAAEoB,QAAOD,mBAAAA,QAAQG,OAAO,qBAAfH,iBAAiBnB,IAAI;IAC9F,IAAIA,KAAKuB,EAAE,KAAK,OAAO,OAAOvB;IAC9B,MAAMC,aAAa/B,sBACjB,cACAgD,MAAMjB,UAAU,EAChBY,OAAOZ,UAAU,GACjBkB,oBAAAA,QAAQG,OAAO,qBAAfH,kBAAiBlB,UAAU;IAE7B,IAAIA,WAAWsB,EAAE,KAAK,OAAO,OAAOtB;IACpC,OAAO;QACLsB,IAAI;QACJV,QAAQ,aAAKA;YAAQb,IAAI,GAAEA,cAAAA,KAAKwB,KAAK,YAAVxB,cAAc;YAAIC,UAAU,GAAEA,oBAAAA,WAAWuB,KAAK,YAAhBvB,oBAAoB;;IAC/E;AACF;AAEA,kFAAkF,GAClF,SAASwB,oBACPZ,MAAgD,EAChDa,KAAgC;IAEhC,IAAIzD,sBAAsB4C,OAAOT,gBAAgB,GAAG,OAAOS,OAAOT,gBAAgB;IAClF,OAAOsB,QAAQ1D,0BAA0B0D,SAAS;AACpD;AAEA;;;;;;;;;CASC,GACD,SAASC,eAAed,MAAsB;QAqBzBxC;IAjBnB,MAAMuD,MAAuC,CAAC;IAC9C,MAAMC,QAAkB,EAAE;IAC1B,MAAMC,MAAM,CAACC,KAAaP;QACxB,IAAIA,UAAU,QAAQA,UAAU,IAAIK,MAAMG,IAAI,CAACD;aAC1CH,GAAG,CAACG,IAAI,GAAGP;IAClB;IACAM,IAAI,eAAenD,iBAAiBkC,OAAOtB,MAAM;IACjDuC,IAAI,qBAAqBvD,YAAYsC,OAAOpB,aAAa;IACzDqC,IAAI,YAAYjB,OAAOnB,QAAQ,CAACqB,IAAI;IACpCe,IAAI,aAAajB,OAAOlB,SAAS,CAACoB,IAAI;IACtCe,IAAI,eAAejB,OAAOlB,SAAS,CAACoB,IAAI,KAAKF,OAAOjB,WAAW,CAACmB,IAAI,KAAK;IACzEe,IAAI,aAAajB,OAAOhB,SAAS,CAACkB,IAAI;IACtCe,IAAI,eAAejB,OAAOhB,SAAS,CAACkB,IAAI,KAAKF,OAAOf,WAAW,CAACiB,IAAI,KAAK;IACzEe,IAAI,SAASjB,OAAOd,KAAK,CAACgB,IAAI,GAAGkB,KAAK,CAAC,GAAGpD;IAC1CiD,IAAI,QAAQjB,OAAOb,IAAI,CAACe,IAAI;IAC5Be,IAAI,cAAcjB,OAAOZ,UAAU,CAACc,IAAI;IACxCe,IAAI,YAAYjB,OAAOX,QAAQ,CAACa,IAAI,GAAGmB,OAAO,CAAC,QAAQ,KAAKD,KAAK,CAAC,GAAGpE;IACrEiE,IAAI,gBAAezD,uBAAAA,oBAAoBwC,OAAOV,WAAW,aAAtC9B,uBAA2C;IAC9DyD,IAAI,cAAcjB,OAAOR,UAAU,CAACU,IAAI;IACxC,OAAO;QAAEa;QAAKC;IAAM;AACtB;AAEA;;;CAGC,GACD,OAAO,SAASM,qBACdtB,MAAsB,EACtBuB,OAAyB;IAEzB,MAAMlD,QAAQ2B,OAAO3B,KAAK,CAAC6B,IAAI,GAAGkB,KAAK,CAAC,GAAGrD;IAC3C,MAAM,EAAEgD,GAAG,EAAE,GAAGD,eAAed;IAC/B,MAAMT,mBAAmBqB,oBAAoBZ,QAAQuB,QAAQV,KAAK;IAClE,MAAM/B,YAAYkB,OAAOlB,SAAS,CAACoB,IAAI;IACvC,OAAO;QACL7B;QACAmD,YAAYlE,cAAce;QAC1BC,YAAY0B,OAAO1B,UAAU;QAC7BE,SAASwB,OAAOxB,OAAO;QACvBiD,QAAQ;OAEJ3C,YAAY;QAAE4C,cAAc;YAAC;gBAAE5C;gBAAW6C,SAAS;YAAK;SAAE;IAAC,IAAI,CAAC;QACpEhD,UAAUgB,OAAOK,OAAOrB,QAAQ,IAAIf,uBAAuBiC,WAAW;QACtE+B,kBAAkBL,QAAQM,KAAK;OAC3BtC,mBAAmB;QAAEA;IAAiB,IAAI,CAAC,GAC5CwB;QACHe,WAAWP,QAAQO,SAAS;QAC5BC,QAAQR,QAAQQ,MAAM;QACtBC,cAAcT,QAAQU,GAAG;QACzBC,WAAW,IAAIC,KAAKZ,QAAQM,KAAK;QACjCO,WAAW,IAAID,KAAKZ,QAAQM,KAAK;;AAErC;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASQ,kBACdrC,MAAsB,EACtB6B,KAAa,EACbvB,UAWI,CAAC,CAAC;QAOgCA;IALtC,MAAMjC,QAAQ2B,OAAO3B,KAAK,CAAC6B,IAAI,GAAGkB,KAAK,CAAC,GAAGrD;IAC3C,MAAMuE,WAAWxB,eAAed;IAChC,MAAMe,MAA+BuB,SAASvB,GAAG;IACjD,MAAM,EAAEC,KAAK,EAAE,GAAGsB;IAClB,MAAMxD,YAAYkB,OAAOlB,SAAS,CAACoB,IAAI;IACvC,IAAII,QAAQG,OAAO,IAAI3B,gBAAewB,6BAAAA,QAAQG,OAAO,CAAC3B,SAAS,YAAzBwB,6BAA6B,KAAK;QACtES,GAAG,CAAC,eAAe,GAAG7D,4BAA4BD,mBAAmBqD,QAAQG,OAAO,GAAG3B,aAAa;IACtG;IACA,uEAAuE;IACvE,mDAAmD;IACnD,MAAMS,mBAAmBqB,oBAAoBZ,QAAQM,QAAQO,KAAK;IAClE,IAAItB,kBAAkBwB,GAAG,CAAC,mBAAmB,GAAGxB;SAC3CyB,MAAMG,IAAI,CAAC;IAChB,IAAIb,QAAQiC,aAAa,EAAE;QACzB,OAAOxB,GAAG,CAAC,cAAc;QACzB,OAAO;YACLA,KAAK;gBAAE1C;gBAAOmD,YAAYlE,cAAce;eAAW0C;gBAAKqB,WAAW,IAAID,KAAKN;;YAC5Eb,OAAOA,MAAMwB,MAAM,CAAC,CAACtB,MAAQA,QAAQ;QACvC;IACF;IACA,OAAO;QACLH,KAAK;YACH1C;YACAmD,YAAYlE,cAAce;YAC1BM,UAAUgB,OAAOK,OAAOrB,QAAQ,IAAIf,uBAAuBiC,WAAW;WACnEkB;YACHqB,WAAW,IAAID,KAAKN;;QAEtBb;IACF;AACF;AASA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASyB,kBACdC,SAAiB,EACjBC,IAAwC,EACxC;;;;GAIC,GACDC,OAA4D,EAC5DC,MAAM,CAAC;IAEP,MAAM3B,MAAM5D,cAAcoF;IAC1B,MAAMI,QAAQvF,gBAAgBmF;IAC9B,MAAMK,UAA2B,EAAE;IACnC,KAAK,MAAMC,OAAOL,KAAM;YACDK,YAgBKA;QAhB1B,MAAMC,QAAQtD,QAAOqD,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB,IAAInD,WAAW;QACpD,MAAMqD,OAAOpG,mBACXkG,KACA,OAAOJ,YAAY,aAAaA,QAAQI,OAAOJ;QAEjD,MAAMO,YAAY7F,cAAc4F;QAChC,MAAME,SAASC,MAAMC,OAAO,CAACN,GAAG,CAAC,aAAa,IAC1C,AAACA,GAAG,CAAC,aAAa,CAAeO,GAAG,CAAC5D,UACrC,EAAE;QACN,MAAM6D,UACJ,CAACtC,OACD+B,MAAMQ,UAAU,CAACvC,QACjBiC,UAAUM,UAAU,CAACvC,QACrBiC,UAAUO,QAAQ,CAAC,CAAC,CAAC,EAAExC,KAAK,KAC3B4B,CAAAA,QAAQM,OAAOM,QAAQ,CAACZ,SAAS,KAAI;QACxC,IAAI,CAACU,SAAS;QACdT,QAAQ5B,IAAI,CAAC;YAAE1C,IAAIkB,QAAOqD,WAAAA,GAAG,CAAC,MAAM,YAAVA,WAAc;YAAKE;YAAMD;QAAM;QACzD,IAAIF,QAAQY,MAAM,IAAId,KAAK;IAC7B;IACA,OAAOE;AACT"}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
* Hand the browser a file to save.
|
|
19
|
+
*
|
|
20
|
+
* It touches `URL.createObjectURL` and `document`, so it belongs to the
|
|
21
|
+
* browser and never to a module a server route imports.
|
|
22
|
+
*/
|
|
23
|
+
export declare function downloadTextFile(name: string, mime: string, text: string): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
* Hand the browser a file to save.
|
|
18
|
+
*
|
|
19
|
+
* It touches `URL.createObjectURL` and `document`, so it belongs to the
|
|
20
|
+
* browser and never to a module a server route imports.
|
|
21
|
+
*/ export function downloadTextFile(name, mime, text) {
|
|
22
|
+
const url = URL.createObjectURL(new Blob([
|
|
23
|
+
text
|
|
24
|
+
], {
|
|
25
|
+
type: mime
|
|
26
|
+
}));
|
|
27
|
+
const anchor = document.createElement('a');
|
|
28
|
+
anchor.href = url;
|
|
29
|
+
anchor.download = name;
|
|
30
|
+
anchor.click();
|
|
31
|
+
URL.revokeObjectURL(url);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=download-text-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/download-text-file.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Hand the browser a file to save.\n *\n * It touches `URL.createObjectURL` and `document`, so it belongs to the\n * browser and never to a module a server route imports.\n */\nexport function downloadTextFile(name: string, mime: string, text: string): void {\n const url = URL.createObjectURL(new Blob([text], { type: mime }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = name\n anchor.click()\n URL.revokeObjectURL(url)\n}\n"],"names":["downloadTextFile","name","mime","text","url","URL","createObjectURL","Blob","type","anchor","document","createElement","href","download","click","revokeObjectURL"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;CAKC,GACD,OAAO,SAASA,iBAAiBC,IAAY,EAAEC,IAAY,EAAEC,IAAY;IACvE,MAAMC,MAAMC,IAAIC,eAAe,CAAC,IAAIC,KAAK;QAACJ;KAAK,EAAE;QAAEK,MAAMN;IAAK;IAC9D,MAAMO,SAASC,SAASC,aAAa,CAAC;IACtCF,OAAOG,IAAI,GAAGR;IACdK,OAAOI,QAAQ,GAAGZ;IAClBQ,OAAOK,KAAK;IACZT,IAAIU,eAAe,CAACX;AACtB"}
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type EmailStateStatus, type CrmLeadStatus } from '@aglyn/aglyn';
|
|
17
|
+
import { type EmailStateStatus, type CrmLeadStatus, type CrmPicklist } from '@aglyn/aglyn';
|
|
18
18
|
import type { CrmViewFilterClause } from '@aglyn/aglyn';
|
|
19
19
|
import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter';
|
|
20
20
|
import { type ListGridFilterCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
|
|
21
|
-
import type
|
|
21
|
+
import { type ListQueryDeclaration, type ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
|
|
22
22
|
import { type CrmClauseAsked } from './crm-list-query';
|
|
23
23
|
/**
|
|
24
24
|
* What the Leads section's `Show` control offers (AGL-2608): the two
|
|
@@ -49,6 +49,20 @@ export declare const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string>;
|
|
|
49
49
|
export declare const LEAD_SOURCE_FILTER_NONE = "\0none";
|
|
50
50
|
/** The fields the grid's panel offers on Leads. */
|
|
51
51
|
export declare const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[];
|
|
52
|
+
/**
|
|
53
|
+
* The lead picklist columns the list filters by (AGL-3513), each with the
|
|
54
|
+
* list its choices come from: a choice's value is the KEY the query
|
|
55
|
+
* compares, its caption the label.
|
|
56
|
+
*/
|
|
57
|
+
export declare const LEAD_PICKLIST_FILTERS: readonly [{
|
|
58
|
+
readonly column: "industry";
|
|
59
|
+
readonly picklistId: "industry";
|
|
60
|
+
readonly header: "Industry";
|
|
61
|
+
}, {
|
|
62
|
+
readonly column: "rating";
|
|
63
|
+
readonly picklistId: "rating";
|
|
64
|
+
readonly header: "Rating";
|
|
65
|
+
}];
|
|
52
66
|
/** What each filter field reads as on a chip. */
|
|
53
67
|
export declare const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>>;
|
|
54
68
|
/** The choices for Status: Open (new, nurturing or working) and each status on its own. */
|
|
@@ -61,6 +75,21 @@ export declare const LEAD_EMAIL_FILTER_OPTIONS: {
|
|
|
61
75
|
value: "none" | "ok" | "bounced" | "complained" | "blocked" | "undeliverable" | "unsubscribed" | "do_not_contact" | "problem";
|
|
62
76
|
label: string;
|
|
63
77
|
}[];
|
|
78
|
+
/** The choices for Lead source direction: each group of the Lead source picklist. */
|
|
79
|
+
export declare const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS: {
|
|
80
|
+
value: import("./lead-source-direction").LeadSourceDirection;
|
|
81
|
+
label: string;
|
|
82
|
+
}[];
|
|
83
|
+
/**
|
|
84
|
+
* The Status choices as an org reads them (AGL-3512): still one choice per
|
|
85
|
+
* MEANING — the query asks `status` — each named by the org's values of that
|
|
86
|
+
* meaning, so a list that keeps "Contacted" and "Meeting set" as Working
|
|
87
|
+
* reads "Working: Contacted, Meeting set".
|
|
88
|
+
*/
|
|
89
|
+
export declare function leadStatusFilterOptions(picklist: CrmPicklist): Array<{
|
|
90
|
+
value: string;
|
|
91
|
+
label: string;
|
|
92
|
+
}>;
|
|
64
93
|
/**
|
|
65
94
|
* The clauses the grid shows for a view's stored ones. A stored list with
|
|
66
95
|
* no status clause is Open, which the list opened on before views existed,
|
|
@@ -105,6 +134,11 @@ export interface LeadQueryReader {
|
|
|
105
134
|
scopeTokens: readonly string[] | null;
|
|
106
135
|
/** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */
|
|
107
136
|
foldsScope: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* The org's Lead source list, which a direction is expanded through;
|
|
139
|
+
* the standard values alone while it has not been read.
|
|
140
|
+
*/
|
|
141
|
+
leadSources?: CrmPicklist;
|
|
108
142
|
}
|
|
109
143
|
/**
|
|
110
144
|
* One grid clause as the clause the Leads query asks — see the block above.
|
|
@@ -14,10 +14,12 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, CRM_LEAD_STATUS_LABELS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_SCOPED_CAMPAIGNS_FIELD, CRM_LEAD_STATUSES, crmLeadSourceKey } from "@aglyn/aglyn";
|
|
17
|
+
*/ import { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, CRM_LEAD_STATUS_LABELS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_SCOPED_CAMPAIGNS_FIELD, CRM_LEAD_STATUSES, crmLeadSourceKey, crmPicklistKey } from "@aglyn/aglyn";
|
|
18
18
|
import { SCOPED_SEARCH_JOIN } from "@aglyn/aglyn/app-utils/name-search";
|
|
19
19
|
import { listSelectCodec } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
|
|
20
|
+
import { LIST_QUERY_DISJUNCTIONS } from "@aglyn/shared-ui-jsx/const/list-query-plan";
|
|
20
21
|
import { CRM_LIST_SEARCH, crmAnyOf, crmClauseValues, crmSelectField } from "./crm-list-query.js";
|
|
22
|
+
import { isLeadSourceDirection, LEAD_SOURCE_DIRECTION_LABELS, LEAD_SOURCE_DIRECTIONS, leadSourceLabelsOfDirection, STANDARD_LEAD_SOURCES } from "./lead-source-direction.js";
|
|
21
23
|
export const LEAD_FILTERS = [
|
|
22
24
|
'open',
|
|
23
25
|
'new',
|
|
@@ -63,6 +65,10 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
|
|
|
63
65
|
* emailState `equals <filter>`
|
|
64
66
|
* campaignIds `contains <campaign id>`
|
|
65
67
|
* leadSource `equals <label>`, or `isEmpty` for the leads holding none
|
|
68
|
+
* leadSourceDirection `equals inbound|outbound` (AGL-3511): every lead
|
|
69
|
+
* whose lead source is a value of that group
|
|
70
|
+
* industry, rating `equals <key>` (AGL-3513): a value of the org's
|
|
71
|
+
* Industry or Rating list, by the key the query compares
|
|
66
72
|
*
|
|
67
73
|
* Every one is asked of the Leads query (AGL-3321) through the field a
|
|
68
74
|
* writer stores for it — see {@link leadQueryClause}.
|
|
@@ -103,6 +109,15 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
|
|
|
103
109
|
'isAnyOf'
|
|
104
110
|
]
|
|
105
111
|
},
|
|
112
|
+
{
|
|
113
|
+
column: 'leadSourceDirection',
|
|
114
|
+
kind: 'exact',
|
|
115
|
+
path: 'leadSourceDirection',
|
|
116
|
+
operators: [
|
|
117
|
+
'equals',
|
|
118
|
+
'isAnyOf'
|
|
119
|
+
]
|
|
120
|
+
},
|
|
106
121
|
{
|
|
107
122
|
column: 'campaignIds',
|
|
108
123
|
kind: 'exact',
|
|
@@ -110,6 +125,41 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
|
|
|
110
125
|
operators: [
|
|
111
126
|
'equals'
|
|
112
127
|
]
|
|
128
|
+
},
|
|
129
|
+
// Salesforce's Industry and Rating (AGL-3513), the lists companies keep.
|
|
130
|
+
{
|
|
131
|
+
column: 'industry',
|
|
132
|
+
kind: 'exact',
|
|
133
|
+
path: 'industry',
|
|
134
|
+
operators: [
|
|
135
|
+
'equals',
|
|
136
|
+
'isAnyOf'
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
column: 'rating',
|
|
141
|
+
kind: 'exact',
|
|
142
|
+
path: 'rating',
|
|
143
|
+
operators: [
|
|
144
|
+
'equals',
|
|
145
|
+
'isAnyOf'
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
/**
|
|
150
|
+
* The lead picklist columns the list filters by (AGL-3513), each with the
|
|
151
|
+
* list its choices come from: a choice's value is the KEY the query
|
|
152
|
+
* compares, its caption the label.
|
|
153
|
+
*/ export const LEAD_PICKLIST_FILTERS = [
|
|
154
|
+
{
|
|
155
|
+
column: 'industry',
|
|
156
|
+
picklistId: 'industry',
|
|
157
|
+
header: 'Industry'
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
column: 'rating',
|
|
161
|
+
picklistId: 'rating',
|
|
162
|
+
header: 'Rating'
|
|
113
163
|
}
|
|
114
164
|
];
|
|
115
165
|
/** What each filter field reads as on a chip. */ export const LEAD_LIST_FILTER_HEADERS = {
|
|
@@ -117,7 +167,10 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
|
|
|
117
167
|
emailState: 'Email',
|
|
118
168
|
ownerUid: 'Owner',
|
|
119
169
|
leadSource: 'Lead source',
|
|
120
|
-
|
|
170
|
+
leadSourceDirection: 'Lead source direction',
|
|
171
|
+
campaignIds: 'Campaign',
|
|
172
|
+
industry: 'Industry',
|
|
173
|
+
rating: 'Rating'
|
|
121
174
|
};
|
|
122
175
|
/** The choices for Status: Open (new, nurturing or working) and each status on its own. */ export const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter((option)=>option !== 'all').map((option)=>({
|
|
123
176
|
value: option,
|
|
@@ -127,6 +180,29 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
|
|
|
127
180
|
value: option,
|
|
128
181
|
label: LEAD_EMAIL_FILTER_LABELS[option]
|
|
129
182
|
}));
|
|
183
|
+
/** The choices for Lead source direction: each group of the Lead source picklist. */ export const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS = LEAD_SOURCE_DIRECTIONS.map((direction)=>({
|
|
184
|
+
value: direction,
|
|
185
|
+
label: LEAD_SOURCE_DIRECTION_LABELS[direction]
|
|
186
|
+
}));
|
|
187
|
+
/**
|
|
188
|
+
* The Status choices as an org reads them (AGL-3512): still one choice per
|
|
189
|
+
* MEANING — the query asks `status` — each named by the org's values of that
|
|
190
|
+
* meaning, so a list that keeps "Contacted" and "Meeting set" as Working
|
|
191
|
+
* reads "Working: Contacted, Meeting set".
|
|
192
|
+
*/ export function leadStatusFilterOptions(picklist) {
|
|
193
|
+
const named = (status)=>{
|
|
194
|
+
const labels = picklist.values.filter((value)=>value.meaning === status && value.active).map((value)=>value.label);
|
|
195
|
+
const meaning = picklistMeaningName(status);
|
|
196
|
+
if (!labels.length) return meaning;
|
|
197
|
+
if (labels.length === 1 && labels[0] === meaning) return meaning;
|
|
198
|
+
return `${meaning}: ${labels.join(', ')}`;
|
|
199
|
+
};
|
|
200
|
+
return LEAD_FILTERS.filter((option)=>option !== 'all').map((option)=>({
|
|
201
|
+
value: option,
|
|
202
|
+
label: option === 'open' ? 'Open (new, nurturing or working)' : named(option)
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
const picklistMeaningName = (status)=>CRM_LEAD_STATUS_LABELS[status];
|
|
130
206
|
const STATUS_OPEN = {
|
|
131
207
|
field: 'status',
|
|
132
208
|
op: 'equals',
|
|
@@ -214,7 +290,15 @@ const STATUS_ALL = {
|
|
|
214
290
|
* emailed" is every verdict but `ok`
|
|
215
291
|
* leadSource `leadSourceKey`, the label as the picklist compares it, or
|
|
216
292
|
* `null` for none
|
|
293
|
+
* leadSourceDirection `leadSourceKey in [...]` over the key of every
|
|
294
|
+
* value the org's list files under that group, read when
|
|
295
|
+
* the clause is asked — a direction is never stored on a
|
|
296
|
+
* lead, so regrouping a value moves its leads with it. One
|
|
297
|
+
* `in` holds thirty keys, and a group holding more is
|
|
298
|
+
* refused by name rather than half asked
|
|
217
299
|
* ownerUid as stored
|
|
300
|
+
* industry, rating `industryKey`, `ratingKey` (AGL-3513), each label
|
|
301
|
+
* as the picklist compares it
|
|
218
302
|
* campaignIds `array-contains` on the lead's own campaigns; under a site,
|
|
219
303
|
* `scopedCampaignIds` — the campaign behind each of the
|
|
220
304
|
* site's scope tokens — which stands in the scope clause's
|
|
@@ -256,6 +340,9 @@ const STATUS_ALL = {
|
|
|
256
340
|
'contains'
|
|
257
341
|
]
|
|
258
342
|
},
|
|
343
|
+
// Industry and Rating (AGL-3513), by the key each writer stores.
|
|
344
|
+
crmSelectField('industryKey'),
|
|
345
|
+
crmSelectField('ratingKey'),
|
|
259
346
|
{
|
|
260
347
|
// A campaign under a site: the lead's campaigns behind its scope.
|
|
261
348
|
column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,
|
|
@@ -344,8 +431,42 @@ const ORG_READER = {
|
|
|
344
431
|
const keys = values.map((value)=>crmLeadSourceKey(value)).filter((key)=>Boolean(key));
|
|
345
432
|
return crmAnyOf('leadSourceKey', keys);
|
|
346
433
|
}
|
|
434
|
+
case 'leadSourceDirection':
|
|
435
|
+
{
|
|
436
|
+
var _reader_leadSources;
|
|
437
|
+
const picklist = (_reader_leadSources = reader.leadSources) != null ? _reader_leadSources : STANDARD_LEAD_SOURCES;
|
|
438
|
+
const keys = [];
|
|
439
|
+
for (const value of values){
|
|
440
|
+
if (!isLeadSourceDirection(value)) return {
|
|
441
|
+
refused: `${value} is not a lead source direction`
|
|
442
|
+
};
|
|
443
|
+
for (const label of leadSourceLabelsOfDirection(picklist, value)){
|
|
444
|
+
const key = crmLeadSourceKey(label);
|
|
445
|
+
if (key) keys.push(key);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const unique = [
|
|
449
|
+
...new Set(keys)
|
|
450
|
+
];
|
|
451
|
+
if (!unique.length) return {
|
|
452
|
+
refused: 'no lead source is in that direction yet'
|
|
453
|
+
};
|
|
454
|
+
if (unique.length > LIST_QUERY_DISJUNCTIONS) {
|
|
455
|
+
return {
|
|
456
|
+
refused: `more than ${LIST_QUERY_DISJUNCTIONS} lead sources are in that direction — filter by Lead source instead`
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return crmAnyOf('leadSourceKey', unique);
|
|
460
|
+
}
|
|
347
461
|
case 'ownerUid':
|
|
348
462
|
return crmAnyOf('ownerUid', values);
|
|
463
|
+
case 'industry':
|
|
464
|
+
case 'rating':
|
|
465
|
+
{
|
|
466
|
+
// A choice is the key; a label typed into a stored view keys the same way.
|
|
467
|
+
const keys = values.map((value)=>crmPicklistKey(value)).filter((key)=>Boolean(key));
|
|
468
|
+
return crmAnyOf(clause.field === 'industry' ? 'industryKey' : 'ratingKey', keys);
|
|
469
|
+
}
|
|
349
470
|
case 'campaignIds':
|
|
350
471
|
{
|
|
351
472
|
if (values.length !== 1) return {
|