@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +13 -13
- package/src/lib/components/activity-list.js +12 -5
- package/src/lib/components/activity-list.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.d.ts +1 -4
- package/src/lib/components/companies-bulk-bar.js +7 -20
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.d.ts +3 -3
- package/src/lib/components/companies-section.js +149 -46
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +281 -20
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-properties-card.js +99 -8
- package/src/lib/components/company-properties-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.js +3 -1
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +33 -10
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-properties-card.d.ts +0 -69
- package/src/lib/components/contact-properties-card.js +350 -60
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contact-reports-to-field.d.ts +46 -0
- package/src/lib/components/contact-reports-to-field.js +67 -0
- package/src/lib/components/contact-reports-to-field.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
- package/src/lib/components/contacts-bulk-bar.js +7 -19
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +46 -33
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-call-actions.d.ts +10 -0
- package/src/lib/components/crm-call-actions.js +26 -9
- package/src/lib/components/crm-call-actions.js.map +1 -1
- package/src/lib/components/crm-record-picker.d.ts +15 -0
- package/src/lib/components/crm-record-picker.js +16 -10
- package/src/lib/components/crm-record-picker.js.map +1 -1
- package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
- package/src/lib/components/crm-transfer-buttons.js +167 -0
- package/src/lib/components/crm-transfer-buttons.js.map +1 -0
- package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
- package/src/lib/components/deal-contact-roles-card.js +368 -0
- package/src/lib/components/deal-contact-roles-card.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +3 -2
- package/src/lib/components/deal-detail-page.js +25 -6
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.d.ts +10 -0
- package/src/lib/components/deal-edit-drawer.js +219 -10
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-properties-card.d.ts +7 -0
- package/src/lib/components/deal-properties-card.js +34 -3
- package/src/lib/components/deal-properties-card.js.map +1 -1
- package/src/lib/components/deal-stage-card.d.ts +4 -0
- package/src/lib/components/deal-stage-card.js +10 -2
- package/src/lib/components/deal-stage-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.d.ts +0 -3
- package/src/lib/components/deals-bulk-bar.js +7 -20
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/deals-section.d.ts +3 -3
- package/src/lib/components/deals-section.js +123 -63
- package/src/lib/components/deals-section.js.map +1 -1
- package/src/lib/components/fields-section.js +31 -17
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/lead-convert-dialog.d.ts +4 -0
- package/src/lib/components/lead-convert-dialog.js +23 -1
- package/src/lib/components/lead-convert-dialog.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +8 -2
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-profile-fields.d.ts +71 -0
- package/src/lib/components/lead-profile-fields.js +386 -0
- package/src/lib/components/lead-profile-fields.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +2 -1
- package/src/lib/components/lead-properties-card.js +85 -176
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-source-select.d.ts +4 -23
- package/src/lib/components/lead-source-select.js +9 -73
- package/src/lib/components/lead-source-select.js.map +1 -1
- package/src/lib/components/lead-status-chip.d.ts +9 -3
- package/src/lib/components/lead-status-chip.js +8 -4
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-status-options.d.ts +20 -0
- package/src/lib/components/lead-status-options.js +51 -0
- package/src/lib/components/lead-status-options.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
- package/src/lib/components/lead-unqualify-dialog.js +50 -18
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.d.ts +0 -3
- package/src/lib/components/leads-bulk-bar.js +29 -29
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +120 -68
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/linked-deals-card.d.ts +4 -0
- package/src/lib/components/linked-deals-card.js +47 -3
- package/src/lib/components/linked-deals-card.js.map +1 -1
- package/src/lib/components/log-activity-dialog.js +43 -3
- package/src/lib/components/log-activity-dialog.js.map +1 -1
- package/src/lib/components/new-contact-drawer.d.ts +8 -0
- package/src/lib/components/new-contact-drawer.js +108 -6
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/new-lead-drawer.d.ts +9 -2
- package/src/lib/components/new-lead-drawer.js +88 -121
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/picklist-select.d.ts +36 -0
- package/src/lib/components/picklist-select.js +114 -0
- package/src/lib/components/picklist-select.js.map +1 -0
- package/src/lib/components/picklist-values-card.d.ts +34 -0
- package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
- package/src/lib/components/picklist-values-card.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
- package/src/lib/components/pipeline-stages-dialog.js +26 -4
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
- package/src/lib/components/pipelines-dialog.js +15 -6
- package/src/lib/components/pipelines-dialog.js.map +1 -1
- package/src/lib/components/recent-activity-feed.js +20 -5
- package/src/lib/components/recent-activity-feed.js.map +1 -1
- package/src/lib/components/recipes-card.js +4 -2
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-tasks-card.js +15 -8
- package/src/lib/components/record-tasks-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.d.ts +6 -0
- package/src/lib/components/reports/forecast-card.js +101 -3
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
- package/src/lib/components/reports/lead-funnel-card.js +9 -5
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/lead-source-card.d.ts +23 -0
- package/src/lib/components/reports/lead-source-card.js +318 -0
- package/src/lib/components/reports/lead-source-card.js.map +1 -0
- package/src/lib/components/reports/report-export.js +1 -1
- package/src/lib/components/reports/report-export.js.map +1 -1
- package/src/lib/components/reports-section.js +8 -0
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
- package/src/lib/components/sharing-rule-drawer.js +108 -19
- package/src/lib/components/sharing-rule-drawer.js.map +1 -1
- package/src/lib/components/sharing-rules-card.js +11 -3
- package/src/lib/components/sharing-rules-card.js.map +1 -1
- package/src/lib/components/task-cells.d.ts +9 -1
- package/src/lib/components/task-cells.js +9 -4
- package/src/lib/components/task-cells.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +111 -43
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
- package/src/lib/components/tasks-bulk-bar.js +7 -20
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
- package/src/lib/components/tasks-calendar.d.ts +3 -0
- package/src/lib/components/tasks-calendar.js +8 -1
- package/src/lib/components/tasks-calendar.js.map +1 -1
- package/src/lib/components/tasks-section.d.ts +3 -3
- package/src/lib/components/tasks-section.js +58 -43
- package/src/lib/components/tasks-section.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +2 -9
- package/src/lib/constants/api-routes.js +1 -7
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +11 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/constants/company-filters.d.ts +14 -0
- package/src/lib/constants/company-filters.js +51 -1
- package/src/lib/constants/company-filters.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +2 -0
- package/src/lib/constants/contact-filters.js +18 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/constants/deal-filters.d.ts +22 -1
- package/src/lib/constants/deal-filters.js +67 -4
- package/src/lib/constants/deal-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +3 -0
- package/src/lib/declarations.console-server.js +35 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +35 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +11 -0
- package/src/lib/declarations.server.js +92 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-company-picklists.d.ts +15 -0
- package/src/lib/hooks/use-company-picklists.js +55 -0
- package/src/lib/hooks/use-company-picklists.js.map +1 -0
- package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
- package/src/lib/hooks/use-crm-picklist.js +53 -0
- package/src/lib/hooks/use-crm-picklist.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
- package/src/lib/hooks/use-crm-record-names.js +45 -19
- package/src/lib/hooks/use-crm-record-names.js.map +1 -1
- package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
- package/src/lib/hooks/use-crm-task-picklists.js +45 -0
- package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +14 -0
- package/src/lib/hooks/use-deals.js +29 -0
- package/src/lib/hooks/use-deals.js.map +1 -1
- package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
- package/src/lib/hooks/use-lead-picklists.js +46 -0
- package/src/lib/hooks/use-lead-picklists.js.map +1 -0
- package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
- package/src/lib/hooks/use-lead-source-picklist.js +12 -22
- package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
- package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
- package/src/lib/hooks/use-lead-status-picklist.js +40 -0
- package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
- package/src/lib/model/companies.d.ts +32 -3
- package/src/lib/model/companies.js +133 -16
- package/src/lib/model/companies.js.map +1 -1
- package/src/lib/model/contact-merge.d.ts +27 -3
- package/src/lib/model/contact-merge.js +81 -7
- package/src/lib/model/contact-merge.js.map +1 -1
- package/src/lib/model/contact-record.d.ts +17 -0
- package/src/lib/model/contact-record.js +15 -1
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-update.d.ts +31 -2
- package/src/lib/model/contact-update.js +14 -0
- package/src/lib/model/contact-update.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +5 -0
- package/src/lib/model/crm-list-query.js +8 -0
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/crm-recipes.d.ts +40 -0
- package/src/lib/model/crm-recipes.js +244 -0
- package/src/lib/model/crm-recipes.js.map +1 -0
- package/src/lib/model/crm-record-counts.d.ts +36 -0
- package/src/lib/model/crm-record-counts.js +54 -0
- package/src/lib/model/crm-record-counts.js.map +1 -0
- package/src/lib/model/crm-record-lists.d.ts +23 -0
- package/src/lib/model/crm-record-lists.js +161 -0
- package/src/lib/model/crm-record-lists.js.map +1 -0
- package/src/lib/model/crm-reports.d.ts +73 -3
- package/src/lib/model/crm-reports.js +120 -1
- package/src/lib/model/crm-reports.js.map +1 -1
- package/src/lib/model/crm-sharing.d.ts +21 -3
- package/src/lib/model/crm-sharing.js +27 -6
- package/src/lib/model/crm-sharing.js.map +1 -1
- package/src/lib/model/deal-board-model.d.ts +8 -2
- package/src/lib/model/deal-board-model.js +15 -2
- package/src/lib/model/deal-board-model.js.map +1 -1
- package/src/lib/model/deal-contact-roles.d.ts +45 -0
- package/src/lib/model/deal-contact-roles.js +73 -0
- package/src/lib/model/deal-contact-roles.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +56 -0
- package/src/lib/model/deal-form-model.js +79 -7
- package/src/lib/model/deal-form-model.js.map +1 -1
- package/src/lib/model/download-text-file.d.ts +23 -0
- package/src/lib/model/download-text-file.js +34 -0
- package/src/lib/model/download-text-file.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +36 -2
- package/src/lib/model/lead-filters.js +123 -2
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +38 -0
- package/src/lib/model/lead-source-direction.js +52 -0
- package/src/lib/model/lead-source-direction.js.map +1 -0
- package/src/lib/model/picklist-values.d.ts +119 -0
- package/src/lib/model/picklist-values.js +256 -0
- package/src/lib/model/picklist-values.js.map +1 -0
- package/src/lib/model/record-facts-disclosed.d.ts +81 -0
- package/src/lib/model/record-facts-disclosed.js +281 -0
- package/src/lib/model/record-facts-disclosed.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +215 -34
- package/src/lib/model/record-facts.js +307 -128
- package/src/lib/model/record-facts.js.map +1 -1
- package/src/lib/model/task-picklists.d.ts +52 -0
- package/src/lib/model/task-picklists.js +83 -0
- package/src/lib/model/task-picklists.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +13 -0
- package/src/lib/model/task-routes.js +23 -4
- package/src/lib/model/task-routes.js.map +1 -1
- package/src/lib/plan-entitlements.js +6 -4
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts.js +220 -14
- package/src/lib/server/api-v1/contacts.js.map +1 -1
- package/src/lib/server/api-v1/crm-activities.js +14 -1
- package/src/lib/server/api-v1/crm-activities.js.map +1 -1
- package/src/lib/server/api-v1/crm-companies.js +123 -14
- package/src/lib/server/api-v1/crm-companies.js.map +1 -1
- package/src/lib/server/api-v1/crm-deals.js +291 -26
- package/src/lib/server/api-v1/crm-deals.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +144 -38
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
- package/src/lib/server/api-v1/crm-pipelines.js +8 -2
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
- package/src/lib/server/api-v1/crm-tasks.js +75 -33
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +0 -11
- package/src/lib/server/api-v1/openapi.js +171 -16
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/assign-contact-owner.d.ts +162 -0
- package/src/lib/server/assign-contact-owner.js +340 -0
- package/src/lib/server/assign-contact-owner.js.map +1 -0
- package/src/lib/server/associate-company-by-domain.d.ts +70 -0
- package/src/lib/server/associate-company-by-domain.js +153 -0
- package/src/lib/server/associate-company-by-domain.js.map +1 -0
- package/src/lib/server/automation-steps.d.ts +139 -0
- package/src/lib/server/automation-steps.js +472 -0
- package/src/lib/server/automation-steps.js.map +1 -0
- package/src/lib/server/capture-contact.js +65 -21
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/capture-host-contact.d.ts +104 -0
- package/src/lib/server/capture-host-contact.js +151 -0
- package/src/lib/server/capture-host-contact.js.map +1 -0
- package/src/lib/server/company-children.d.ts +25 -0
- package/src/lib/server/company-children.js +42 -0
- package/src/lib/server/company-children.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +18 -0
- package/src/lib/server/company-delete.js +44 -21
- package/src/lib/server/company-delete.js.map +1 -1
- package/src/lib/server/contact-email-history.js +21 -19
- package/src/lib/server/contact-email-history.js.map +1 -1
- package/src/lib/server/contact-remove.js +27 -2
- package/src/lib/server/contact-remove.js.map +1 -1
- package/src/lib/server/contact-reports-to.d.ts +61 -0
- package/src/lib/server/contact-reports-to.js +114 -0
- package/src/lib/server/contact-reports-to.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +42 -1
- package/src/lib/server/contact-update.js +204 -23
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/convert-host-lead.d.ts +46 -1
- package/src/lib/server/convert-host-lead.js +173 -15
- package/src/lib/server/convert-host-lead.js.map +1 -1
- package/src/lib/server/convert-open-lead.js +12 -5
- package/src/lib/server/convert-open-lead.js.map +1 -1
- package/src/lib/server/crm-record-indexes.d.ts +45 -0
- package/src/lib/server/crm-record-indexes.js +78 -0
- package/src/lib/server/crm-record-indexes.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +9 -1
- package/src/lib/server/crm-sharing.js +23 -3
- package/src/lib/server/crm-sharing.js.map +1 -1
- package/src/lib/server/deal-contact-roles.d.ts +27 -0
- package/src/lib/server/deal-contact-roles.js +89 -0
- package/src/lib/server/deal-contact-roles.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +5 -0
- package/src/lib/server/lead-convert.js +19 -3
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +17 -2
- package/src/lib/server/lead-create.js +46 -14
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +6 -0
- package/src/lib/server/lead-nurturing.js +57 -20
- package/src/lib/server/lead-nurturing.js.map +1 -1
- package/src/lib/server/lead-picklists.d.ts +35 -0
- package/src/lib/server/lead-picklists.js +52 -0
- package/src/lib/server/lead-picklists.js.map +1 -0
- package/src/lib/server/lead-source-picklist.d.ts +1 -13
- package/src/lib/server/lead-source-picklist.js +6 -33
- package/src/lib/server/lead-source-picklist.js.map +1 -1
- package/src/lib/server/merge-contacts.js +22 -4
- package/src/lib/server/merge-contacts.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +53 -0
- package/src/lib/server/person-eraser.js +149 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/person-records.d.ts +69 -0
- package/src/lib/server/person-records.js +317 -0
- package/src/lib/server/person-records.js.map +1 -0
- package/src/lib/server/person-refund.d.ts +97 -0
- package/src/lib/server/person-refund.js +221 -0
- package/src/lib/server/person-refund.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +21 -0
- package/src/lib/server/picklist-values.js +308 -0
- package/src/lib/server/picklist-values.js.map +1 -0
- package/src/lib/server/read-picklist.d.ts +44 -0
- package/src/lib/server/read-picklist.js +65 -0
- package/src/lib/server/read-picklist.js.map +1 -0
- package/src/lib/server/recipe-routes.js +15 -7
- package/src/lib/server/recipe-routes.js.map +1 -1
- package/src/lib/server/record-email-state.js +7 -1
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.d.ts +3 -0
- package/src/lib/server/record-facts.js +233 -31
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-origin.d.ts +27 -0
- package/src/lib/server/record-origin.js +104 -0
- package/src/lib/server/record-origin.js.map +1 -0
- package/src/lib/server/record-timeline.js +15 -2
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/suite-gate.js +4 -3
- package/src/lib/server/suite-gate.js.map +1 -1
- package/src/lib/server/task-routes.js +30 -16
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.d.ts +5 -1
- package/src/lib/server-deal-stage.js +10 -5
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +8 -3
- package/src/lib/server.js +47 -43
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +55 -0
- package/src/lib/testing/memory-firestore.js +335 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
- package/src/lib/testing/stand-in-email-send-index.js +49 -0
- package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
- package/src/lib/transfer/activities.d.ts +30 -0
- package/src/lib/transfer/activities.js +132 -0
- package/src/lib/transfer/activities.js.map +1 -0
- package/src/lib/transfer/aliases.d.ts +22 -0
- package/src/lib/transfer/aliases.js +1342 -0
- package/src/lib/transfer/aliases.js.map +1 -0
- package/src/lib/transfer/common.d.ts +222 -0
- package/src/lib/transfer/common.js +832 -0
- package/src/lib/transfer/common.js.map +1 -0
- package/src/lib/transfer/companies.d.ts +43 -0
- package/src/lib/transfer/companies.js +549 -0
- package/src/lib/transfer/companies.js.map +1 -0
- package/src/lib/transfer/contacts.d.ts +31 -0
- package/src/lib/transfer/contacts.js +727 -0
- package/src/lib/transfer/contacts.js.map +1 -0
- package/src/lib/transfer/deals.d.ts +66 -0
- package/src/lib/transfer/deals.js +698 -0
- package/src/lib/transfer/deals.js.map +1 -0
- package/src/lib/transfer/email-templates-package.d.ts +40 -0
- package/src/lib/transfer/email-templates-package.js +76 -0
- package/src/lib/transfer/email-templates-package.js.map +1 -0
- package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
- package/src/lib/transfer/email-templates-package.server.js +176 -0
- package/src/lib/transfer/email-templates-package.server.js.map +1 -0
- package/src/lib/transfer/fields.d.ts +102 -0
- package/src/lib/transfer/fields.js +1755 -0
- package/src/lib/transfer/fields.js.map +1 -0
- package/src/lib/transfer/leads.d.ts +30 -0
- package/src/lib/transfer/leads.js +582 -0
- package/src/lib/transfer/leads.js.map +1 -0
- package/src/lib/transfer/pipelines.d.ts +21 -0
- package/src/lib/transfer/pipelines.js +172 -0
- package/src/lib/transfer/pipelines.js.map +1 -0
- package/src/lib/transfer/register.d.ts +47 -0
- package/src/lib/transfer/register.js +277 -0
- package/src/lib/transfer/register.js.map +1 -0
- package/src/lib/transfer/tasks.d.ts +30 -0
- package/src/lib/transfer/tasks.js +418 -0
- package/src/lib/transfer/tasks.js.map +1 -0
- package/src/lib/transfer/ui.d.ts +20 -0
- package/src/lib/transfer/ui.js +42 -0
- package/src/lib/transfer/ui.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +0 -42
- package/src/lib/components/company-import-drawer.js +0 -99
- package/src/lib/components/company-import-drawer.js.map +0 -1
- package/src/lib/components/contact-import-drawer.d.ts +0 -46
- package/src/lib/components/contact-import-drawer.js +0 -149
- package/src/lib/components/contact-import-drawer.js.map +0 -1
- package/src/lib/components/crm-export-all-button.d.ts +0 -39
- package/src/lib/components/crm-export-all-button.js +0 -98
- package/src/lib/components/crm-export-all-button.js.map +0 -1
- package/src/lib/components/csv-import-drawer.d.ts +0 -139
- package/src/lib/components/csv-import-drawer.js +0 -757
- package/src/lib/components/csv-import-drawer.js.map +0 -1
- package/src/lib/components/deal-import-drawer.d.ts +0 -24
- package/src/lib/components/deal-import-drawer.js +0 -83
- package/src/lib/components/deal-import-drawer.js.map +0 -1
- package/src/lib/components/lead-import-drawer.d.ts +0 -27
- package/src/lib/components/lead-import-drawer.js +0 -118
- package/src/lib/components/lead-import-drawer.js.map +0 -1
- package/src/lib/components/lead-source-values-card.d.ts +0 -27
- package/src/lib/components/lead-source-values-card.js.map +0 -1
- package/src/lib/components/task-import-drawer.d.ts +0 -24
- package/src/lib/components/task-import-drawer.js +0 -82
- package/src/lib/components/task-import-drawer.js.map +0 -1
- package/src/lib/model/companies-csv.d.ts +0 -36
- package/src/lib/model/companies-csv.js +0 -38
- package/src/lib/model/companies-csv.js.map +0 -1
- package/src/lib/model/contacts-csv.d.ts +0 -54
- package/src/lib/model/contacts-csv.js +0 -67
- package/src/lib/model/contacts-csv.js.map +0 -1
- package/src/lib/model/crm-company-import.d.ts +0 -154
- package/src/lib/model/crm-company-import.js +0 -331
- package/src/lib/model/crm-company-import.js.map +0 -1
- package/src/lib/model/crm-csv.d.ts +0 -239
- package/src/lib/model/crm-csv.js +0 -467
- package/src/lib/model/crm-csv.js.map +0 -1
- package/src/lib/model/crm-deal-import.d.ts +0 -112
- package/src/lib/model/crm-deal-import.js +0 -263
- package/src/lib/model/crm-deal-import.js.map +0 -1
- package/src/lib/model/crm-import.d.ts +0 -250
- package/src/lib/model/crm-import.js +0 -467
- package/src/lib/model/crm-import.js.map +0 -1
- package/src/lib/model/crm-lead-import.d.ts +0 -138
- package/src/lib/model/crm-lead-import.js +0 -446
- package/src/lib/model/crm-lead-import.js.map +0 -1
- package/src/lib/model/crm-task-import.d.ts +0 -94
- package/src/lib/model/crm-task-import.js +0 -275
- package/src/lib/model/crm-task-import.js.map +0 -1
- package/src/lib/model/deals-csv.d.ts +0 -32
- package/src/lib/model/deals-csv.js +0 -36
- package/src/lib/model/deals-csv.js.map +0 -1
- package/src/lib/model/import-column-shapes.d.ts +0 -36
- package/src/lib/model/import-column-shapes.js +0 -76
- package/src/lib/model/import-column-shapes.js.map +0 -1
- package/src/lib/model/lead-source-values.d.ts +0 -82
- package/src/lib/model/lead-source-values.js +0 -149
- package/src/lib/model/lead-source-values.js.map +0 -1
- package/src/lib/model/leads-csv.d.ts +0 -40
- package/src/lib/model/leads-csv.js +0 -40
- package/src/lib/model/leads-csv.js.map +0 -1
- package/src/lib/model/tasks-csv.d.ts +0 -28
- package/src/lib/model/tasks-csv.js +0 -28
- package/src/lib/model/tasks-csv.js.map +0 -1
- package/src/lib/server/companies-import.d.ts +0 -57
- package/src/lib/server/companies-import.js +0 -242
- package/src/lib/server/companies-import.js.map +0 -1
- package/src/lib/server/contacts-import.d.ts +0 -109
- package/src/lib/server/contacts-import.js +0 -393
- package/src/lib/server/contacts-import.js.map +0 -1
- package/src/lib/server/deals-import.d.ts +0 -76
- package/src/lib/server/deals-import.js +0 -248
- package/src/lib/server/deals-import.js.map +0 -1
- package/src/lib/server/export-route.d.ts +0 -60
- package/src/lib/server/export-route.js +0 -447
- package/src/lib/server/export-route.js.map +0 -1
- package/src/lib/server/import-context.d.ts +0 -87
- package/src/lib/server/import-context.js +0 -182
- package/src/lib/server/import-context.js.map +0 -1
- package/src/lib/server/lead-source-values.d.ts +0 -50
- package/src/lib/server/lead-source-values.js +0 -191
- package/src/lib/server/lead-source-values.js.map +0 -1
- package/src/lib/server/leads-import.d.ts +0 -84
- package/src/lib/server/leads-import.js +0 -367
- package/src/lib/server/leads-import.js.map +0 -1
- package/src/lib/server/tasks-import.d.ts +0 -54
- package/src/lib/server/tasks-import.js +0 -160
- package/src/lib/server/tasks-import.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/crm-sharing.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 server half of CRM sharing (AGL-3336) — see `model/crm-sharing.ts`\n * for the model.\n *\n * Three writers, one transaction shape. A record's sharing is RECOMPUTED,\n * never patched: the transaction reads the record, keeps its hand shares\n * (changed by the caller when the act is a share or an unshare), matches\n * every active rule against it as it stands, and writes `visibleTo`,\n * `writeTo`, `sharing` and the list fields derived from `visibleTo` only\n * when any of them moves. So the same function serves\n *\n * - a manager's share or unshare (`crm/sharing`, `share`/`unshare`);\n * - a write to the record anywhere on the server, through the core's\n * record-written seam (`crmSharingRecordWritten`), and a client-direct\n * write's follow-up (`evaluate`);\n * - a rule's recompute over existing records (`rule-save`, `rule-continue`),\n * and its removal (`rule-delete`), in budgeted passes with the progress on\n * the rule.\n */\n\nimport {\n CRM_COLLECTIONS,\n crmActivityLogHasRoom,\n type CrmActivityLink,\n crmListFieldsPatch,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostIdsFromScope } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost, logOrgActivity } from '@aglyn/tenant-data-admin'\nimport { countCrmActivitiesForRecord } from '@aglyn/tenant-data-admin/server/crm-records'\nimport { FieldPath, FieldValue } from 'firebase-admin/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n activeSharingRules,\n CRM_SHARING_CRITERIA_FOR,\n CRM_SHARING_CRITERIA_KEYS,\n CRM_SHARING_FIELD,\n CRM_SHARING_IDS_MAX,\n CRM_SHARING_OBJECT_LABELS,\n CRM_SHARING_RULE_IDS_PATH,\n CRM_SHARING_RULES_MAX,\n CRM_WRITE_SCOPE_FIELD,\n type CrmShareAccess,\n type CrmShareGrant,\n type CrmSharingCriteria,\n type CrmSharingObject,\n type CrmSharingResponse,\n type CrmSharingRule,\n type CrmSharingRuleRun,\n type CrmSharingSite,\n describeShareTargets,\n evaluateRecordGrants,\n isCrmShareAccess,\n isCrmSharingObject,\n planRecordSharing,\n readCrmSharingRule,\n readCrmSharingRules,\n recordHeldScope,\n sharingPlanChanges,\n shareTargetsFrom,\n withManualShares,\n withoutManualShares,\n} from '../model/crm-sharing'\nimport { authorizeOrgCaller, orgHostIds, readCrmRouteScope } from './org-caller'\nimport { crmSuiteRefusal } from './suite-gate'\nimport { authorizeCrmWriter } from './task-routes'\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/*==========================================\n * THE RULES, READ OFF THE ORG DOCUMENT.\n *=========================================*/\n\n/**\n * A few seconds of memory per process: every CRM write asks, and an org\n * with no rules should cost it nothing past the first read. A cached list\n * is never the reason a grant is TAKEN away — see `recomputeRecordSharing`.\n */\nconst RULES_TTL_MS = 10_000\nconst rulesCache = new Map<string, { atMs: number; rules: CrmSharingRule[] }>()\n\n/** Test seam, and what a rule write calls so its own process reads it fresh. */\nexport function forgetCrmSharingRules(orgId?: string): void {\n if (orgId) rulesCache.delete(orgId)\n else rulesCache.clear()\n}\n\nexport async function crmSharingRulesOf(\n firestore: Firestore,\n orgId: string,\n options: { fresh?: boolean; nowMs?: number } = {},\n): Promise<{ rules: CrmSharingRule[]; fresh: boolean }> {\n const nowMs = options.nowMs ?? Date.now()\n const cached = rulesCache.get(orgId)\n if (!options.fresh && cached && nowMs - cached.atMs < RULES_TTL_MS) {\n return { rules: cached.rules, fresh: false }\n }\n const snapshot = await firestore.collection('orgs').doc(orgId).get()\n const rules = readCrmSharingRules(snapshot.data() ?? null)\n rulesCache.set(orgId, { atMs: nowMs, rules })\n return { rules, fresh: true }\n}\n\n/*==========================================\n * ONE RECORD, RECOMPUTED.\n *=========================================*/\n\nexport interface RecomputeRecordSharingInput {\n firestore: Firestore\n orgId: string\n object: CrmSharingObject\n id: string\n /** Every rule the org has, in any state. */\n rules: readonly CrmSharingRule[]\n /** Whether `rules` was read just now; a stale list is re-read before it takes a grant away. */\n fresh: boolean\n /** The hand shares' change, for a share or an unshare. */\n manual?: (grants: Record<string, CrmShareGrant>) => Record<string, CrmShareGrant>\n atMs: number\n}\n\nexport type RecomputeOutcome = 'changed' | 'unchanged' | 'missing'\n\nexport async function recomputeRecordSharing(\n input: RecomputeRecordSharingInput,\n): Promise<RecomputeOutcome> {\n const { firestore, orgId, object, id, atMs } = input\n const ref = firestore.collection('orgs').doc(orgId).collection(object).doc(id)\n return await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(ref)\n if (!snapshot.exists) return 'missing' as const\n const record = (snapshot.data() ?? {}) as Record<string, unknown>\n let rules = input.rules\n /*\n * A GRANT IS NEVER TAKEN AWAY ON A STALE ANSWER. A rule grant whose rule\n * the cached list does not name is dropped only once a fresh read of the\n * org agrees: another process may have saved that rule seconds ago and\n * applied it to this very record.\n */\n if (!input.fresh) {\n const known = new Set(rules.map((rule) => rule.id))\n const stored = record[CRM_SHARING_FIELD] as { ruleIds?: unknown } | undefined\n const ruleIds = Array.isArray(stored?.ruleIds) ? (stored.ruleIds as unknown[]) : []\n if (ruleIds.some((ruleId) => !known.has(String(ruleId)))) {\n const org = await tx.get(firestore.collection('orgs').doc(orgId))\n rules = readCrmSharingRules(org.data() ?? null)\n }\n }\n let grants = evaluateRecordGrants(object, record, rules, atMs)\n if (input.manual) grants = input.manual(grants)\n const plan = planRecordSharing(record, grants)\n if (!sharingPlanChanges(record, plan)) return 'unchanged' as const\n const next = {\n ...record,\n visibleTo: plan.visibleTo,\n [CRM_WRITE_SCOPE_FIELD]: plan.writeTo ?? undefined,\n [CRM_SHARING_FIELD]: plan.sharing ?? undefined,\n }\n tx.update(ref, {\n visibleTo: plan.visibleTo,\n [CRM_WRITE_SCOPE_FIELD]: plan.writeTo ?? FieldValue.delete(),\n [CRM_SHARING_FIELD]: plan.sharing ?? FieldValue.delete(),\n // The scoped list fields are derived from `visibleTo` (AGL-3321), so a\n // target site's folded search finds the record the moment it can see it.\n ...crmListFieldsPatch(object, next),\n })\n return 'changed' as const\n })\n}\n\n/** Several records at once, a few in flight. */\nasync function recomputeMany(\n input: Omit<RecomputeRecordSharingInput, 'id'>,\n ids: readonly string[],\n): Promise<{ changed: string[]; missing: number }> {\n const queue = [...new Set(ids)]\n const changed: string[] = []\n let missing = 0\n const worker = async () => {\n for (let id = queue.shift(); id !== undefined; id = queue.shift()) {\n const outcome = await recomputeRecordSharing({ ...input, id })\n if (outcome === 'changed') changed.push(id)\n if (outcome === 'missing') missing += 1\n }\n }\n await Promise.all(Array.from({ length: Math.min(8, queue.length) }, worker))\n return { changed, missing }\n}\n\n/**\n * Re-evaluates the org's rules for these records — the one thing a write\n * anywhere owes the sharing. An org with no active rule for the object\n * costs one cached read and nothing else; grants of a rule switched off\n * are taken away by that rule's own removal pass, not here.\n */\nexport async function evaluateCrmSharing(\n firestore: Firestore,\n orgId: string,\n object: CrmSharingObject,\n ids: readonly string[],\n): Promise<{ changed: number; missing: number }> {\n const { rules, fresh } = await crmSharingRulesOf(firestore, orgId)\n if (!activeSharingRules(rules, object).length) return { changed: 0, missing: 0 }\n const outcome = await recomputeMany(\n { firestore, orgId, object, rules, fresh, atMs: Date.now() },\n ids,\n )\n return { changed: outcome.changed.length, missing: outcome.missing }\n}\n\n/**\n * The core's record-written seam (AGL-3336): every server writer of a lead,\n * a contact, a company or a deal restamps it, and the restamp tells this.\n * Never throws — the write it is told about already landed.\n */\nexport async function crmSharingRecordWritten(event: {\n path: string\n collection: string\n}): Promise<void> {\n if (!isCrmSharingObject(event.collection)) return\n const segments = event.path.split('/')\n if (segments.length !== 4 || segments[0] !== 'orgs' || segments[2] !== event.collection) return\n try {\n await evaluateCrmSharing(\n firebaseAdmin.app().firestore(),\n segments[1],\n event.collection,\n [segments[3]],\n )\n } catch (error) {\n console.error(`[crm] sharing rules could not be evaluated for ${event.path}`, error)\n }\n}\n\n/*==========================================\n * THE AUDIT: a line on the record, a line in the org's feed.\n *=========================================*/\n\nconst LINK_FIELD: Readonly<Record<CrmSharingObject, keyof CrmActivityLink>> = {\n leads: 'leadId',\n contacts: 'contactId',\n companies: 'companyId',\n deals: 'dealId',\n}\n\nconst ORG_TARGET: Readonly<Record<CrmSharingObject, 'lead' | 'contact' | 'company' | 'deal'>> = {\n leads: 'lead',\n contacts: 'contact',\n companies: 'company',\n deals: 'deal',\n}\n\n/**\n * The entry on the record's Activity: who shared it with which sites, or\n * took a share away. Visible to the sites that hold the record and to the\n * sites it names, so both sides of a share read how it came about.\n */\nasync function fileSharingActivity(\n firestore: Firestore,\n input: {\n orgId: string\n object: CrmSharingObject\n id: string\n body: string\n targets: readonly string[]\n by: { uid: string; name: string }\n atMs: number\n },\n): Promise<void> {\n try {\n const orgRef = firestore.collection('orgs').doc(input.orgId)\n const record = (await orgRef.collection(input.object).doc(input.id).get()).data() ?? {}\n const held = recordHeldScope(record)\n const visibleTo = [...new Set([...held, ...input.targets])]\n if (!visibleTo.length) return\n const link: CrmActivityLink = { [LINK_FIELD[input.object]]: input.id }\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) return\n const hostId =\n typeof record['hostId'] === 'string' && record['hostId']\n ? record['hostId']\n : (hostIdsFromScope(held)[0] ?? '')\n await orgRef\n .collection(CRM_COLLECTIONS.activities)\n .doc(createResourceUid())\n .create({\n kind: 'note',\n body: input.body,\n atMs: input.atMs,\n byUid: input.by.uid,\n ...(input.by.name ? { byName: input.by.name } : {}),\n ...link,\n hostId,\n visibleTo,\n sourcePluginId: BUNDLE_ID,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n } catch (error) {\n console.error('[crm] the sharing entry could not be written on the record', input.id, error)\n }\n}\n\n/*==========================================\n * THE ORG'S SITES.\n *=========================================*/\n\nexport async function crmSharingSites(firestore: Firestore, orgId: string): Promise<CrmSharingSite[]> {\n const hosts = await firestore.collection('hosts').where('orgId', '==', orgId).get()\n return hosts.docs\n .map((doc) => {\n const displayName = String(doc.get('displayName') ?? '').trim()\n const subdomain = String(doc.get('subdomain') ?? '').trim()\n return { id: String(doc.id), name: displayName || subdomain || String(doc.id) }\n })\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/*==========================================\n * A RULE, SAVED FROM A REQUEST.\n *=========================================*/\n\nfunction listFrom(raw: unknown, max: number): string[] {\n if (!Array.isArray(raw)) return []\n return [\n ...new Set(\n raw.map((entry) => String(entry ?? '').trim().slice(0, 200)).filter(Boolean),\n ),\n ].slice(0, max)\n}\n\n/**\n * The rule a request describes, judged: an object, a name, sites that are\n * the org's own, targets that are the org's own or all sites. `null` with\n * the reason when it cannot be saved.\n */\nexport function ruleFromRequest(\n raw: unknown,\n context: { hostIds: readonly string[]; existing: CrmSharingRule | null; uid: string; nowMs: number },\n): { rule: CrmSharingRule; error: null } | { rule: null; error: string } {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { rule: null, error: 'Describe the rule.' }\n }\n const value = raw as Record<string, unknown>\n const object = value['object']\n if (!isCrmSharingObject(object)) {\n return { rule: null, error: 'Choose which records the rule shares.' }\n }\n const name = String(value['name'] ?? '').trim().slice(0, 120)\n if (!name) return { rule: null, error: 'Name the rule.' }\n const own = new Set(context.hostIds)\n const sourceHostIds = listFrom(value['sourceHostIds'], 30)\n if (sourceHostIds.some((id) => !own.has(id))) {\n return { rule: null, error: 'A site the rule names is not one of this organization’s.' }\n }\n const targets = shareTargetsFrom(value['targets'])\n if (!targets) return { rule: null, error: 'Choose the sites to share with, or All sites.' }\n if (hostIdsFromScope(targets).some((id) => !own.has(id))) {\n return { rule: null, error: 'A site the rule shares with is not one of this organization’s.' }\n }\n const access = value['access']\n if (access !== undefined && !isCrmShareAccess(access)) {\n return { rule: null, error: 'Access is read-only, or read and edit.' }\n }\n const rawCriteria = (value['criteria'] ?? {}) as Record<string, unknown>\n const criteria: CrmSharingCriteria = {}\n for (const key of CRM_SHARING_CRITERIA_KEYS) {\n if (!CRM_SHARING_CRITERIA_FOR[object].includes(key)) continue\n const list = listFrom(rawCriteria[key], 20)\n if (list.length) criteria[key] = list\n }\n const existing = context.existing\n const rule = readCrmSharingRule({\n id: existing?.id ?? createResourceUid(),\n name,\n object,\n enabled: value['enabled'] !== false,\n sourceHostIds,\n criteria,\n targets,\n access: (access as CrmShareAccess | undefined) ?? 'read',\n createdByUid: existing?.createdByUid ?? context.uid,\n createdAtMs: existing?.createdAtMs ?? context.nowMs,\n updatedAtMs: context.nowMs,\n })\n if (!rule) return { rule: null, error: 'The rule could not be read.' }\n return { rule, error: null }\n}\n\n/** A rule as the org document stores it — no `undefined` anywhere. */\nfunction storedRule(rule: CrmSharingRule): Record<string, unknown> {\n return JSON.parse(JSON.stringify(rule)) as Record<string, unknown>\n}\n\n/**\n * Writes the org's rule list through `change`, in a transaction over the org\n * document, and answers the list as written.\n */\nasync function writeRules(\n firestore: Firestore,\n orgId: string,\n change: (rules: CrmSharingRule[]) => CrmSharingRule[] | { error: string },\n): Promise<{ rules: CrmSharingRule[]; error: null } | { rules: null; error: string }> {\n const orgRef = firestore.collection('orgs').doc(orgId)\n const result = await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(orgRef)\n const next = change(readCrmSharingRules(snapshot.data() ?? null))\n if ('error' in next) return { rules: null, error: next.error }\n tx.update(orgRef, new FieldPath('crm', 'sharingRules'), next.map(storedRule))\n return { rules: next, error: null }\n })\n forgetCrmSharingRules(orgId)\n return result as { rules: CrmSharingRule[]; error: null } | { rules: null; error: string }\n}\n\n/** Replaces one rule's run, leaving every other rule as it is. */\nasync function writeRun(\n firestore: Firestore,\n orgId: string,\n ruleId: string,\n run: CrmSharingRuleRun,\n options: { dropRule?: boolean } = {},\n): Promise<CrmSharingRule | null> {\n const written = await writeRules(firestore, orgId, (rules) =>\n options.dropRule\n ? rules.filter((rule) => rule.id !== ruleId)\n : rules.map((rule) => (rule.id === ruleId ? { ...rule, run } : rule)),\n )\n return written.rules?.find((rule) => rule.id === ruleId) ?? null\n}\n\n/*==========================================\n * A RULE'S RUN: apply it everywhere, or take it away.\n *=========================================*/\n\nconst RUN_PAGE = 200\n\n/**\n * One budgeted pass of a rule's run, with the progress written on the rule\n * after every page so a reopened settings page shows where it stands and\n * `rule-continue` picks up from the cursor.\n *\n * `apply` walks the object's whole collection by document id — a rule\n * matches on fields no index narrows to (a facet's tags, a site that\n * captured the person) — and writes only the records whose sharing moves.\n * `remove` asks for exactly the records the rule holds a grant on\n * (`sharing.ruleIds`), each of which the recompute then takes it off; a\n * page that makes no progress fails the run rather than looping.\n */\nexport async function advanceSharingRuleRun(\n firestore: Firestore,\n orgId: string,\n ruleId: string,\n deadlineMs: number,\n): Promise<{ rule: CrmSharingRule | null; more: boolean }> {\n const read = await crmSharingRulesOf(firestore, orgId, { fresh: true })\n let rule = read.rules.find((entry) => entry.id === ruleId) ?? null\n if (!rule?.run || rule.run.status !== 'running') return { rule, more: false }\n const records = firestore.collection('orgs').doc(orgId).collection(rule.object)\n let run: CrmSharingRuleRun = { ...rule.run }\n const seen = new Set<string>()\n try {\n while (Date.now() < deadlineMs) {\n const { rules } = await crmSharingRulesOf(firestore, orgId, { fresh: true })\n let page: FirebaseFirestore.QuerySnapshot\n if (run.mode === 'apply') {\n let query = records.orderBy(FieldPath.documentId()).limit(RUN_PAGE)\n if (run.cursor) query = query.startAfter(run.cursor)\n page = await query.get()\n } else {\n page = await records.where(CRM_SHARING_RULE_IDS_PATH, 'array-contains', ruleId).limit(RUN_PAGE).get()\n }\n const atMs = Date.now()\n // Only the records whose sharing would move are written.\n const moving = page.docs\n .filter((doc) => {\n const record = (doc.data() ?? {}) as Record<string, unknown>\n const plan = planRecordSharing(record, evaluateRecordGrants(rule!.object, record, rules, atMs))\n return sharingPlanChanges(record, plan)\n })\n .map((doc) => doc.id)\n if (run.mode === 'remove' && page.docs.length && page.docs.every((doc) => seen.has(doc.id))) {\n throw new Error('the rule’s grants did not come off these records')\n }\n for (const doc of page.docs) seen.add(doc.id)\n const outcome = await recomputeMany(\n { firestore, orgId, object: rule.object, rules, fresh: true, atMs },\n moving,\n )\n const done = run.mode === 'apply' ? page.size < RUN_PAGE : page.empty\n run = {\n ...run,\n processed: run.processed + page.size,\n changed: run.changed + outcome.changed.length,\n cursor: run.mode === 'apply' ? (page.docs[page.docs.length - 1]?.id ?? run.cursor ?? null) : null,\n ...(done ? { status: 'done' as const, finishedAtMs: Date.now() } : {}),\n }\n rule = await writeRun(firestore, orgId, ruleId, run, {\n dropRule: done && rule.deleting === true,\n })\n if (done || !rule) return { rule, more: false }\n }\n return { rule, more: true }\n } catch (error) {\n console.error('[crm] a sharing rule run failed', orgId, ruleId, error)\n rule = await writeRun(firestore, orgId, ruleId, {\n ...run,\n status: 'failed',\n finishedAtMs: Date.now(),\n error: 'The recompute stopped. Resume it to carry on from where it stopped.',\n })\n return { rule, more: false }\n }\n}\n\n/*==========================================\n * THE ROUTE.\n *=========================================*/\n\n/** How long one request works on a rule before it answers and the page asks again. */\nconst RUN_BUDGET_MS = 20_000\n\nconst MANAGERS_ONLY =\n 'Sharing records across sites is for the workspace’s owners and admins.'\n\nfunction readIds(raw: unknown): string[] | null {\n if (!Array.isArray(raw)) return null\n const ids = [\n ...new Set(\n raw.map((entry) => String(entry ?? '').trim().slice(0, 256)).filter((id) => id && !id.includes('/')),\n ),\n ].slice(0, CRM_SHARING_IDS_MAX)\n return ids.length ? ids : null\n}\n\n/**\n * `POST /api/crm/sharing` — `{ action, hostId | orgId, … }`.\n *\n * Every action but `evaluate` is an org manager's: an OWNER or ADMIN of the\n * whole workspace (`authorizeOrgCaller(…, 'manage-org')`), under a site's\n * hub or the org's. `evaluate` is what a client-direct write owes the rules\n * and is open to whoever may write the CRM. The plan gate is the CRM's own\n * (`crmSuiteRefusal`): sharing is only meaningful across several sites, and\n * the number of sites a workspace may have is already the plan's.\n */\nexport const crmSharingHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const body = (req.body ?? {}) as Record<string, unknown>\n const scope = readCrmRouteScope(body)\n if (!scope) {\n res.status(400).json({ error: 'Missing hostId' })\n return\n }\n const action = String(body['action'] ?? '')\n const firestore = firebaseAdmin.app().firestore()\n try {\n if (action === 'evaluate') {\n const object = body['object']\n const ids = readIds(body['ids'])\n if (!isCrmSharingObject(object) || !ids) {\n res.status(400).json({ error: 'Name the records.' })\n return\n }\n const writer = await authorizeCrmWriter(req, scope, { suiteAct: null })\n if (writer.ok === false) {\n res.status(writer.status).json(writer.body)\n return\n }\n const outcome = await evaluateCrmSharing(firestore, writer.orgId, object, ids)\n const answer: CrmSharingResponse = { ok: true, ...outcome }\n res.status(200).json(answer)\n return\n }\n\n const orgId =\n scope.level === 'org'\n ? scope.orgId\n : ((await getOrgForHost(scope.hostId).catch(() => null))?.orgId ?? '')\n if (!orgId) {\n res.status(404).json({ error: 'This site has no organization, so it has no CRM.' })\n return\n }\n const caller = await authorizeOrgCaller(req, orgId, {\n needs: 'manage-org',\n refusal: MANAGERS_ONLY,\n })\n if (caller.ok === false) {\n res.status(caller.status).json({ error: caller.error })\n return\n }\n const suite = crmSuiteRefusal(caller.org, 'Sharing records across sites')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const actor = { uid: caller.uid, email: caller.email }\n const byName = caller.name || caller.email || ''\n const nowMs = Date.now()\n\n if (action === 'sites') {\n const answer: CrmSharingResponse = { ok: true, sites: await crmSharingSites(firestore, orgId) }\n res.status(200).json(answer)\n return\n }\n\n if (action === 'share' || action === 'unshare') {\n const object = body['object']\n const ids = readIds(body['ids'])\n if (!isCrmSharingObject(object) || !ids) {\n res.status(400).json({ error: 'Name the records to share.' })\n return\n }\n const targets = shareTargetsFrom(body['targets'])\n if (!targets) {\n res.status(400).json({ error: 'Choose the sites, or All sites.' })\n return\n }\n const sites = await crmSharingSites(firestore, orgId)\n const own = new Set(sites.map((site) => site.id))\n if (hostIdsFromScope(targets).some((id) => !own.has(id))) {\n res.status(400).json({ error: 'A site named is not one of this organization’s.' })\n return\n }\n const access: CrmShareAccess = body['access'] === 'edit' ? 'edit' : 'read'\n const { rules, fresh } = await crmSharingRulesOf(firestore, orgId)\n const outcome = await recomputeMany(\n {\n firestore,\n orgId,\n object,\n rules,\n fresh,\n atMs: nowMs,\n manual: (grants) =>\n action === 'share'\n ? withManualShares(grants, targets, access, { uid: caller.uid, name: byName }, nowMs)\n : withoutManualShares(grants, targets),\n },\n ids,\n )\n const siteName = (id: string) => sites.find((site) => site.id === id)?.name ?? 'a site'\n const named = describeShareTargets(targets, siteName)\n const sentence =\n action === 'share'\n ? `Shared with ${named}${access === 'edit' ? ' (read and edit)' : ' (read-only)'}`\n : `Stopped sharing with ${named}`\n await Promise.all(\n outcome.changed.map((id) =>\n fileSharingActivity(firestore, {\n orgId,\n object,\n id,\n body: sentence,\n targets,\n by: { uid: caller.uid, name: byName },\n atMs: nowMs,\n }),\n ),\n )\n if (outcome.changed.length) {\n const label = CRM_SHARING_OBJECT_LABELS[object]\n await logOrgActivity(\n orgId,\n actor,\n outcome.changed.length === 1\n ? `${sentence}: one ${label.one}`\n : `${sentence}: ${outcome.changed.length} ${label.many.toLowerCase()}`,\n {\n type: ORG_TARGET[object],\n ...(outcome.changed.length === 1 ? { id: outcome.changed[0] } : {}),\n },\n ).catch(() => undefined)\n }\n const answer: CrmSharingResponse = {\n ok: true,\n changed: outcome.changed.length,\n missing: outcome.missing,\n }\n res.status(200).json(answer)\n return\n }\n\n if (action === 'rule-save') {\n const hostIds = await orgHostIds(firestore, orgId)\n const raw = body['rule']\n const ruleId = raw && typeof raw === 'object' ? String((raw as Record<string, unknown>)['id'] ?? '') : ''\n let saved: CrmSharingRule | null = null\n let error: string | null = null\n const written = await writeRules(firestore, orgId, (rules) => {\n const existing = ruleId ? (rules.find((rule) => rule.id === ruleId) ?? null) : null\n if (ruleId && !existing) return { error: 'That rule no longer exists.' }\n if (existing?.deleting) return { error: 'That rule is being deleted.' }\n if (!existing && rules.length >= CRM_SHARING_RULES_MAX) {\n return { error: `A workspace keeps at most ${CRM_SHARING_RULES_MAX} sharing rules.` }\n }\n const judged = ruleFromRequest(raw, { hostIds, existing, uid: caller.uid, nowMs })\n if (!judged.rule) {\n error = judged.error\n return { error: judged.error }\n }\n /*\n * An enabled rule walks every record — it may now match some and not\n * others. A disabled one only has grants to take away, and asks for\n * exactly the records holding them.\n */\n saved = {\n ...judged.rule,\n run: {\n status: 'running',\n mode: judged.rule.enabled ? 'apply' : 'remove',\n processed: 0,\n changed: 0,\n cursor: null,\n startedAtMs: nowMs,\n },\n }\n return existing\n ? rules.map((rule) => (rule.id === existing.id ? saved! : rule))\n : [...rules, saved]\n })\n if (!written.rules || !saved) {\n res.status(400).json({ error: error ?? written.error ?? 'The rule could not be saved.' })\n return\n }\n const rule = saved as CrmSharingRule\n await logOrgActivity(\n orgId,\n actor,\n `${ruleId ? 'Changed' : 'Added'} the sharing rule “${rule.name}”`,\n { type: `${BUNDLE_ID}:sharingRule`, id: rule.id, name: rule.name },\n ).catch(() => undefined)\n const advanced = await advanceSharingRuleRun(firestore, orgId, rule.id, Date.now() + RUN_BUDGET_MS)\n const answer: CrmSharingResponse = { ok: true, rule: advanced.rule, more: advanced.more }\n res.status(200).json(answer)\n return\n }\n\n if (action === 'rule-delete' || action === 'rule-continue') {\n const ruleId = String(body['ruleId'] ?? '').trim()\n if (!ruleId) {\n res.status(400).json({ error: 'Name the rule.' })\n return\n }\n if (action === 'rule-delete') {\n let name = ''\n const written = await writeRules(firestore, orgId, (rules) => {\n const existing = rules.find((rule) => rule.id === ruleId)\n if (!existing) return { error: 'That rule no longer exists.' }\n name = existing.name\n return rules.map((rule) =>\n rule.id === ruleId\n ? {\n ...rule,\n enabled: false,\n deleting: true,\n updatedAtMs: nowMs,\n run: {\n status: 'running' as const,\n mode: 'remove' as const,\n processed: 0,\n changed: 0,\n cursor: null,\n startedAtMs: nowMs,\n },\n }\n : rule,\n )\n })\n if (!written.rules) {\n res.status(404).json({ error: written.error })\n return\n }\n await logOrgActivity(orgId, actor, `Deleted the sharing rule “${name}”`, {\n type: `${BUNDLE_ID}:sharingRule`,\n id: ruleId,\n name,\n }).catch(() => undefined)\n } else {\n // A failed run resumes from its cursor.\n await writeRules(firestore, orgId, (rules) =>\n rules.map((rule) =>\n rule.id === ruleId && rule.run?.status === 'failed'\n ? { ...rule, run: { ...rule.run, status: 'running' as const } }\n : rule,\n ),\n )\n }\n const advanced = await advanceSharingRuleRun(firestore, orgId, ruleId, Date.now() + RUN_BUDGET_MS)\n const answer: CrmSharingResponse = { ok: true, rule: advanced.rule, more: advanced.more }\n res.status(200).json(answer)\n return\n }\n\n res.status(400).json({ error: 'Unknown sharing action.' })\n } catch (error) {\n console.error('[crm] sharing failed', action, error)\n res.status(500).json({ error: 'The sharing could not be saved. Try again.' })\n }\n}\n\n"],"names":["CRM_COLLECTIONS","crmActivityLogHasRoom","crmListFieldsPatch","createResourceUid","hostIdsFromScope","firebaseAdmin","getOrgForHost","logOrgActivity","countCrmActivitiesForRecord","FieldPath","FieldValue","BUNDLE_ID","activeSharingRules","CRM_SHARING_CRITERIA_FOR","CRM_SHARING_CRITERIA_KEYS","CRM_SHARING_FIELD","CRM_SHARING_IDS_MAX","CRM_SHARING_OBJECT_LABELS","CRM_SHARING_RULE_IDS_PATH","CRM_SHARING_RULES_MAX","CRM_WRITE_SCOPE_FIELD","describeShareTargets","evaluateRecordGrants","isCrmShareAccess","isCrmSharingObject","planRecordSharing","readCrmSharingRule","readCrmSharingRules","recordHeldScope","sharingPlanChanges","shareTargetsFrom","withManualShares","withoutManualShares","authorizeOrgCaller","orgHostIds","readCrmRouteScope","crmSuiteRefusal","authorizeCrmWriter","RULES_TTL_MS","rulesCache","Map","forgetCrmSharingRules","orgId","delete","clear","crmSharingRulesOf","firestore","options","snapshot","nowMs","Date","now","cached","get","fresh","atMs","rules","collection","doc","data","set","recomputeRecordSharing","input","object","id","ref","runTransaction","tx","plan","exists","record","known","Set","map","rule","stored","ruleIds","Array","isArray","some","ruleId","has","String","org","grants","manual","next","visibleTo","writeTo","undefined","sharing","update","recomputeMany","ids","queue","changed","missing","worker","shift","outcome","push","Promise","all","from","length","Math","min","evaluateCrmSharing","crmSharingRecordWritten","event","segments","path","split","app","error","console","LINK_FIELD","leads","contacts","companies","deals","ORG_TARGET","fileSharingActivity","orgRef","held","targets","link","hostId","activities","create","kind","body","byUid","by","uid","name","byName","sourcePluginId","createdAt","serverTimestamp","updatedAt","crmSharingSites","hosts","where","docs","displayName","trim","subdomain","sort","a","b","localeCompare","listFrom","raw","max","entry","slice","filter","Boolean","ruleFromRequest","context","value","own","hostIds","sourceHostIds","access","rawCriteria","criteria","key","includes","list","existing","enabled","createdByUid","createdAtMs","updatedAtMs","storedRule","JSON","parse","stringify","writeRules","change","result","writeRun","run","written","dropRule","find","RUN_PAGE","advanceSharingRuleRun","deadlineMs","read","status","more","records","seen","page","mode","query","orderBy","documentId","limit","cursor","startAfter","moving","every","Error","add","done","size","empty","processed","finishedAtMs","deleting","RUN_BUDGET_MS","MANAGERS_ONLY","readIds","crmSharingHandler","req","res","method","setHeader","json","scope","action","writer","suiteAct","ok","answer","level","catch","caller","needs","refusal","suite","actor","email","sites","site","siteName","named","sentence","label","one","many","toLowerCase","type","saved","judged","startedAtMs","advanced"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/crm-sharing.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 server half of CRM sharing (AGL-3336) — see `model/crm-sharing.ts`\n * for the model.\n *\n * Three writers, one transaction shape. A record's sharing is RECOMPUTED,\n * never patched: the transaction reads the record, keeps its hand shares\n * (changed by the caller when the act is a share or an unshare), matches\n * every active rule against it as it stands, and writes `visibleTo`,\n * `writeTo`, `sharing` and the list fields derived from `visibleTo` only\n * when any of them moves. So the same function serves\n *\n * - a manager's share or unshare (`crm/sharing`, `share`/`unshare`);\n * - a write to the record anywhere on the server, through the core's\n * record-written seam (`crmSharingRecordWritten`), and a client-direct\n * write's follow-up (`evaluate`);\n * - a rule's recompute over existing records (`rule-save`, `rule-continue`),\n * and its removal (`rule-delete`), in budgeted passes with the progress on\n * the rule.\n */\n\nimport {\n CRM_COLLECTIONS,\n crmActivityLogHasRoom,\n type CrmActivityLink,\n crmListFieldsPatch,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostIdsFromScope } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost, logOrgActivity } from '@aglyn/tenant-data-admin'\nimport { countCrmActivitiesForRecord } from '@aglyn/tenant-data-admin/server/crm-records'\nimport { FieldPath, FieldValue } from 'firebase-admin/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n activeSharingRules,\n CRM_SHARING_CRITERIA_FOR,\n CRM_SHARING_CRITERIA_KEYS,\n CRM_SHARING_FIELD,\n CRM_SHARING_IDS_MAX,\n CRM_SHARING_OBJECT_LABELS,\n CRM_SHARING_RULE_IDS_PATH,\n CRM_SHARING_RULES_MAX,\n CRM_WRITE_SCOPE_FIELD,\n type CrmShareAccess,\n type CrmShareGrant,\n type CrmSharingContext,\n type CrmSharingCriteria,\n type CrmSharingObject,\n type CrmSharingResponse,\n type CrmSharingRule,\n type CrmSharingRuleRun,\n type CrmSharingSite,\n describeShareTargets,\n evaluateRecordGrants,\n isCrmShareAccess,\n isCrmSharingObject,\n planRecordSharing,\n readCrmSharingRule,\n readCrmSharingRules,\n recordHeldScope,\n sharingPlanChanges,\n shareTargetsFrom,\n sharingRulesAskLeadSourceDirection,\n withManualShares,\n withoutManualShares,\n} from '../model/crm-sharing'\nimport { readLeadSourcePicklist } from './lead-source-picklist'\nimport { authorizeOrgCaller, orgHostIds, readCrmRouteScope } from './org-caller'\nimport { crmSuiteRefusal } from './suite-gate'\nimport { authorizeCrmWriter } from './task-routes'\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/*==========================================\n * THE RULES, READ OFF THE ORG DOCUMENT.\n *=========================================*/\n\n/**\n * A few seconds of memory per process: every CRM write asks, and an org\n * with no rules should cost it nothing past the first read. A cached list\n * is never the reason a grant is TAKEN away — see `recomputeRecordSharing`.\n */\nconst RULES_TTL_MS = 10_000\nconst rulesCache = new Map<string, { atMs: number; rules: CrmSharingRule[] }>()\n\n/** Test seam, and what a rule write calls so its own process reads it fresh. */\nexport function forgetCrmSharingRules(orgId?: string): void {\n if (orgId) rulesCache.delete(orgId)\n else rulesCache.clear()\n}\n\nexport async function crmSharingRulesOf(\n firestore: Firestore,\n orgId: string,\n options: { fresh?: boolean; nowMs?: number } = {},\n): Promise<{ rules: CrmSharingRule[]; fresh: boolean }> {\n const nowMs = options.nowMs ?? Date.now()\n const cached = rulesCache.get(orgId)\n if (!options.fresh && cached && nowMs - cached.atMs < RULES_TTL_MS) {\n return { rules: cached.rules, fresh: false }\n }\n const snapshot = await firestore.collection('orgs').doc(orgId).get()\n const rules = readCrmSharingRules(snapshot.data() ?? null)\n rulesCache.set(orgId, { atMs: nowMs, rules })\n return { rules, fresh: true }\n}\n\n/**\n * What the rules for `object` are evaluated against beyond the record: the\n * org's Lead source list, read only when an active rule asks a lead source\n * direction (AGL-3511) — an org whose rules never do pays no read for it.\n */\nexport async function crmSharingContextFor(\n firestore: Firestore,\n orgId: string,\n rules: readonly CrmSharingRule[],\n object: CrmSharingObject,\n): Promise<CrmSharingContext> {\n if (!sharingRulesAskLeadSourceDirection(rules, object)) return {}\n return { leadSources: await readLeadSourcePicklist(firestore, orgId) }\n}\n\n/*==========================================\n * ONE RECORD, RECOMPUTED.\n *=========================================*/\n\nexport interface RecomputeRecordSharingInput {\n firestore: Firestore\n orgId: string\n object: CrmSharingObject\n id: string\n /** Every rule the org has, in any state. */\n rules: readonly CrmSharingRule[]\n /** Whether `rules` was read just now; a stale list is re-read before it takes a grant away. */\n fresh: boolean\n /** The hand shares' change, for a share or an unshare. */\n manual?: (grants: Record<string, CrmShareGrant>) => Record<string, CrmShareGrant>\n /** What the rules are evaluated against beyond the record — see {@link crmSharingContextFor}. */\n context?: CrmSharingContext\n atMs: number\n}\n\nexport type RecomputeOutcome = 'changed' | 'unchanged' | 'missing'\n\nexport async function recomputeRecordSharing(\n input: RecomputeRecordSharingInput,\n): Promise<RecomputeOutcome> {\n const { firestore, orgId, object, id, atMs } = input\n const ref = firestore.collection('orgs').doc(orgId).collection(object).doc(id)\n return await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(ref)\n if (!snapshot.exists) return 'missing' as const\n const record = (snapshot.data() ?? {}) as Record<string, unknown>\n let rules = input.rules\n /*\n * A GRANT IS NEVER TAKEN AWAY ON A STALE ANSWER. A rule grant whose rule\n * the cached list does not name is dropped only once a fresh read of the\n * org agrees: another process may have saved that rule seconds ago and\n * applied it to this very record.\n */\n if (!input.fresh) {\n const known = new Set(rules.map((rule) => rule.id))\n const stored = record[CRM_SHARING_FIELD] as { ruleIds?: unknown } | undefined\n const ruleIds = Array.isArray(stored?.ruleIds) ? (stored.ruleIds as unknown[]) : []\n if (ruleIds.some((ruleId) => !known.has(String(ruleId)))) {\n const org = await tx.get(firestore.collection('orgs').doc(orgId))\n rules = readCrmSharingRules(org.data() ?? null)\n }\n }\n // A rule read afresh above may ask a direction the caller's context was not read for.\n const context =\n input.context?.leadSources || rules === input.rules\n ? input.context\n : await crmSharingContextFor(firestore, orgId, rules, object)\n let grants = evaluateRecordGrants(object, record, rules, atMs, context)\n if (input.manual) grants = input.manual(grants)\n const plan = planRecordSharing(record, grants)\n if (!sharingPlanChanges(record, plan)) return 'unchanged' as const\n const next = {\n ...record,\n visibleTo: plan.visibleTo,\n [CRM_WRITE_SCOPE_FIELD]: plan.writeTo ?? undefined,\n [CRM_SHARING_FIELD]: plan.sharing ?? undefined,\n }\n tx.update(ref, {\n visibleTo: plan.visibleTo,\n [CRM_WRITE_SCOPE_FIELD]: plan.writeTo ?? FieldValue.delete(),\n [CRM_SHARING_FIELD]: plan.sharing ?? FieldValue.delete(),\n // The scoped list fields are derived from `visibleTo` (AGL-3321), so a\n // target site's folded search finds the record the moment it can see it.\n ...crmListFieldsPatch(object, next),\n })\n return 'changed' as const\n })\n}\n\n/** Several records at once, a few in flight. */\nasync function recomputeMany(\n input: Omit<RecomputeRecordSharingInput, 'id'>,\n ids: readonly string[],\n): Promise<{ changed: string[]; missing: number }> {\n const queue = [...new Set(ids)]\n const changed: string[] = []\n let missing = 0\n const worker = async () => {\n for (let id = queue.shift(); id !== undefined; id = queue.shift()) {\n const outcome = await recomputeRecordSharing({ ...input, id })\n if (outcome === 'changed') changed.push(id)\n if (outcome === 'missing') missing += 1\n }\n }\n await Promise.all(Array.from({ length: Math.min(8, queue.length) }, worker))\n return { changed, missing }\n}\n\n/**\n * Re-evaluates the org's rules for these records — the one thing a write\n * anywhere owes the sharing. An org with no active rule for the object\n * costs one cached read and nothing else; grants of a rule switched off\n * are taken away by that rule's own removal pass, not here.\n */\nexport async function evaluateCrmSharing(\n firestore: Firestore,\n orgId: string,\n object: CrmSharingObject,\n ids: readonly string[],\n): Promise<{ changed: number; missing: number }> {\n const { rules, fresh } = await crmSharingRulesOf(firestore, orgId)\n if (!activeSharingRules(rules, object).length) return { changed: 0, missing: 0 }\n const context = await crmSharingContextFor(firestore, orgId, rules, object)\n const outcome = await recomputeMany(\n { firestore, orgId, object, rules, fresh, context, atMs: Date.now() },\n ids,\n )\n return { changed: outcome.changed.length, missing: outcome.missing }\n}\n\n/**\n * The core's record-written seam (AGL-3336): every server writer of a lead,\n * a contact, a company or a deal restamps it, and the restamp tells this.\n * Never throws — the write it is told about already landed.\n */\nexport async function crmSharingRecordWritten(event: {\n path: string\n collection: string\n}): Promise<void> {\n if (!isCrmSharingObject(event.collection)) return\n const segments = event.path.split('/')\n if (segments.length !== 4 || segments[0] !== 'orgs' || segments[2] !== event.collection) return\n try {\n await evaluateCrmSharing(\n firebaseAdmin.app().firestore(),\n segments[1],\n event.collection,\n [segments[3]],\n )\n } catch (error) {\n console.error(`[crm] sharing rules could not be evaluated for ${event.path}`, error)\n }\n}\n\n/*==========================================\n * THE AUDIT: a line on the record, a line in the org's feed.\n *=========================================*/\n\nconst LINK_FIELD: Readonly<Record<CrmSharingObject, keyof CrmActivityLink>> = {\n leads: 'leadId',\n contacts: 'contactId',\n companies: 'companyId',\n deals: 'dealId',\n}\n\nconst ORG_TARGET: Readonly<Record<CrmSharingObject, 'lead' | 'contact' | 'company' | 'deal'>> = {\n leads: 'lead',\n contacts: 'contact',\n companies: 'company',\n deals: 'deal',\n}\n\n/**\n * The entry on the record's Activity: who shared it with which sites, or\n * took a share away. Visible to the sites that hold the record and to the\n * sites it names, so both sides of a share read how it came about.\n */\nasync function fileSharingActivity(\n firestore: Firestore,\n input: {\n orgId: string\n object: CrmSharingObject\n id: string\n body: string\n targets: readonly string[]\n by: { uid: string; name: string }\n atMs: number\n },\n): Promise<void> {\n try {\n const orgRef = firestore.collection('orgs').doc(input.orgId)\n const record = (await orgRef.collection(input.object).doc(input.id).get()).data() ?? {}\n const held = recordHeldScope(record)\n const visibleTo = [...new Set([...held, ...input.targets])]\n if (!visibleTo.length) return\n const link: CrmActivityLink = { [LINK_FIELD[input.object]]: input.id }\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) return\n const hostId =\n typeof record['hostId'] === 'string' && record['hostId']\n ? record['hostId']\n : (hostIdsFromScope(held)[0] ?? '')\n await orgRef\n .collection(CRM_COLLECTIONS.activities)\n .doc(createResourceUid())\n .create({\n kind: 'note',\n body: input.body,\n atMs: input.atMs,\n byUid: input.by.uid,\n ...(input.by.name ? { byName: input.by.name } : {}),\n ...link,\n hostId,\n visibleTo,\n sourcePluginId: BUNDLE_ID,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n } catch (error) {\n console.error('[crm] the sharing entry could not be written on the record', input.id, error)\n }\n}\n\n/*==========================================\n * THE ORG'S SITES.\n *=========================================*/\n\nexport async function crmSharingSites(firestore: Firestore, orgId: string): Promise<CrmSharingSite[]> {\n const hosts = await firestore.collection('hosts').where('orgId', '==', orgId).get()\n return hosts.docs\n .map((doc) => {\n const displayName = String(doc.get('displayName') ?? '').trim()\n const subdomain = String(doc.get('subdomain') ?? '').trim()\n return { id: String(doc.id), name: displayName || subdomain || String(doc.id) }\n })\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/*==========================================\n * A RULE, SAVED FROM A REQUEST.\n *=========================================*/\n\nfunction listFrom(raw: unknown, max: number): string[] {\n if (!Array.isArray(raw)) return []\n return [\n ...new Set(\n raw.map((entry) => String(entry ?? '').trim().slice(0, 200)).filter(Boolean),\n ),\n ].slice(0, max)\n}\n\n/**\n * The rule a request describes, judged: an object, a name, sites that are\n * the org's own, targets that are the org's own or all sites. `null` with\n * the reason when it cannot be saved.\n */\nexport function ruleFromRequest(\n raw: unknown,\n context: { hostIds: readonly string[]; existing: CrmSharingRule | null; uid: string; nowMs: number },\n): { rule: CrmSharingRule; error: null } | { rule: null; error: string } {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { rule: null, error: 'Describe the rule.' }\n }\n const value = raw as Record<string, unknown>\n const object = value['object']\n if (!isCrmSharingObject(object)) {\n return { rule: null, error: 'Choose which records the rule shares.' }\n }\n const name = String(value['name'] ?? '').trim().slice(0, 120)\n if (!name) return { rule: null, error: 'Name the rule.' }\n const own = new Set(context.hostIds)\n const sourceHostIds = listFrom(value['sourceHostIds'], 30)\n if (sourceHostIds.some((id) => !own.has(id))) {\n return { rule: null, error: 'A site the rule names is not one of this organization’s.' }\n }\n const targets = shareTargetsFrom(value['targets'])\n if (!targets) return { rule: null, error: 'Choose the sites to share with, or All sites.' }\n if (hostIdsFromScope(targets).some((id) => !own.has(id))) {\n return { rule: null, error: 'A site the rule shares with is not one of this organization’s.' }\n }\n const access = value['access']\n if (access !== undefined && !isCrmShareAccess(access)) {\n return { rule: null, error: 'Access is read-only, or read and edit.' }\n }\n const rawCriteria = (value['criteria'] ?? {}) as Record<string, unknown>\n const criteria: CrmSharingCriteria = {}\n for (const key of CRM_SHARING_CRITERIA_KEYS) {\n if (!CRM_SHARING_CRITERIA_FOR[object].includes(key)) continue\n const list = listFrom(rawCriteria[key], 20)\n if (list.length) criteria[key] = list\n }\n const existing = context.existing\n const rule = readCrmSharingRule({\n id: existing?.id ?? createResourceUid(),\n name,\n object,\n enabled: value['enabled'] !== false,\n sourceHostIds,\n criteria,\n targets,\n access: (access as CrmShareAccess | undefined) ?? 'read',\n createdByUid: existing?.createdByUid ?? context.uid,\n createdAtMs: existing?.createdAtMs ?? context.nowMs,\n updatedAtMs: context.nowMs,\n })\n if (!rule) return { rule: null, error: 'The rule could not be read.' }\n return { rule, error: null }\n}\n\n/** A rule as the org document stores it — no `undefined` anywhere. */\nfunction storedRule(rule: CrmSharingRule): Record<string, unknown> {\n return JSON.parse(JSON.stringify(rule)) as Record<string, unknown>\n}\n\n/**\n * Writes the org's rule list through `change`, in a transaction over the org\n * document, and answers the list as written.\n */\nasync function writeRules(\n firestore: Firestore,\n orgId: string,\n change: (rules: CrmSharingRule[]) => CrmSharingRule[] | { error: string },\n): Promise<{ rules: CrmSharingRule[]; error: null } | { rules: null; error: string }> {\n const orgRef = firestore.collection('orgs').doc(orgId)\n const result = await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(orgRef)\n const next = change(readCrmSharingRules(snapshot.data() ?? null))\n if ('error' in next) return { rules: null, error: next.error }\n tx.update(orgRef, new FieldPath('crm', 'sharingRules'), next.map(storedRule))\n return { rules: next, error: null }\n })\n forgetCrmSharingRules(orgId)\n return result as { rules: CrmSharingRule[]; error: null } | { rules: null; error: string }\n}\n\n/** Replaces one rule's run, leaving every other rule as it is. */\nasync function writeRun(\n firestore: Firestore,\n orgId: string,\n ruleId: string,\n run: CrmSharingRuleRun,\n options: { dropRule?: boolean } = {},\n): Promise<CrmSharingRule | null> {\n const written = await writeRules(firestore, orgId, (rules) =>\n options.dropRule\n ? rules.filter((rule) => rule.id !== ruleId)\n : rules.map((rule) => (rule.id === ruleId ? { ...rule, run } : rule)),\n )\n return written.rules?.find((rule) => rule.id === ruleId) ?? null\n}\n\n/*==========================================\n * A RULE'S RUN: apply it everywhere, or take it away.\n *=========================================*/\n\nconst RUN_PAGE = 200\n\n/**\n * One budgeted pass of a rule's run, with the progress written on the rule\n * after every page so a reopened settings page shows where it stands and\n * `rule-continue` picks up from the cursor.\n *\n * `apply` walks the object's whole collection by document id — a rule\n * matches on fields no index narrows to (a facet's tags, a site that\n * captured the person) — and writes only the records whose sharing moves.\n * `remove` asks for exactly the records the rule holds a grant on\n * (`sharing.ruleIds`), each of which the recompute then takes it off; a\n * page that makes no progress fails the run rather than looping.\n */\nexport async function advanceSharingRuleRun(\n firestore: Firestore,\n orgId: string,\n ruleId: string,\n deadlineMs: number,\n): Promise<{ rule: CrmSharingRule | null; more: boolean }> {\n const read = await crmSharingRulesOf(firestore, orgId, { fresh: true })\n let rule = read.rules.find((entry) => entry.id === ruleId) ?? null\n if (!rule?.run || rule.run.status !== 'running') return { rule, more: false }\n const records = firestore.collection('orgs').doc(orgId).collection(rule.object)\n let run: CrmSharingRuleRun = { ...rule.run }\n const seen = new Set<string>()\n try {\n while (Date.now() < deadlineMs) {\n const { rules } = await crmSharingRulesOf(firestore, orgId, { fresh: true })\n const context = await crmSharingContextFor(firestore, orgId, rules, rule.object)\n let page: FirebaseFirestore.QuerySnapshot\n if (run.mode === 'apply') {\n let query = records.orderBy(FieldPath.documentId()).limit(RUN_PAGE)\n if (run.cursor) query = query.startAfter(run.cursor)\n page = await query.get()\n } else {\n page = await records.where(CRM_SHARING_RULE_IDS_PATH, 'array-contains', ruleId).limit(RUN_PAGE).get()\n }\n const atMs = Date.now()\n // Only the records whose sharing would move are written.\n const moving = page.docs\n .filter((doc) => {\n const record = (doc.data() ?? {}) as Record<string, unknown>\n const plan = planRecordSharing(\n record,\n evaluateRecordGrants(rule!.object, record, rules, atMs, context),\n )\n return sharingPlanChanges(record, plan)\n })\n .map((doc) => doc.id)\n if (run.mode === 'remove' && page.docs.length && page.docs.every((doc) => seen.has(doc.id))) {\n throw new Error('the rule’s grants did not come off these records')\n }\n for (const doc of page.docs) seen.add(doc.id)\n const outcome = await recomputeMany(\n { firestore, orgId, object: rule.object, rules, fresh: true, context, atMs },\n moving,\n )\n const done = run.mode === 'apply' ? page.size < RUN_PAGE : page.empty\n run = {\n ...run,\n processed: run.processed + page.size,\n changed: run.changed + outcome.changed.length,\n cursor: run.mode === 'apply' ? (page.docs[page.docs.length - 1]?.id ?? run.cursor ?? null) : null,\n ...(done ? { status: 'done' as const, finishedAtMs: Date.now() } : {}),\n }\n rule = await writeRun(firestore, orgId, ruleId, run, {\n dropRule: done && rule.deleting === true,\n })\n if (done || !rule) return { rule, more: false }\n }\n return { rule, more: true }\n } catch (error) {\n console.error('[crm] a sharing rule run failed', orgId, ruleId, error)\n rule = await writeRun(firestore, orgId, ruleId, {\n ...run,\n status: 'failed',\n finishedAtMs: Date.now(),\n error: 'The recompute stopped. Resume it to carry on from where it stopped.',\n })\n return { rule, more: false }\n }\n}\n\n/*==========================================\n * THE ROUTE.\n *=========================================*/\n\n/** How long one request works on a rule before it answers and the page asks again. */\nconst RUN_BUDGET_MS = 20_000\n\nconst MANAGERS_ONLY =\n 'Sharing records across sites is for the workspace’s owners and admins.'\n\nfunction readIds(raw: unknown): string[] | null {\n if (!Array.isArray(raw)) return null\n const ids = [\n ...new Set(\n raw.map((entry) => String(entry ?? '').trim().slice(0, 256)).filter((id) => id && !id.includes('/')),\n ),\n ].slice(0, CRM_SHARING_IDS_MAX)\n return ids.length ? ids : null\n}\n\n/**\n * `POST /api/crm/sharing` — `{ action, hostId | orgId, … }`.\n *\n * Every action but `evaluate` is an org manager's: an OWNER or ADMIN of the\n * whole workspace (`authorizeOrgCaller(…, 'manage-org')`), under a site's\n * hub or the org's. `evaluate` is what a client-direct write owes the rules\n * and is open to whoever may write the CRM. The plan gate is the CRM's own\n * (`crmSuiteRefusal`): sharing is only meaningful across several sites, and\n * the number of sites a workspace may have is already the plan's.\n */\nexport const crmSharingHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const body = (req.body ?? {}) as Record<string, unknown>\n const scope = readCrmRouteScope(body)\n if (!scope) {\n res.status(400).json({ error: 'Missing hostId' })\n return\n }\n const action = String(body['action'] ?? '')\n const firestore = firebaseAdmin.app().firestore()\n try {\n if (action === 'evaluate') {\n const object = body['object']\n const ids = readIds(body['ids'])\n if (!isCrmSharingObject(object) || !ids) {\n res.status(400).json({ error: 'Name the records.' })\n return\n }\n const writer = await authorizeCrmWriter(req, scope, { suiteAct: null })\n if (writer.ok === false) {\n res.status(writer.status).json(writer.body)\n return\n }\n const outcome = await evaluateCrmSharing(firestore, writer.orgId, object, ids)\n const answer: CrmSharingResponse = { ok: true, ...outcome }\n res.status(200).json(answer)\n return\n }\n\n const orgId =\n scope.level === 'org'\n ? scope.orgId\n : ((await getOrgForHost(scope.hostId).catch(() => null))?.orgId ?? '')\n if (!orgId) {\n res.status(404).json({ error: 'This site has no organization, so it has no CRM.' })\n return\n }\n const caller = await authorizeOrgCaller(req, orgId, {\n needs: 'manage-org',\n refusal: MANAGERS_ONLY,\n })\n if (caller.ok === false) {\n res.status(caller.status).json({ error: caller.error })\n return\n }\n const suite = crmSuiteRefusal(caller.org, 'Sharing records across sites')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const actor = { uid: caller.uid, email: caller.email }\n const byName = caller.name || caller.email || ''\n const nowMs = Date.now()\n\n if (action === 'sites') {\n const answer: CrmSharingResponse = { ok: true, sites: await crmSharingSites(firestore, orgId) }\n res.status(200).json(answer)\n return\n }\n\n if (action === 'share' || action === 'unshare') {\n const object = body['object']\n const ids = readIds(body['ids'])\n if (!isCrmSharingObject(object) || !ids) {\n res.status(400).json({ error: 'Name the records to share.' })\n return\n }\n const targets = shareTargetsFrom(body['targets'])\n if (!targets) {\n res.status(400).json({ error: 'Choose the sites, or All sites.' })\n return\n }\n const sites = await crmSharingSites(firestore, orgId)\n const own = new Set(sites.map((site) => site.id))\n if (hostIdsFromScope(targets).some((id) => !own.has(id))) {\n res.status(400).json({ error: 'A site named is not one of this organization’s.' })\n return\n }\n const access: CrmShareAccess = body['access'] === 'edit' ? 'edit' : 'read'\n const { rules, fresh } = await crmSharingRulesOf(firestore, orgId)\n const context = await crmSharingContextFor(firestore, orgId, rules, object)\n const outcome = await recomputeMany(\n {\n firestore,\n orgId,\n object,\n rules,\n fresh,\n context,\n atMs: nowMs,\n manual: (grants) =>\n action === 'share'\n ? withManualShares(grants, targets, access, { uid: caller.uid, name: byName }, nowMs)\n : withoutManualShares(grants, targets),\n },\n ids,\n )\n const siteName = (id: string) => sites.find((site) => site.id === id)?.name ?? 'a site'\n const named = describeShareTargets(targets, siteName)\n const sentence =\n action === 'share'\n ? `Shared with ${named}${access === 'edit' ? ' (read and edit)' : ' (read-only)'}`\n : `Stopped sharing with ${named}`\n await Promise.all(\n outcome.changed.map((id) =>\n fileSharingActivity(firestore, {\n orgId,\n object,\n id,\n body: sentence,\n targets,\n by: { uid: caller.uid, name: byName },\n atMs: nowMs,\n }),\n ),\n )\n if (outcome.changed.length) {\n const label = CRM_SHARING_OBJECT_LABELS[object]\n await logOrgActivity(\n orgId,\n actor,\n outcome.changed.length === 1\n ? `${sentence}: one ${label.one}`\n : `${sentence}: ${outcome.changed.length} ${label.many.toLowerCase()}`,\n {\n type: ORG_TARGET[object],\n ...(outcome.changed.length === 1 ? { id: outcome.changed[0] } : {}),\n },\n ).catch(() => undefined)\n }\n const answer: CrmSharingResponse = {\n ok: true,\n changed: outcome.changed.length,\n missing: outcome.missing,\n }\n res.status(200).json(answer)\n return\n }\n\n if (action === 'rule-save') {\n const hostIds = await orgHostIds(firestore, orgId)\n const raw = body['rule']\n const ruleId = raw && typeof raw === 'object' ? String((raw as Record<string, unknown>)['id'] ?? '') : ''\n let saved: CrmSharingRule | null = null\n let error: string | null = null\n const written = await writeRules(firestore, orgId, (rules) => {\n const existing = ruleId ? (rules.find((rule) => rule.id === ruleId) ?? null) : null\n if (ruleId && !existing) return { error: 'That rule no longer exists.' }\n if (existing?.deleting) return { error: 'That rule is being deleted.' }\n if (!existing && rules.length >= CRM_SHARING_RULES_MAX) {\n return { error: `A workspace keeps at most ${CRM_SHARING_RULES_MAX} sharing rules.` }\n }\n const judged = ruleFromRequest(raw, { hostIds, existing, uid: caller.uid, nowMs })\n if (!judged.rule) {\n error = judged.error\n return { error: judged.error }\n }\n /*\n * An enabled rule walks every record — it may now match some and not\n * others. A disabled one only has grants to take away, and asks for\n * exactly the records holding them.\n */\n saved = {\n ...judged.rule,\n run: {\n status: 'running',\n mode: judged.rule.enabled ? 'apply' : 'remove',\n processed: 0,\n changed: 0,\n cursor: null,\n startedAtMs: nowMs,\n },\n }\n return existing\n ? rules.map((rule) => (rule.id === existing.id ? saved! : rule))\n : [...rules, saved]\n })\n if (!written.rules || !saved) {\n res.status(400).json({ error: error ?? written.error ?? 'The rule could not be saved.' })\n return\n }\n const rule = saved as CrmSharingRule\n await logOrgActivity(\n orgId,\n actor,\n `${ruleId ? 'Changed' : 'Added'} the sharing rule “${rule.name}”`,\n { type: `${BUNDLE_ID}:sharingRule`, id: rule.id, name: rule.name },\n ).catch(() => undefined)\n const advanced = await advanceSharingRuleRun(firestore, orgId, rule.id, Date.now() + RUN_BUDGET_MS)\n const answer: CrmSharingResponse = { ok: true, rule: advanced.rule, more: advanced.more }\n res.status(200).json(answer)\n return\n }\n\n if (action === 'rule-delete' || action === 'rule-continue') {\n const ruleId = String(body['ruleId'] ?? '').trim()\n if (!ruleId) {\n res.status(400).json({ error: 'Name the rule.' })\n return\n }\n if (action === 'rule-delete') {\n let name = ''\n const written = await writeRules(firestore, orgId, (rules) => {\n const existing = rules.find((rule) => rule.id === ruleId)\n if (!existing) return { error: 'That rule no longer exists.' }\n name = existing.name\n return rules.map((rule) =>\n rule.id === ruleId\n ? {\n ...rule,\n enabled: false,\n deleting: true,\n updatedAtMs: nowMs,\n run: {\n status: 'running' as const,\n mode: 'remove' as const,\n processed: 0,\n changed: 0,\n cursor: null,\n startedAtMs: nowMs,\n },\n }\n : rule,\n )\n })\n if (!written.rules) {\n res.status(404).json({ error: written.error })\n return\n }\n await logOrgActivity(orgId, actor, `Deleted the sharing rule “${name}”`, {\n type: `${BUNDLE_ID}:sharingRule`,\n id: ruleId,\n name,\n }).catch(() => undefined)\n } else {\n // A failed run resumes from its cursor.\n await writeRules(firestore, orgId, (rules) =>\n rules.map((rule) =>\n rule.id === ruleId && rule.run?.status === 'failed'\n ? { ...rule, run: { ...rule.run, status: 'running' as const } }\n : rule,\n ),\n )\n }\n const advanced = await advanceSharingRuleRun(firestore, orgId, ruleId, Date.now() + RUN_BUDGET_MS)\n const answer: CrmSharingResponse = { ok: true, rule: advanced.rule, more: advanced.more }\n res.status(200).json(answer)\n return\n }\n\n res.status(400).json({ error: 'Unknown sharing action.' })\n } catch (error) {\n console.error('[crm] sharing failed', action, error)\n res.status(500).json({ error: 'The sharing could not be saved. Try again.' })\n }\n}\n\n"],"names":["CRM_COLLECTIONS","crmActivityLogHasRoom","crmListFieldsPatch","createResourceUid","hostIdsFromScope","firebaseAdmin","getOrgForHost","logOrgActivity","countCrmActivitiesForRecord","FieldPath","FieldValue","BUNDLE_ID","activeSharingRules","CRM_SHARING_CRITERIA_FOR","CRM_SHARING_CRITERIA_KEYS","CRM_SHARING_FIELD","CRM_SHARING_IDS_MAX","CRM_SHARING_OBJECT_LABELS","CRM_SHARING_RULE_IDS_PATH","CRM_SHARING_RULES_MAX","CRM_WRITE_SCOPE_FIELD","describeShareTargets","evaluateRecordGrants","isCrmShareAccess","isCrmSharingObject","planRecordSharing","readCrmSharingRule","readCrmSharingRules","recordHeldScope","sharingPlanChanges","shareTargetsFrom","sharingRulesAskLeadSourceDirection","withManualShares","withoutManualShares","readLeadSourcePicklist","authorizeOrgCaller","orgHostIds","readCrmRouteScope","crmSuiteRefusal","authorizeCrmWriter","RULES_TTL_MS","rulesCache","Map","forgetCrmSharingRules","orgId","delete","clear","crmSharingRulesOf","firestore","options","snapshot","nowMs","Date","now","cached","get","fresh","atMs","rules","collection","doc","data","set","crmSharingContextFor","object","leadSources","recomputeRecordSharing","input","id","ref","runTransaction","tx","plan","exists","record","known","Set","map","rule","stored","ruleIds","Array","isArray","some","ruleId","has","String","org","context","grants","manual","next","visibleTo","writeTo","undefined","sharing","update","recomputeMany","ids","queue","changed","missing","worker","shift","outcome","push","Promise","all","from","length","Math","min","evaluateCrmSharing","crmSharingRecordWritten","event","segments","path","split","app","error","console","LINK_FIELD","leads","contacts","companies","deals","ORG_TARGET","fileSharingActivity","orgRef","held","targets","link","hostId","activities","create","kind","body","byUid","by","uid","name","byName","sourcePluginId","createdAt","serverTimestamp","updatedAt","crmSharingSites","hosts","where","docs","displayName","trim","subdomain","sort","a","b","localeCompare","listFrom","raw","max","entry","slice","filter","Boolean","ruleFromRequest","value","own","hostIds","sourceHostIds","access","rawCriteria","criteria","key","includes","list","existing","enabled","createdByUid","createdAtMs","updatedAtMs","storedRule","JSON","parse","stringify","writeRules","change","result","writeRun","run","written","dropRule","find","RUN_PAGE","advanceSharingRuleRun","deadlineMs","read","status","more","records","seen","page","mode","query","orderBy","documentId","limit","cursor","startAfter","moving","every","Error","add","done","size","empty","processed","finishedAtMs","deleting","RUN_BUDGET_MS","MANAGERS_ONLY","readIds","crmSharingHandler","req","res","method","setHeader","json","scope","action","writer","suiteAct","ok","answer","level","catch","caller","needs","refusal","suite","actor","email","sites","site","siteName","named","sentence","label","one","many","toLowerCase","type","saved","judged","startedAtMs","advanced"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;CAkBC,GAED,SACEA,eAAe,EACfC,qBAAqB,EAErBC,kBAAkB,QACb,6BAA4B;AACnC,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,gBAAgB,QAAQ,sCAAqC;AAEtE,SAASC,aAAa,EAAEC,aAAa,EAAEC,cAAc,QAAQ,2BAA0B;AACvF,SAASC,2BAA2B,QAAQ,8CAA6C;AACzF,SAASC,SAAS,EAAEC,UAAU,QAAQ,2BAA0B;AAChE,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SACEC,kBAAkB,EAClBC,wBAAwB,EACxBC,yBAAyB,EACzBC,iBAAiB,EACjBC,mBAAmB,EACnBC,yBAAyB,EACzBC,yBAAyB,EACzBC,qBAAqB,EACrBC,qBAAqB,EAUrBC,oBAAoB,EACpBC,oBAAoB,EACpBC,gBAAgB,EAChBC,kBAAkB,EAClBC,iBAAiB,EACjBC,kBAAkB,EAClBC,mBAAmB,EACnBC,eAAe,EACfC,kBAAkB,EAClBC,gBAAgB,EAChBC,kCAAkC,EAClCC,gBAAgB,EAChBC,mBAAmB,QACd,0BAAsB;AAC7B,SAASC,sBAAsB,QAAQ,4BAAwB;AAC/D,SAASC,kBAAkB,EAAEC,UAAU,EAAEC,iBAAiB,QAAQ,kBAAc;AAChF,SAASC,eAAe,QAAQ,kBAAc;AAC9C,SAASC,kBAAkB,QAAQ,mBAAe;AAIlD;;2CAE2C,GAE3C;;;;CAIC,GACD,MAAMC,eAAe;AACrB,MAAMC,aAAa,IAAIC;AAEvB,8EAA8E,GAC9E,OAAO,SAASC,sBAAsBC,KAAc;IAClD,IAAIA,OAAOH,WAAWI,MAAM,CAACD;SACxBH,WAAWK,KAAK;AACvB;AAEA,OAAO,eAAeC,kBACpBC,SAAoB,EACpBJ,KAAa,EACbK,UAA+C,CAAC,CAAC;QAEnCA,gBAMoBC;IANlC,MAAMC,SAAQF,iBAAAA,QAAQE,KAAK,YAAbF,iBAAiBG,KAAKC,GAAG;IACvC,MAAMC,SAASb,WAAWc,GAAG,CAACX;IAC9B,IAAI,CAACK,QAAQO,KAAK,IAAIF,UAAUH,QAAQG,OAAOG,IAAI,GAAGjB,cAAc;QAClE,OAAO;YAAEkB,OAAOJ,OAAOI,KAAK;YAAEF,OAAO;QAAM;IAC7C;IACA,MAAMN,WAAW,MAAMF,UAAUW,UAAU,CAAC,QAAQC,GAAG,CAAChB,OAAOW,GAAG;IAClE,MAAMG,QAAQ/B,qBAAoBuB,iBAAAA,SAASW,IAAI,cAAbX,iBAAmB;IACrDT,WAAWqB,GAAG,CAAClB,OAAO;QAAEa,MAAMN;QAAOO;IAAM;IAC3C,OAAO;QAAEA;QAAOF,OAAO;IAAK;AAC9B;AAEA;;;;CAIC,GACD,OAAO,eAAeO,qBACpBf,SAAoB,EACpBJ,KAAa,EACbc,KAAgC,EAChCM,MAAwB;IAExB,IAAI,CAACjC,mCAAmC2B,OAAOM,SAAS,OAAO,CAAC;IAChE,OAAO;QAAEC,aAAa,MAAM/B,uBAAuBc,WAAWJ;IAAO;AACvE;AAwBA,OAAO,eAAesB,uBACpBC,KAAkC;IAElC,MAAM,EAAEnB,SAAS,EAAEJ,KAAK,EAAEoB,MAAM,EAAEI,EAAE,EAAEX,IAAI,EAAE,GAAGU;IAC/C,MAAME,MAAMrB,UAAUW,UAAU,CAAC,QAAQC,GAAG,CAAChB,OAAOe,UAAU,CAACK,QAAQJ,GAAG,CAACQ;IAC3E,OAAO,MAAMpB,UAAUsB,cAAc,CAAC,OAAOC;YAG3BrB,gBA6BWsB,eACJA,eAIIA,gBACJA;YAhBrBL;QArBF,MAAMjB,WAAW,MAAMqB,GAAGhB,GAAG,CAACc;QAC9B,IAAI,CAACnB,SAASuB,MAAM,EAAE,OAAO;QAC7B,MAAMC,UAAUxB,iBAAAA,SAASW,IAAI,cAAbX,iBAAmB,CAAC;QACpC,IAAIQ,QAAQS,MAAMT,KAAK;QACvB;;;;;KAKC,GACD,IAAI,CAACS,MAAMX,KAAK,EAAE;YAChB,MAAMmB,QAAQ,IAAIC,IAAIlB,MAAMmB,GAAG,CAAC,CAACC,OAASA,KAAKV,EAAE;YACjD,MAAMW,SAASL,MAAM,CAAC3D,kBAAkB;YACxC,MAAMiE,UAAUC,MAAMC,OAAO,CAACH,0BAAAA,OAAQC,OAAO,IAAKD,OAAOC,OAAO,GAAiB,EAAE;YACnF,IAAIA,QAAQG,IAAI,CAAC,CAACC,SAAW,CAACT,MAAMU,GAAG,CAACC,OAAOF,WAAW;oBAE5BG;gBAD5B,MAAMA,MAAM,MAAMhB,GAAGhB,GAAG,CAACP,UAAUW,UAAU,CAAC,QAAQC,GAAG,CAAChB;gBAC1Dc,QAAQ/B,qBAAoB4D,YAAAA,IAAI1B,IAAI,cAAR0B,YAAc;YAC5C;QACF;QACA,sFAAsF;QACtF,MAAMC,UACJrB,EAAAA,iBAAAA,MAAMqB,OAAO,qBAAbrB,eAAeF,WAAW,KAAIP,UAAUS,MAAMT,KAAK,GAC/CS,MAAMqB,OAAO,GACb,MAAMzB,qBAAqBf,WAAWJ,OAAOc,OAAOM;QAC1D,IAAIyB,SAASnE,qBAAqB0C,QAAQU,QAAQhB,OAAOD,MAAM+B;QAC/D,IAAIrB,MAAMuB,MAAM,EAAED,SAAStB,MAAMuB,MAAM,CAACD;QACxC,MAAMjB,OAAO/C,kBAAkBiD,QAAQe;QACvC,IAAI,CAAC5D,mBAAmB6C,QAAQF,OAAO,OAAO;QAC9C,MAAMmB,OAAO,aACRjB;YACHkB,WAAWpB,KAAKoB,SAAS;YACzB,CAACxE,sBAAsB,GAAEoD,gBAAAA,KAAKqB,OAAO,YAAZrB,gBAAgBsB;YACzC,CAAC/E,kBAAkB,GAAEyD,gBAAAA,KAAKuB,OAAO,YAAZvB,gBAAgBsB;;QAEvCvB,GAAGyB,MAAM,CAAC3B,KAAK;YACbuB,WAAWpB,KAAKoB,SAAS;YACzB,CAACxE,sBAAsB,GAAEoD,iBAAAA,KAAKqB,OAAO,YAAZrB,iBAAgB9D,WAAWmC,MAAM;YAC1D,CAAC9B,kBAAkB,GAAEyD,iBAAAA,KAAKuB,OAAO,YAAZvB,iBAAgB9D,WAAWmC,MAAM;WAGnD3C,mBAAmB8D,QAAQ2B;QAEhC,OAAO;IACT;AACF;AAEA,8CAA8C,GAC9C,eAAeM,cACb9B,KAA8C,EAC9C+B,GAAsB;IAEtB,MAAMC,QAAQ;WAAI,IAAIvB,IAAIsB;KAAK;IAC/B,MAAME,UAAoB,EAAE;IAC5B,IAAIC,UAAU;IACd,MAAMC,SAAS;QACb,IAAK,IAAIlC,KAAK+B,MAAMI,KAAK,IAAInC,OAAO0B,WAAW1B,KAAK+B,MAAMI,KAAK,GAAI;YACjE,MAAMC,UAAU,MAAMtC,uBAAuB,aAAKC;gBAAOC;;YACzD,IAAIoC,YAAY,WAAWJ,QAAQK,IAAI,CAACrC;YACxC,IAAIoC,YAAY,WAAWH,WAAW;QACxC;IACF;IACA,MAAMK,QAAQC,GAAG,CAAC1B,MAAM2B,IAAI,CAAC;QAAEC,QAAQC,KAAKC,GAAG,CAAC,GAAGZ,MAAMU,MAAM;IAAE,GAAGP;IACpE,OAAO;QAAEF;QAASC;IAAQ;AAC5B;AAEA;;;;;CAKC,GACD,OAAO,eAAeW,mBACpBhE,SAAoB,EACpBJ,KAAa,EACboB,MAAwB,EACxBkC,GAAsB;IAEtB,MAAM,EAAExC,KAAK,EAAEF,KAAK,EAAE,GAAG,MAAMT,kBAAkBC,WAAWJ;IAC5D,IAAI,CAAChC,mBAAmB8C,OAAOM,QAAQ6C,MAAM,EAAE,OAAO;QAAET,SAAS;QAAGC,SAAS;IAAE;IAC/E,MAAMb,UAAU,MAAMzB,qBAAqBf,WAAWJ,OAAOc,OAAOM;IACpE,MAAMwC,UAAU,MAAMP,cACpB;QAAEjD;QAAWJ;QAAOoB;QAAQN;QAAOF;QAAOgC;QAAS/B,MAAML,KAAKC,GAAG;IAAG,GACpE6C;IAEF,OAAO;QAAEE,SAASI,QAAQJ,OAAO,CAACS,MAAM;QAAER,SAASG,QAAQH,OAAO;IAAC;AACrE;AAEA;;;;CAIC,GACD,OAAO,eAAeY,wBAAwBC,KAG7C;IACC,IAAI,CAAC1F,mBAAmB0F,MAAMvD,UAAU,GAAG;IAC3C,MAAMwD,WAAWD,MAAME,IAAI,CAACC,KAAK,CAAC;IAClC,IAAIF,SAASN,MAAM,KAAK,KAAKM,QAAQ,CAAC,EAAE,KAAK,UAAUA,QAAQ,CAAC,EAAE,KAAKD,MAAMvD,UAAU,EAAE;IACzF,IAAI;QACF,MAAMqD,mBACJ3G,cAAciH,GAAG,GAAGtE,SAAS,IAC7BmE,QAAQ,CAAC,EAAE,EACXD,MAAMvD,UAAU,EAChB;YAACwD,QAAQ,CAAC,EAAE;SAAC;IAEjB,EAAE,OAAOI,OAAO;QACdC,QAAQD,KAAK,CAAC,CAAC,+CAA+C,EAAEL,MAAME,IAAI,EAAE,EAAEG;IAChF;AACF;AAEA;;2CAE2C,GAE3C,MAAME,aAAwE;IAC5EC,OAAO;IACPC,UAAU;IACVC,WAAW;IACXC,OAAO;AACT;AAEA,MAAMC,aAA0F;IAC9FJ,OAAO;IACPC,UAAU;IACVC,WAAW;IACXC,OAAO;AACT;AAEA;;;;CAIC,GACD,eAAeE,oBACb/E,SAAoB,EACpBmB,KAQC;IAED,IAAI;YAEa,OASR/D;QAVP,MAAM4H,SAAShF,UAAUW,UAAU,CAAC,QAAQC,GAAG,CAACO,MAAMvB,KAAK;QAC3D,MAAM8B,UAAS,QAAA,AAAC,CAAA,MAAMsD,OAAOrE,UAAU,CAACQ,MAAMH,MAAM,EAAEJ,GAAG,CAACO,MAAMC,EAAE,EAAEb,GAAG,EAAC,EAAGM,IAAI,cAAhE,QAAsE,CAAC;QACtF,MAAMoE,OAAOrG,gBAAgB8C;QAC7B,MAAMkB,YAAY;eAAI,IAAIhB,IAAI;mBAAIqD;mBAAS9D,MAAM+D,OAAO;aAAC;SAAE;QAC3D,IAAI,CAACtC,UAAUiB,MAAM,EAAE;QACvB,MAAMsB,OAAwB;YAAE,CAACV,UAAU,CAACtD,MAAMH,MAAM,CAAC,CAAC,EAAEG,MAAMC,EAAE;QAAC;QACrE,IAAI,CAACnE,sBAAsB,MAAMO,4BAA4BwH,QAAQG,QAAQ;QAC7E,MAAMC,SACJ,OAAO1D,MAAM,CAAC,SAAS,KAAK,YAAYA,MAAM,CAAC,SAAS,GACpDA,MAAM,CAAC,SAAS,IACftE,qBAAAA,iBAAiB6H,KAAK,CAAC,EAAE,YAAzB7H,qBAA6B;QACpC,MAAM4H,OACHrE,UAAU,CAAC3D,gBAAgBqI,UAAU,EACrCzE,GAAG,CAACzD,qBACJmI,MAAM,CAAC;YACNC,MAAM;YACNC,MAAMrE,MAAMqE,IAAI;YAChB/E,MAAMU,MAAMV,IAAI;YAChBgF,OAAOtE,MAAMuE,EAAE,CAACC,GAAG;WACfxE,MAAMuE,EAAE,CAACE,IAAI,GAAG;YAAEC,QAAQ1E,MAAMuE,EAAE,CAACE,IAAI;QAAC,IAAI,CAAC,GAC9CT;YACHC;YACAxC;YACAkD,gBAAgBnI;YAChBoI,WAAWrI,WAAWsI,eAAe;YACrCC,WAAWvI,WAAWsI,eAAe;;IAE3C,EAAE,OAAOzB,OAAO;QACdC,QAAQD,KAAK,CAAC,8DAA8DpD,MAAMC,EAAE,EAAEmD;IACxF;AACF;AAEA;;2CAE2C,GAE3C,OAAO,eAAe2B,gBAAgBlG,SAAoB,EAAEJ,KAAa;IACvE,MAAMuG,QAAQ,MAAMnG,UAAUW,UAAU,CAAC,SAASyF,KAAK,CAAC,SAAS,MAAMxG,OAAOW,GAAG;IACjF,OAAO4F,MAAME,IAAI,CACdxE,GAAG,CAAC,CAACjB;YACuBA,UACFA;QADzB,MAAM0F,cAAchE,QAAO1B,WAAAA,IAAIL,GAAG,CAAC,0BAARK,WAA0B,IAAI2F,IAAI;QAC7D,MAAMC,YAAYlE,QAAO1B,YAAAA,IAAIL,GAAG,CAAC,wBAARK,YAAwB,IAAI2F,IAAI;QACzD,OAAO;YAAEnF,IAAIkB,OAAO1B,IAAIQ,EAAE;YAAGwE,MAAMU,eAAeE,aAAalE,OAAO1B,IAAIQ,EAAE;QAAE;IAChF,GACCqF,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEd,IAAI,CAACgB,aAAa,CAACD,EAAEf,IAAI;AAC/C;AAEA;;2CAE2C,GAE3C,SAASiB,SAASC,GAAY,EAAEC,GAAW;IACzC,IAAI,CAAC9E,MAAMC,OAAO,CAAC4E,MAAM,OAAO,EAAE;IAClC,OAAO;WACF,IAAIlF,IACLkF,IAAIjF,GAAG,CAAC,CAACmF,QAAU1E,OAAO0E,gBAAAA,QAAS,IAAIT,IAAI,GAAGU,KAAK,CAAC,GAAG,MAAMC,MAAM,CAACC;KAEvE,CAACF,KAAK,CAAC,GAAGF;AACb;AAEA;;;;CAIC,GACD,OAAO,SAASK,gBACdN,GAAY,EACZtE,OAAoG;QAUhF6E,aAgBCA;IAxBrB,IAAI,CAACP,OAAO,OAAOA,QAAQ,YAAY7E,MAAMC,OAAO,CAAC4E,MAAM;QACzD,OAAO;YAAEhF,MAAM;YAAMyC,OAAO;QAAqB;IACnD;IACA,MAAM8C,QAAQP;IACd,MAAM9F,SAASqG,KAAK,CAAC,SAAS;IAC9B,IAAI,CAAC7I,mBAAmBwC,SAAS;QAC/B,OAAO;YAAEc,MAAM;YAAMyC,OAAO;QAAwC;IACtE;IACA,MAAMqB,OAAOtD,QAAO+E,cAAAA,KAAK,CAAC,OAAO,YAAbA,cAAiB,IAAId,IAAI,GAAGU,KAAK,CAAC,GAAG;IACzD,IAAI,CAACrB,MAAM,OAAO;QAAE9D,MAAM;QAAMyC,OAAO;IAAiB;IACxD,MAAM+C,MAAM,IAAI1F,IAAIY,QAAQ+E,OAAO;IACnC,MAAMC,gBAAgBX,SAASQ,KAAK,CAAC,gBAAgB,EAAE;IACvD,IAAIG,cAAcrF,IAAI,CAAC,CAACf,KAAO,CAACkG,IAAIjF,GAAG,CAACjB,MAAM;QAC5C,OAAO;YAAEU,MAAM;YAAMyC,OAAO;QAA2D;IACzF;IACA,MAAMW,UAAUpG,iBAAiBuI,KAAK,CAAC,UAAU;IACjD,IAAI,CAACnC,SAAS,OAAO;QAAEpD,MAAM;QAAMyC,OAAO;IAAgD;IAC1F,IAAInH,iBAAiB8H,SAAS/C,IAAI,CAAC,CAACf,KAAO,CAACkG,IAAIjF,GAAG,CAACjB,MAAM;QACxD,OAAO;YAAEU,MAAM;YAAMyC,OAAO;QAAiE;IAC/F;IACA,MAAMkD,SAASJ,KAAK,CAAC,SAAS;IAC9B,IAAII,WAAW3E,aAAa,CAACvE,iBAAiBkJ,SAAS;QACrD,OAAO;YAAE3F,MAAM;YAAMyC,OAAO;QAAyC;IACvE;IACA,MAAMmD,eAAeL,kBAAAA,KAAK,CAAC,WAAW,YAAjBA,kBAAqB,CAAC;IAC3C,MAAMM,WAA+B,CAAC;IACtC,KAAK,MAAMC,OAAO9J,0BAA2B;QAC3C,IAAI,CAACD,wBAAwB,CAACmD,OAAO,CAAC6G,QAAQ,CAACD,MAAM;QACrD,MAAME,OAAOjB,SAASa,WAAW,CAACE,IAAI,EAAE;QACxC,IAAIE,KAAKjE,MAAM,EAAE8D,QAAQ,CAACC,IAAI,GAAGE;IACnC;IACA,MAAMC,WAAWvF,QAAQuF,QAAQ;IACjC,MAAMjG,OAAOpD,mBAAmB;QAC9B0C,EAAE,UAAE2G,4BAAAA,SAAU3G,EAAE,mBAAIjE;QACpByI;QACA5E;QACAgH,SAASX,KAAK,CAAC,UAAU,KAAK;QAC9BG;QACAG;QACAzC;QACAuC,MAAM,EAAGA,iBAAAA,SAAyC;QAClDQ,YAAY,WAAEF,4BAAAA,SAAUE,YAAY,oBAAIzF,QAAQmD,GAAG;QACnDuC,WAAW,WAAEH,4BAAAA,SAAUG,WAAW,oBAAI1F,QAAQrC,KAAK;QACnDgI,aAAa3F,QAAQrC,KAAK;IAC5B;IACA,IAAI,CAAC2B,MAAM,OAAO;QAAEA,MAAM;QAAMyC,OAAO;IAA8B;IACrE,OAAO;QAAEzC;QAAMyC,OAAO;IAAK;AAC7B;AAEA,oEAAoE,GACpE,SAAS6D,WAAWtG,IAAoB;IACtC,OAAOuG,KAAKC,KAAK,CAACD,KAAKE,SAAS,CAACzG;AACnC;AAEA;;;CAGC,GACD,eAAe0G,WACbxI,SAAoB,EACpBJ,KAAa,EACb6I,MAAyE;IAEzE,MAAMzD,SAAShF,UAAUW,UAAU,CAAC,QAAQC,GAAG,CAAChB;IAChD,MAAM8I,SAAS,MAAM1I,UAAUsB,cAAc,CAAC,OAAOC;YAEXrB;QADxC,MAAMA,WAAW,MAAMqB,GAAGhB,GAAG,CAACyE;QAC9B,MAAMrC,OAAO8F,OAAO9J,qBAAoBuB,iBAAAA,SAASW,IAAI,cAAbX,iBAAmB;QAC3D,IAAI,WAAWyC,MAAM,OAAO;YAAEjC,OAAO;YAAM6D,OAAO5B,KAAK4B,KAAK;QAAC;QAC7DhD,GAAGyB,MAAM,CAACgC,QAAQ,IAAIvH,UAAU,OAAO,iBAAiBkF,KAAKd,GAAG,CAACuG;QACjE,OAAO;YAAE1H,OAAOiC;YAAM4B,OAAO;QAAK;IACpC;IACA5E,sBAAsBC;IACtB,OAAO8I;AACT;AAEA,gEAAgE,GAChE,eAAeC,SACb3I,SAAoB,EACpBJ,KAAa,EACbwC,MAAc,EACdwG,GAAsB,EACtB3I,UAAkC,CAAC,CAAC;;QAO7B4I;IALP,MAAMA,UAAU,MAAML,WAAWxI,WAAWJ,OAAO,CAACc,QAClDT,QAAQ6I,QAAQ,GACZpI,MAAMwG,MAAM,CAAC,CAACpF,OAASA,KAAKV,EAAE,KAAKgB,UACnC1B,MAAMmB,GAAG,CAAC,CAACC,OAAUA,KAAKV,EAAE,KAAKgB,SAAS,aAAKN;gBAAM8G;iBAAQ9G;IAEnE,gBAAO+G,iBAAAA,QAAQnI,KAAK,qBAAbmI,eAAeE,IAAI,CAAC,CAACjH,OAASA,KAAKV,EAAE,KAAKgB,0BAAW;AAC9D;AAEA;;2CAE2C,GAE3C,MAAM4G,WAAW;AAEjB;;;;;;;;;;;CAWC,GACD,OAAO,eAAeC,sBACpBjJ,SAAoB,EACpBJ,KAAa,EACbwC,MAAc,EACd8G,UAAkB;QAGPC;IADX,MAAMA,OAAO,MAAMpJ,kBAAkBC,WAAWJ,OAAO;QAAEY,OAAO;IAAK;IACrE,IAAIsB,QAAOqH,mBAAAA,KAAKzI,KAAK,CAACqI,IAAI,CAAC,CAAC/B,QAAUA,MAAM5F,EAAE,KAAKgB,mBAAxC+G,mBAAmD;IAC9D,IAAI,EAACrH,wBAAAA,KAAM8G,GAAG,KAAI9G,KAAK8G,GAAG,CAACQ,MAAM,KAAK,WAAW,OAAO;QAAEtH;QAAMuH,MAAM;IAAM;IAC5E,MAAMC,UAAUtJ,UAAUW,UAAU,CAAC,QAAQC,GAAG,CAAChB,OAAOe,UAAU,CAACmB,KAAKd,MAAM;IAC9E,IAAI4H,MAAyB,aAAK9G,KAAK8G,GAAG;IAC1C,MAAMW,OAAO,IAAI3H;IACjB,IAAI;QACF,MAAOxB,KAAKC,GAAG,KAAK6I,WAAY;gBAoCIM;gBAAAA;YAnClC,MAAM,EAAE9I,KAAK,EAAE,GAAG,MAAMX,kBAAkBC,WAAWJ,OAAO;gBAAEY,OAAO;YAAK;YAC1E,MAAMgC,UAAU,MAAMzB,qBAAqBf,WAAWJ,OAAOc,OAAOoB,KAAKd,MAAM;YAC/E,IAAIwI;YACJ,IAAIZ,IAAIa,IAAI,KAAK,SAAS;gBACxB,IAAIC,QAAQJ,QAAQK,OAAO,CAAClM,UAAUmM,UAAU,IAAIC,KAAK,CAACb;gBAC1D,IAAIJ,IAAIkB,MAAM,EAAEJ,QAAQA,MAAMK,UAAU,CAACnB,IAAIkB,MAAM;gBACnDN,OAAO,MAAME,MAAMnJ,GAAG;YACxB,OAAO;gBACLiJ,OAAO,MAAMF,QAAQlD,KAAK,CAAClI,2BAA2B,kBAAkBkE,QAAQyH,KAAK,CAACb,UAAUzI,GAAG;YACrG;YACA,MAAME,OAAOL,KAAKC,GAAG;YACrB,yDAAyD;YACzD,MAAM2J,SAASR,KAAKnD,IAAI,CACrBa,MAAM,CAAC,CAACtG;oBACSA;gBAAhB,MAAMc,UAAUd,YAAAA,IAAIC,IAAI,cAARD,YAAc,CAAC;gBAC/B,MAAMY,OAAO/C,kBACXiD,QACApD,qBAAqBwD,KAAMd,MAAM,EAAEU,QAAQhB,OAAOD,MAAM+B;gBAE1D,OAAO3D,mBAAmB6C,QAAQF;YACpC,GACCK,GAAG,CAAC,CAACjB,MAAQA,IAAIQ,EAAE;YACtB,IAAIwH,IAAIa,IAAI,KAAK,YAAYD,KAAKnD,IAAI,CAACxC,MAAM,IAAI2F,KAAKnD,IAAI,CAAC4D,KAAK,CAAC,CAACrJ,MAAQ2I,KAAKlH,GAAG,CAACzB,IAAIQ,EAAE,IAAI;gBAC3F,MAAM,IAAI8I,MAAM;YAClB;YACA,KAAK,MAAMtJ,OAAO4I,KAAKnD,IAAI,CAAEkD,KAAKY,GAAG,CAACvJ,IAAIQ,EAAE;YAC5C,MAAMoC,UAAU,MAAMP,cACpB;gBAAEjD;gBAAWJ;gBAAOoB,QAAQc,KAAKd,MAAM;gBAAEN;gBAAOF,OAAO;gBAAMgC;gBAAS/B;YAAK,GAC3EuJ;YAEF,MAAMI,OAAOxB,IAAIa,IAAI,KAAK,UAAUD,KAAKa,IAAI,GAAGrB,WAAWQ,KAAKc,KAAK;YACrE1B,MAAM,aACDA;gBACH2B,WAAW3B,IAAI2B,SAAS,GAAGf,KAAKa,IAAI;gBACpCjH,SAASwF,IAAIxF,OAAO,GAAGI,QAAQJ,OAAO,CAACS,MAAM;gBAC7CiG,QAAQlB,IAAIa,IAAI,KAAK,WAAWD,iBAAAA,cAAAA,KAAKnD,IAAI,CAACmD,KAAKnD,IAAI,CAACxC,MAAM,GAAG,EAAE,qBAA/B2F,YAAiCpI,EAAE,oBAAIwH,IAAIkB,MAAM,YAAjDN,OAAqD,OAAQ;eACzFY,OAAO;gBAAEhB,QAAQ;gBAAiBoB,cAAcpK,KAAKC,GAAG;YAAG,IAAI,CAAC;YAEtEyB,OAAO,MAAM6G,SAAS3I,WAAWJ,OAAOwC,QAAQwG,KAAK;gBACnDE,UAAUsB,QAAQtI,KAAK2I,QAAQ,KAAK;YACtC;YACA,IAAIL,QAAQ,CAACtI,MAAM,OAAO;gBAAEA;gBAAMuH,MAAM;YAAM;QAChD;QACA,OAAO;YAAEvH;YAAMuH,MAAM;QAAK;IAC5B,EAAE,OAAO9E,OAAO;QACdC,QAAQD,KAAK,CAAC,mCAAmC3E,OAAOwC,QAAQmC;QAChEzC,OAAO,MAAM6G,SAAS3I,WAAWJ,OAAOwC,QAAQ,aAC3CwG;YACHQ,QAAQ;YACRoB,cAAcpK,KAAKC,GAAG;YACtBkE,OAAO;;QAET,OAAO;YAAEzC;YAAMuH,MAAM;QAAM;IAC7B;AACF;AAEA;;2CAE2C,GAE3C,oFAAoF,GACpF,MAAMqB,gBAAgB;AAEtB,MAAMC,gBACJ;AAEF,SAASC,QAAQ9D,GAAY;IAC3B,IAAI,CAAC7E,MAAMC,OAAO,CAAC4E,MAAM,OAAO;IAChC,MAAM5D,MAAM;WACP,IAAItB,IACLkF,IAAIjF,GAAG,CAAC,CAACmF,QAAU1E,OAAO0E,gBAAAA,QAAS,IAAIT,IAAI,GAAGU,KAAK,CAAC,GAAG,MAAMC,MAAM,CAAC,CAAC9F,KAAOA,MAAM,CAACA,GAAGyG,QAAQ,CAAC;KAElG,CAACZ,KAAK,CAAC,GAAGjJ;IACX,OAAOkF,IAAIW,MAAM,GAAGX,MAAM;AAC5B;AAEA;;;;;;;;;CASC,GACD,OAAO,MAAM2H,oBAAsC,OAAOC,KAAKC;QAM/CD,WAMQtF;IAXtB,IAAIsF,IAAIE,MAAM,KAAK,QAAQ;QACzBD,IAAIE,SAAS,CAAC,SAAS;QACvBF,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;YAAE3G,OAAO;QAAqB;QACnD;IACF;IACA,MAAMiB,QAAQsF,YAAAA,IAAItF,IAAI,YAARsF,YAAY,CAAC;IAC3B,MAAMK,QAAQ9L,kBAAkBmG;IAChC,IAAI,CAAC2F,OAAO;QACVJ,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;YAAE3G,OAAO;QAAiB;QAC/C;IACF;IACA,MAAM6G,SAAS9I,QAAOkD,eAAAA,IAAI,CAAC,SAAS,YAAdA,eAAkB;IACxC,MAAMxF,YAAY3C,cAAciH,GAAG,GAAGtE,SAAS;IAC/C,IAAI;;YAsBM;QArBR,IAAIoL,WAAW,YAAY;YACzB,MAAMpK,SAASwE,IAAI,CAAC,SAAS;YAC7B,MAAMtC,MAAM0H,QAAQpF,IAAI,CAAC,MAAM;YAC/B,IAAI,CAAChH,mBAAmBwC,WAAW,CAACkC,KAAK;gBACvC6H,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;oBAAE3G,OAAO;gBAAoB;gBAClD;YACF;YACA,MAAM8G,SAAS,MAAM9L,mBAAmBuL,KAAKK,OAAO;gBAAEG,UAAU;YAAK;YACrE,IAAID,OAAOE,EAAE,KAAK,OAAO;gBACvBR,IAAI3B,MAAM,CAACiC,OAAOjC,MAAM,EAAE8B,IAAI,CAACG,OAAO7F,IAAI;gBAC1C;YACF;YACA,MAAMhC,UAAU,MAAMQ,mBAAmBhE,WAAWqL,OAAOzL,KAAK,EAAEoB,QAAQkC;YAC1E,MAAMsI,SAA6B;gBAAED,IAAI;eAAS/H;YAClDuH,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAACM;YACrB;QACF;QAEA,MAAM5L,QACJuL,MAAMM,KAAK,KAAK,QACZN,MAAMvL,KAAK,YACT,QAAA,MAAMtC,cAAc6N,MAAM/F,MAAM,EAAEsG,KAAK,CAAC,IAAM,0BAA/C,AAAC,MAAsD9L,KAAK,mBAAI;QACvE,IAAI,CAACA,OAAO;YACVmL,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;gBAAE3G,OAAO;YAAmD;YACjF;QACF;QACA,MAAMoH,SAAS,MAAMxM,mBAAmB2L,KAAKlL,OAAO;YAClDgM,OAAO;YACPC,SAASlB;QACX;QACA,IAAIgB,OAAOJ,EAAE,KAAK,OAAO;YACvBR,IAAI3B,MAAM,CAACuC,OAAOvC,MAAM,EAAE8B,IAAI,CAAC;gBAAE3G,OAAOoH,OAAOpH,KAAK;YAAC;YACrD;QACF;QACA,MAAMuH,QAAQxM,gBAAgBqM,OAAOpJ,GAAG,EAAE;QAC1C,IAAIuJ,OAAO;YACTf,IAAI3B,MAAM,CAAC0C,MAAM1C,MAAM,EAAE8B,IAAI,CAACY,MAAMtG,IAAI;YACxC;QACF;QACA,MAAMuG,QAAQ;YAAEpG,KAAKgG,OAAOhG,GAAG;YAAEqG,OAAOL,OAAOK,KAAK;QAAC;QACrD,MAAMnG,SAAS8F,OAAO/F,IAAI,IAAI+F,OAAOK,KAAK,IAAI;QAC9C,MAAM7L,QAAQC,KAAKC,GAAG;QAEtB,IAAI+K,WAAW,SAAS;YACtB,MAAMI,SAA6B;gBAAED,IAAI;gBAAMU,OAAO,MAAM/F,gBAAgBlG,WAAWJ;YAAO;YAC9FmL,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAACM;YACrB;QACF;QAEA,IAAIJ,WAAW,WAAWA,WAAW,WAAW;YAC9C,MAAMpK,SAASwE,IAAI,CAAC,SAAS;YAC7B,MAAMtC,MAAM0H,QAAQpF,IAAI,CAAC,MAAM;YAC/B,IAAI,CAAChH,mBAAmBwC,WAAW,CAACkC,KAAK;gBACvC6H,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;oBAAE3G,OAAO;gBAA6B;gBAC3D;YACF;YACA,MAAMW,UAAUpG,iBAAiB0G,IAAI,CAAC,UAAU;YAChD,IAAI,CAACN,SAAS;gBACZ6F,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;oBAAE3G,OAAO;gBAAkC;gBAChE;YACF;YACA,MAAM0H,QAAQ,MAAM/F,gBAAgBlG,WAAWJ;YAC/C,MAAM0H,MAAM,IAAI1F,IAAIqK,MAAMpK,GAAG,CAAC,CAACqK,OAASA,KAAK9K,EAAE;YAC/C,IAAIhE,iBAAiB8H,SAAS/C,IAAI,CAAC,CAACf,KAAO,CAACkG,IAAIjF,GAAG,CAACjB,MAAM;gBACxD2J,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;oBAAE3G,OAAO;gBAAkD;gBAChF;YACF;YACA,MAAMkD,SAAyBjC,IAAI,CAAC,SAAS,KAAK,SAAS,SAAS;YACpE,MAAM,EAAE9E,KAAK,EAAEF,KAAK,EAAE,GAAG,MAAMT,kBAAkBC,WAAWJ;YAC5D,MAAM4C,UAAU,MAAMzB,qBAAqBf,WAAWJ,OAAOc,OAAOM;YACpE,MAAMwC,UAAU,MAAMP,cACpB;gBACEjD;gBACAJ;gBACAoB;gBACAN;gBACAF;gBACAgC;gBACA/B,MAAMN;gBACNuC,QAAQ,CAACD,SACP2I,WAAW,UACPpM,iBAAiByD,QAAQyC,SAASuC,QAAQ;wBAAE9B,KAAKgG,OAAOhG,GAAG;wBAAEC,MAAMC;oBAAO,GAAG1F,SAC7ElB,oBAAoBwD,QAAQyC;YACpC,GACAhC;YAEF,MAAMiJ,WAAW,CAAC/K;;oBAAe6K;gCAAAA,cAAAA,MAAMlD,IAAI,CAAC,CAACmD,OAASA,KAAK9K,EAAE,KAAKA,wBAAjC6K,YAAsCrG,IAAI,mBAAI;;YAC/E,MAAMwG,QAAQ/N,qBAAqB6G,SAASiH;YAC5C,MAAME,WACJjB,WAAW,UACP,CAAC,YAAY,EAAEgB,QAAQ3E,WAAW,SAAS,qBAAqB,gBAAgB,GAChF,CAAC,qBAAqB,EAAE2E,OAAO;YACrC,MAAM1I,QAAQC,GAAG,CACfH,QAAQJ,OAAO,CAACvB,GAAG,CAAC,CAACT,KACnB2D,oBAAoB/E,WAAW;oBAC7BJ;oBACAoB;oBACAI;oBACAoE,MAAM6G;oBACNnH;oBACAQ,IAAI;wBAAEC,KAAKgG,OAAOhG,GAAG;wBAAEC,MAAMC;oBAAO;oBACpCpF,MAAMN;gBACR;YAGJ,IAAIqD,QAAQJ,OAAO,CAACS,MAAM,EAAE;gBAC1B,MAAMyI,QAAQrO,yBAAyB,CAAC+C,OAAO;gBAC/C,MAAMzD,eACJqC,OACAmM,OACAvI,QAAQJ,OAAO,CAACS,MAAM,KAAK,IACvB,GAAGwI,SAAS,MAAM,EAAEC,MAAMC,GAAG,EAAE,GAC/B,GAAGF,SAAS,EAAE,EAAE7I,QAAQJ,OAAO,CAACS,MAAM,CAAC,CAAC,EAAEyI,MAAME,IAAI,CAACC,WAAW,IAAI,EACxE;oBACEC,MAAM5H,UAAU,CAAC9D,OAAO;mBACpBwC,QAAQJ,OAAO,CAACS,MAAM,KAAK,IAAI;oBAAEzC,IAAIoC,QAAQJ,OAAO,CAAC,EAAE;gBAAC,IAAI,CAAC,IAEnEsI,KAAK,CAAC,IAAM5I;YAChB;YACA,MAAM0I,SAA6B;gBACjCD,IAAI;gBACJnI,SAASI,QAAQJ,OAAO,CAACS,MAAM;gBAC/BR,SAASG,QAAQH,OAAO;YAC1B;YACA0H,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAACM;YACrB;QACF;QAEA,IAAIJ,WAAW,aAAa;gBAG6B;YAFvD,MAAM7D,UAAU,MAAMnI,WAAWY,WAAWJ;YAC5C,MAAMkH,MAAMtB,IAAI,CAAC,OAAO;YACxB,MAAMpD,SAAS0E,OAAO,OAAOA,QAAQ,WAAWxE,QAAO,UAAA,AAACwE,GAA+B,CAAC,KAAK,YAAtC,UAA0C,MAAM;YACvG,IAAI6F,QAA+B;YACnC,IAAIpI,QAAuB;YAC3B,MAAMsE,UAAU,MAAML,WAAWxI,WAAWJ,OAAO,CAACc;oBACvBA;gBAA3B,MAAMqH,WAAW3F,UAAU1B,cAAAA,MAAMqI,IAAI,CAAC,CAACjH,OAASA,KAAKV,EAAE,KAAKgB,mBAAjC1B,cAA4C,OAAQ;gBAC/E,IAAI0B,UAAU,CAAC2F,UAAU,OAAO;oBAAExD,OAAO;gBAA8B;gBACvE,IAAIwD,4BAAAA,SAAU0C,QAAQ,EAAE,OAAO;oBAAElG,OAAO;gBAA8B;gBACtE,IAAI,CAACwD,YAAYrH,MAAMmD,MAAM,IAAI1F,uBAAuB;oBACtD,OAAO;wBAAEoG,OAAO,CAAC,0BAA0B,EAAEpG,sBAAsB,eAAe,CAAC;oBAAC;gBACtF;gBACA,MAAMyO,SAASxF,gBAAgBN,KAAK;oBAAES;oBAASQ;oBAAUpC,KAAKgG,OAAOhG,GAAG;oBAAExF;gBAAM;gBAChF,IAAI,CAACyM,OAAO9K,IAAI,EAAE;oBAChByC,QAAQqI,OAAOrI,KAAK;oBACpB,OAAO;wBAAEA,OAAOqI,OAAOrI,KAAK;oBAAC;gBAC/B;gBACA;;;;SAIC,GACDoI,QAAQ,aACHC,OAAO9K,IAAI;oBACd8G,KAAK;wBACHQ,QAAQ;wBACRK,MAAMmD,OAAO9K,IAAI,CAACkG,OAAO,GAAG,UAAU;wBACtCuC,WAAW;wBACXnH,SAAS;wBACT0G,QAAQ;wBACR+C,aAAa1M;oBACf;;gBAEF,OAAO4H,WACHrH,MAAMmB,GAAG,CAAC,CAACC,OAAUA,KAAKV,EAAE,KAAK2G,SAAS3G,EAAE,GAAGuL,QAAS7K,QACxD;uBAAIpB;oBAAOiM;iBAAM;YACvB;YACA,IAAI,CAAC9D,QAAQnI,KAAK,IAAI,CAACiM,OAAO;oBACEpI;gBAA9BwG,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;oBAAE3G,KAAK,GAAEA,QAAAA,gBAAAA,QAASsE,QAAQtE,KAAK,YAAtBA,QAA0B;gBAA+B;gBACvF;YACF;YACA,MAAMzC,OAAO6K;YACb,MAAMpP,eACJqC,OACAmM,OACA,GAAG3J,SAAS,YAAY,QAAQ,mBAAmB,EAAEN,KAAK8D,IAAI,CAAC,CAAC,CAAC,EACjE;gBAAE8G,MAAM,GAAG/O,UAAU,YAAY,CAAC;gBAAEyD,IAAIU,KAAKV,EAAE;gBAAEwE,MAAM9D,KAAK8D,IAAI;YAAC,GACjE8F,KAAK,CAAC,IAAM5I;YACd,MAAMgK,WAAW,MAAM7D,sBAAsBjJ,WAAWJ,OAAOkC,KAAKV,EAAE,EAAEhB,KAAKC,GAAG,KAAKqK;YACrF,MAAMc,SAA6B;gBAAED,IAAI;gBAAMzJ,MAAMgL,SAAShL,IAAI;gBAAEuH,MAAMyD,SAASzD,IAAI;YAAC;YACxF0B,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAACM;YACrB;QACF;QAEA,IAAIJ,WAAW,iBAAiBA,WAAW,iBAAiB;gBACpC5F;YAAtB,MAAMpD,SAASE,QAAOkD,eAAAA,IAAI,CAAC,SAAS,YAAdA,eAAkB,IAAIe,IAAI;YAChD,IAAI,CAACnE,QAAQ;gBACX2I,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;oBAAE3G,OAAO;gBAAiB;gBAC/C;YACF;YACA,IAAI6G,WAAW,eAAe;gBAC5B,IAAIxF,OAAO;gBACX,MAAMiD,UAAU,MAAML,WAAWxI,WAAWJ,OAAO,CAACc;oBAClD,MAAMqH,WAAWrH,MAAMqI,IAAI,CAAC,CAACjH,OAASA,KAAKV,EAAE,KAAKgB;oBAClD,IAAI,CAAC2F,UAAU,OAAO;wBAAExD,OAAO;oBAA8B;oBAC7DqB,OAAOmC,SAASnC,IAAI;oBACpB,OAAOlF,MAAMmB,GAAG,CAAC,CAACC,OAChBA,KAAKV,EAAE,KAAKgB,SACR,aACKN;4BACHkG,SAAS;4BACTyC,UAAU;4BACVtC,aAAahI;4BACbyI,KAAK;gCACHQ,QAAQ;gCACRK,MAAM;gCACNc,WAAW;gCACXnH,SAAS;gCACT0G,QAAQ;gCACR+C,aAAa1M;4BACf;6BAEF2B;gBAER;gBACA,IAAI,CAAC+G,QAAQnI,KAAK,EAAE;oBAClBqK,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;wBAAE3G,OAAOsE,QAAQtE,KAAK;oBAAC;oBAC5C;gBACF;gBACA,MAAMhH,eAAeqC,OAAOmM,OAAO,CAAC,0BAA0B,EAAEnG,KAAK,CAAC,CAAC,EAAE;oBACvE8G,MAAM,GAAG/O,UAAU,YAAY,CAAC;oBAChCyD,IAAIgB;oBACJwD;gBACF,GAAG8F,KAAK,CAAC,IAAM5I;YACjB,OAAO;gBACL,wCAAwC;gBACxC,MAAM0F,WAAWxI,WAAWJ,OAAO,CAACc,QAClCA,MAAMmB,GAAG,CAAC,CAACC;4BACaA;+BAAtBA,KAAKV,EAAE,KAAKgB,UAAUN,EAAAA,YAAAA,KAAK8G,GAAG,qBAAR9G,UAAUsH,MAAM,MAAK,WACvC,aAAKtH;4BAAM8G,KAAK,aAAK9G,KAAK8G,GAAG;gCAAEQ,QAAQ;;6BACvCtH;;YAGV;YACA,MAAMgL,WAAW,MAAM7D,sBAAsBjJ,WAAWJ,OAAOwC,QAAQhC,KAAKC,GAAG,KAAKqK;YACpF,MAAMc,SAA6B;gBAAED,IAAI;gBAAMzJ,MAAMgL,SAAShL,IAAI;gBAAEuH,MAAMyD,SAASzD,IAAI;YAAC;YACxF0B,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAACM;YACrB;QACF;QAEAT,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;YAAE3G,OAAO;QAA0B;IAC1D,EAAE,OAAOA,OAAO;QACdC,QAAQD,KAAK,CAAC,wBAAwB6G,QAAQ7G;QAC9CwG,IAAI3B,MAAM,CAAC,KAAK8B,IAAI,CAAC;YAAE3G,OAAO;QAA6C;IAC7E;AACF,EAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 deals naming `contactId`, each with that contact moved to `to` —
|
|
19
|
+
* a merge's survivor — or, with `to` null, taken off the deal. A Primary
|
|
20
|
+
* taken off leaves the deal with none, and `contactId` with it. Answers how
|
|
21
|
+
* many deals were written. A failure is logged and that query's sweep
|
|
22
|
+
* stops: a deal still naming the contact reads it as a person who is not
|
|
23
|
+
* there, which is how a deal named a deleted contact before roles existed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sweepDealContactRoles(firestore: FirebaseFirestore.Firestore, deals: FirebaseFirestore.CollectionReference, contactId: string, to: string | null, label: string): Promise<number>;
|
|
26
|
+
/** One deal's update once `contactId` is moved to `to`, or taken off for `null`. */
|
|
27
|
+
export declare function dealContactRolesUpdate(deal: Record<string, unknown>, contactId: string, to: string | null): Record<string, unknown>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { crmListFieldsPatch, dealContactRoleFields, dealContactRolesOf, dealContactRolesRepointed, dealContactRolesWithout } from "@aglyn/aglyn/app-utils/crm";
|
|
18
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
19
|
+
/**
|
|
20
|
+
* A CONTACT LEAVING THE DEALS IT IS ON (AGL-3521): Salesforce's Opportunity
|
|
21
|
+
* Contact Roles, kept in step with a contact that is merged away, deleted or
|
|
22
|
+
* erased.
|
|
23
|
+
*
|
|
24
|
+
* A deal names its contacts twice over — every one in `contactRoles`, and
|
|
25
|
+
* the Primary as `contactId` — so a deal is found by either:
|
|
26
|
+
* `contactRoleContactIds` (the list field every writer stamps from the
|
|
27
|
+
* roles) and `contactId` (a deal written before roles, which holds that
|
|
28
|
+
* alone). Each match is rewritten through the one pure rule
|
|
29
|
+
* (`dealContactRolesWithout` / `dealContactRolesRepointed`), its Primary and
|
|
30
|
+
* `contactId` written together and its list fields restamped, so a written
|
|
31
|
+
* deal falls out of both queries and the next pass finds only what is left.
|
|
32
|
+
*/ /** How many deals one pass reads, and one batch writes. */ const PAGE = 400;
|
|
33
|
+
/** A ceiling on passes, so a query that keeps answering cannot loop forever. */ const PASSES = 25;
|
|
34
|
+
/**
|
|
35
|
+
* The deals naming `contactId`, each with that contact moved to `to` —
|
|
36
|
+
* a merge's survivor — or, with `to` null, taken off the deal. A Primary
|
|
37
|
+
* taken off leaves the deal with none, and `contactId` with it. Answers how
|
|
38
|
+
* many deals were written. A failure is logged and that query's sweep
|
|
39
|
+
* stops: a deal still naming the contact reads it as a person who is not
|
|
40
|
+
* there, which is how a deal named a deleted contact before roles existed.
|
|
41
|
+
*/ export async function sweepDealContactRoles(firestore, deals, contactId, to, label) {
|
|
42
|
+
if (!contactId || contactId === to) return 0;
|
|
43
|
+
let written = 0;
|
|
44
|
+
// Each query on its own: a deal the first could not reach may still name
|
|
45
|
+
// the contact as its `contactId`.
|
|
46
|
+
for (const field of [
|
|
47
|
+
'contactRoleContactIds',
|
|
48
|
+
'contactId'
|
|
49
|
+
]){
|
|
50
|
+
try {
|
|
51
|
+
const query = deals.where(field, field === 'contactId' ? '==' : 'array-contains', contactId);
|
|
52
|
+
for(let pass = 0; pass < PASSES; pass += 1){
|
|
53
|
+
const page = await query.limit(PAGE).get();
|
|
54
|
+
if (page.empty) break;
|
|
55
|
+
const batch = firestore.batch();
|
|
56
|
+
for (const deal of page.docs){
|
|
57
|
+
var _deal_data;
|
|
58
|
+
batch.update(deal.ref, dealContactRolesUpdate((_deal_data = deal.data()) != null ? _deal_data : {}, contactId, to));
|
|
59
|
+
}
|
|
60
|
+
await batch.commit();
|
|
61
|
+
written += page.size;
|
|
62
|
+
if (page.size < PAGE) break;
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`${label}: deal contact roles sweep failed on ${field}`, error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return written;
|
|
69
|
+
}
|
|
70
|
+
/** One deal's update once `contactId` is moved to `to`, or taken off for `null`. */ export function dealContactRolesUpdate(deal, contactId, to) {
|
|
71
|
+
var _fields_contactId, _fields_contactId1;
|
|
72
|
+
const roles = dealContactRolesOf(deal);
|
|
73
|
+
const next = to === null ? dealContactRolesWithout(roles, contactId) : dealContactRolesRepointed(roles, contactId, to);
|
|
74
|
+
const fields = dealContactRoleFields(next);
|
|
75
|
+
const after = _extends({}, deal, {
|
|
76
|
+
contactRoles: fields.contactRoles,
|
|
77
|
+
contactId: (_fields_contactId = fields.contactId) != null ? _fields_contactId : undefined
|
|
78
|
+
});
|
|
79
|
+
return _extends({
|
|
80
|
+
contactRoles: fields.contactRoles,
|
|
81
|
+
contactId: (_fields_contactId1 = fields.contactId) != null ? _fields_contactId1 : FieldValue.delete()
|
|
82
|
+
}, fields.contactId !== deal['contactId'] ? {
|
|
83
|
+
contactName: FieldValue.delete()
|
|
84
|
+
} : {}, crmListFieldsPatch('deals', after), {
|
|
85
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=deal-contact-roles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/deal-contact-roles.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 CrmDealContactRole,\n crmListFieldsPatch,\n dealContactRoleFields,\n dealContactRolesOf,\n dealContactRolesRepointed,\n dealContactRolesWithout,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { FieldValue } from 'firebase-admin/firestore'\n\n/**\n * A CONTACT LEAVING THE DEALS IT IS ON (AGL-3521): Salesforce's Opportunity\n * Contact Roles, kept in step with a contact that is merged away, deleted or\n * erased.\n *\n * A deal names its contacts twice over — every one in `contactRoles`, and\n * the Primary as `contactId` — so a deal is found by either:\n * `contactRoleContactIds` (the list field every writer stamps from the\n * roles) and `contactId` (a deal written before roles, which holds that\n * alone). Each match is rewritten through the one pure rule\n * (`dealContactRolesWithout` / `dealContactRolesRepointed`), its Primary and\n * `contactId` written together and its list fields restamped, so a written\n * deal falls out of both queries and the next pass finds only what is left.\n */\n\n/** How many deals one pass reads, and one batch writes. */\nconst PAGE = 400\n/** A ceiling on passes, so a query that keeps answering cannot loop forever. */\nconst PASSES = 25\n\n/**\n * The deals naming `contactId`, each with that contact moved to `to` —\n * a merge's survivor — or, with `to` null, taken off the deal. A Primary\n * taken off leaves the deal with none, and `contactId` with it. Answers how\n * many deals were written. A failure is logged and that query's sweep\n * stops: a deal still naming the contact reads it as a person who is not\n * there, which is how a deal named a deleted contact before roles existed.\n */\nexport async function sweepDealContactRoles(\n firestore: FirebaseFirestore.Firestore,\n deals: FirebaseFirestore.CollectionReference,\n contactId: string,\n to: string | null,\n label: string,\n): Promise<number> {\n if (!contactId || contactId === to) return 0\n let written = 0\n // Each query on its own: a deal the first could not reach may still name\n // the contact as its `contactId`.\n for (const field of ['contactRoleContactIds', 'contactId'] as const) {\n try {\n const query = deals.where(field, field === 'contactId' ? '==' : 'array-contains', contactId)\n for (let pass = 0; pass < PASSES; pass += 1) {\n const page = await query.limit(PAGE).get()\n if (page.empty) break\n const batch = firestore.batch()\n for (const deal of page.docs) {\n batch.update(deal.ref, dealContactRolesUpdate(deal.data() ?? {}, contactId, to))\n }\n await batch.commit()\n written += page.size\n if (page.size < PAGE) break\n }\n } catch (error) {\n console.error(`${label}: deal contact roles sweep failed on ${field}`, error)\n }\n }\n return written\n}\n\n/** One deal's update once `contactId` is moved to `to`, or taken off for `null`. */\nexport function dealContactRolesUpdate(\n deal: Record<string, unknown>,\n contactId: string,\n to: string | null,\n): Record<string, unknown> {\n const roles = dealContactRolesOf(deal)\n const next: CrmDealContactRole[] =\n to === null ? dealContactRolesWithout(roles, contactId) : dealContactRolesRepointed(roles, contactId, to)\n const fields = dealContactRoleFields(next)\n const after = { ...deal, contactRoles: fields.contactRoles, contactId: fields.contactId ?? undefined }\n return {\n contactRoles: fields.contactRoles,\n contactId: fields.contactId ?? FieldValue.delete(),\n // The name the drawer copied beside the Primary goes when the Primary does.\n ...(fields.contactId !== deal['contactId'] ? { contactName: FieldValue.delete() } : {}),\n ...crmListFieldsPatch('deals', after),\n updatedAt: FieldValue.serverTimestamp(),\n }\n}\n"],"names":["crmListFieldsPatch","dealContactRoleFields","dealContactRolesOf","dealContactRolesRepointed","dealContactRolesWithout","FieldValue","PAGE","PASSES","sweepDealContactRoles","firestore","deals","contactId","to","label","written","field","query","where","pass","page","limit","get","empty","batch","deal","docs","update","ref","dealContactRolesUpdate","data","commit","size","error","console","fields","roles","next","after","contactRoles","undefined","delete","contactName","updatedAt","serverTimestamp"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAEEA,kBAAkB,EAClBC,qBAAqB,EACrBC,kBAAkB,EAClBC,yBAAyB,EACzBC,uBAAuB,QAClB,6BAA4B;AACnC,SAASC,UAAU,QAAQ,2BAA0B;AAErD;;;;;;;;;;;;;CAaC,GAED,yDAAyD,GACzD,MAAMC,OAAO;AACb,8EAA8E,GAC9E,MAAMC,SAAS;AAEf;;;;;;;CAOC,GACD,OAAO,eAAeC,sBACpBC,SAAsC,EACtCC,KAA4C,EAC5CC,SAAiB,EACjBC,EAAiB,EACjBC,KAAa;IAEb,IAAI,CAACF,aAAaA,cAAcC,IAAI,OAAO;IAC3C,IAAIE,UAAU;IACd,yEAAyE;IACzE,kCAAkC;IAClC,KAAK,MAAMC,SAAS;QAAC;QAAyB;KAAY,CAAW;QACnE,IAAI;YACF,MAAMC,QAAQN,MAAMO,KAAK,CAACF,OAAOA,UAAU,cAAc,OAAO,kBAAkBJ;YAClF,IAAK,IAAIO,OAAO,GAAGA,OAAOX,QAAQW,QAAQ,EAAG;gBAC3C,MAAMC,OAAO,MAAMH,MAAMI,KAAK,CAACd,MAAMe,GAAG;gBACxC,IAAIF,KAAKG,KAAK,EAAE;gBAChB,MAAMC,QAAQd,UAAUc,KAAK;gBAC7B,KAAK,MAAMC,QAAQL,KAAKM,IAAI,CAAE;wBACkBD;oBAA9CD,MAAMG,MAAM,CAACF,KAAKG,GAAG,EAAEC,wBAAuBJ,aAAAA,KAAKK,IAAI,cAATL,aAAe,CAAC,GAAGb,WAAWC;gBAC9E;gBACA,MAAMW,MAAMO,MAAM;gBAClBhB,WAAWK,KAAKY,IAAI;gBACpB,IAAIZ,KAAKY,IAAI,GAAGzB,MAAM;YACxB;QACF,EAAE,OAAO0B,OAAO;YACdC,QAAQD,KAAK,CAAC,GAAGnB,MAAM,qCAAqC,EAAEE,OAAO,EAAEiB;QACzE;IACF;IACA,OAAOlB;AACT;AAEA,kFAAkF,GAClF,OAAO,SAASc,uBACdJ,IAA6B,EAC7Bb,SAAiB,EACjBC,EAAiB;QAMsDsB,mBAG1DA;IAPb,MAAMC,QAAQjC,mBAAmBsB;IACjC,MAAMY,OACJxB,OAAO,OAAOR,wBAAwB+B,OAAOxB,aAAaR,0BAA0BgC,OAAOxB,WAAWC;IACxG,MAAMsB,SAASjC,sBAAsBmC;IACrC,MAAMC,QAAQ,aAAKb;QAAMc,cAAcJ,OAAOI,YAAY;QAAE3B,SAAS,GAAEuB,oBAAAA,OAAOvB,SAAS,YAAhBuB,oBAAoBK;;IAC3F,OAAO;QACLD,cAAcJ,OAAOI,YAAY;QACjC3B,SAAS,GAAEuB,qBAAAA,OAAOvB,SAAS,YAAhBuB,qBAAoB7B,WAAWmC,MAAM;OAE5CN,OAAOvB,SAAS,KAAKa,IAAI,CAAC,YAAY,GAAG;QAAEiB,aAAapC,WAAWmC,MAAM;IAAG,IAAI,CAAC,GAClFxC,mBAAmB,SAASqC;QAC/BK,WAAWrC,WAAWsC,eAAe;;AAEzC"}
|
|
@@ -63,6 +63,11 @@ export interface LeadConvertRequest {
|
|
|
63
63
|
currency?: string;
|
|
64
64
|
/** A stage of the default pipeline; its first open stage when absent. */
|
|
65
65
|
stageId?: string;
|
|
66
|
+
/**
|
|
67
|
+
* One of the org's deal types (AGL-3516); the list's default when
|
|
68
|
+
* absent. The deal's lead source is the lead's own.
|
|
69
|
+
*/
|
|
70
|
+
type?: string;
|
|
66
71
|
};
|
|
67
72
|
}
|
|
68
73
|
/** What the route answers on success. */
|
|
@@ -32,11 +32,12 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
32
32
|
* Checked here with the Admin SDK rather than trusted from the client,
|
|
33
33
|
* because this route writes into collections the caller's own rules would
|
|
34
34
|
* otherwise decide about.
|
|
35
|
-
*/ import { CONTACT_ERASED_MESSAGE, CRM_RECORDS_BAND_FULL_MESSAGE, normalizeCompanyDomain } from "@aglyn/aglyn/server";
|
|
35
|
+
*/ import { CONTACT_ERASED_MESSAGE, CRM_RECORDS_BAND_FULL_MESSAGE, judgeCrmPicklistValue, normalizeCompanyDomain } from "@aglyn/aglyn/server";
|
|
36
36
|
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
37
37
|
import { convertHostLead } from "./convert-host-lead.js";
|
|
38
38
|
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
39
39
|
import { holdsDataManage } from "./org-caller.js";
|
|
40
|
+
import { readCrmPicklist } from "./read-picklist.js";
|
|
40
41
|
import { crmSuiteRefusal } from "./suite-gate.js";
|
|
41
42
|
// The stage picker moved to the runtime with the writes; re-exported so the
|
|
42
43
|
// pipeline code that imports it from here keeps its import.
|
|
@@ -97,7 +98,8 @@ const CURRENCY_PATTERN = /^[a-z]{3}$/;
|
|
|
97
98
|
}
|
|
98
99
|
};
|
|
99
100
|
export const leadConvertHandler = async (req, res)=>{
|
|
100
|
-
var _req_headers_authorization, _req_body, _body_hostId, _body_leadId, _body_companyId;
|
|
101
|
+
var _req_headers_authorization, _req_body, _body_hostId, _body_leadId, _body_companyId, _ref;
|
|
102
|
+
var _body_deal;
|
|
101
103
|
if (req.method !== 'POST') {
|
|
102
104
|
res.setHeader('Allow', 'POST');
|
|
103
105
|
res.status(405).json({
|
|
@@ -169,6 +171,7 @@ export const leadConvertHandler = async (req, res)=>{
|
|
|
169
171
|
stageId: body.deal.stageId ? String(body.deal.stageId) : undefined
|
|
170
172
|
};
|
|
171
173
|
}
|
|
174
|
+
const requestedType = deal ? String((_ref = (_body_deal = body.deal) == null ? void 0 : _body_deal.type) != null ? _ref : '').trim() : '';
|
|
172
175
|
try {
|
|
173
176
|
var _decoded_email;
|
|
174
177
|
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
@@ -203,8 +206,21 @@ export const leadConvertHandler = async (req, res)=>{
|
|
|
203
206
|
res.status(suite.status).json(suite.body);
|
|
204
207
|
return;
|
|
205
208
|
}
|
|
209
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
210
|
+
// The deal's Type against the org's list (AGL-3516), before any write.
|
|
211
|
+
if (deal && requestedType) {
|
|
212
|
+
const judged = judgeCrmPicklistValue('opportunityType', await readCrmPicklist(firestore, orgId, 'opportunityType'), requestedType);
|
|
213
|
+
if (judged.ok === false) {
|
|
214
|
+
res.status(400).json({
|
|
215
|
+
error: judged.error,
|
|
216
|
+
field: 'deal.type'
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (judged.value) deal.type = judged.value;
|
|
221
|
+
}
|
|
206
222
|
const result = await convertHostLead(_extends({
|
|
207
|
-
firestore
|
|
223
|
+
firestore,
|
|
208
224
|
hostId,
|
|
209
225
|
orgId,
|
|
210
226
|
org: org,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/lead-convert.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 * `POST /api/crm/lead-convert` — a lead becomes a contact, and optionally a\n * company and a deal (AGL-2608).\n *\n * This is the console's door onto `convertHostLead` (the tenancy runtime),\n * which owns the four writes, their order, and the idempotency on the lead;\n * the REST API's `POST /v1/leads/{id}/convert` is the other door onto the\n * same function (AGL-2627). What lives here is what only this door knows:\n * the ID token, the body the convert dialog posts, and the statuses and\n * sentences the dialog shows.\n *\n * ## What the caller has to be\n *\n * A signed-in member of the host's org holding `data.manage` — the same key\n * the console gate on the CRM surface enforces — with a role on this site.\n * Checked here with the Admin SDK rather than trusted from the client,\n * because this route writes into collections the caller's own rules would\n * otherwise decide about.\n */\n\nimport {\n CONTACT_ERASED_MESSAGE,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n normalizeCompanyDomain,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport {\n type ConvertHostLeadRefusal,\n convertHostLead,\n} from './convert-host-lead'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { holdsDataManage } from './org-caller'\nimport { crmSuiteRefusal } from './suite-gate'\n\n// The stage picker moved to the runtime with the writes; re-exported so the\n// pipeline code that imports it from here keeps its import.\nexport { stageForNewDeal } from './convert-host-lead'\n\n/** The body the console's convert dialog posts. */\nexport interface LeadConvertRequest {\n hostId: string\n /** `hosts/{hostId}/leads/{leadId}` — the document id, which is the person key. */\n leadId: string\n /**\n * Who owns the resulting contact (and deal). Defaults to the lead's own\n * owner; failing that the org's assignment rules and the site's default\n * owner decide (AGL-2618), and failing those the caller — somebody\n * converted this person, and a record with no owner is one nobody\n * follows up.\n */\n ownerUid?: string\n /** Link an existing `orgs/{orgId}/companies/{companyId}`. */\n companyId?: string\n /** Or create one. Ignored when `companyId` is given. */\n createCompany?: { name: string; domain?: string }\n /** Open a deal in the org's default pipeline. */\n deal?: {\n title: string\n amountCents?: number\n /** Lowercase ISO 4217; `usd` when absent or malformed. */\n currency?: string\n /** A stage of the default pipeline; its first open stage when absent. */\n stageId?: string\n }\n}\n\n/** What the route answers on success. */\nexport interface LeadConvertResponse {\n ok: true\n contactId: string\n companyId?: string\n dealId?: string\n /** The lead was converted before this call; nothing was created now. */\n alreadyConverted: boolean\n}\n\nconst COMPANY_NAME_MAX = 120\nconst DEAL_TITLE_MAX = 200\nconst CURRENCY_PATTERN = /^[a-z]{3}$/\n\n/**\n * How each refusal the writes can answer reads at this door — the status\n * and the sentence the convert dialog has always shown for it.\n */\nconst REFUSALS: Record<\n ConvertHostLeadRefusal,\n { status: number; body: Record<string, unknown> }\n> = {\n 'unknown-lead': { status: 404, body: { error: 'Unknown lead' } },\n 'no-email': {\n status: 422,\n body: { error: 'This lead has no usable email address to convert' },\n },\n 'contact-not-created': {\n status: 409,\n body: {\n error:\n 'The contact could not be created — the audience band may be ' +\n 'full. Nothing was changed.',\n },\n },\n 'band-full': {\n status: 409,\n body: { error: CRM_RECORDS_BAND_FULL_MESSAGE, reason: 'band' },\n },\n // The contact create route's own answer for an erased address (AGL-2623),\n // so the convert dialog and the add-contact drawer say one thing about\n // one person.\n erased: {\n status: 409,\n body: { error: CONTACT_ERASED_MESSAGE, reason: 'erased' },\n },\n 'unknown-company': { status: 404, body: { error: 'Unknown company' } },\n 'no-stages': {\n status: 409,\n body: { error: 'The default pipeline has no stages to open a deal in' },\n },\n}\n\nexport const leadConvertHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) {\n res.status(401).json({ error: 'Unauthenticated' })\n return\n }\n const body: Partial<LeadConvertRequest> =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '').trim()\n const leadId = String(body.leadId ?? '').trim()\n if (!hostId || !leadId) {\n res.status(400).json({ error: 'Missing hostId or leadId' })\n return\n }\n\n /*\n * Validate the optional parts BEFORE any read, so a malformed request costs\n * nothing and a bad company domain is refused rather than silently dropped\n * — a converter who typed a domain expects the company to carry it.\n */\n const requestedCompanyId = String(body.companyId ?? '').trim()\n let createCompany: { name: string; domain: string | null } | null = null\n if (!requestedCompanyId && body.createCompany) {\n const name = String(body.createCompany.name ?? '')\n .trim()\n .slice(0, COMPANY_NAME_MAX)\n if (!name) {\n res.status(400).json({ error: 'A company needs a name' })\n return\n }\n const rawDomain = String(body.createCompany.domain ?? '').trim()\n const domain = rawDomain ? normalizeCompanyDomain(rawDomain) : null\n if (rawDomain && !domain) {\n res.status(400).json({ error: 'That does not look like a domain' })\n return\n }\n createCompany = { name, domain }\n }\n let deal: {\n title: string\n amountCents: number | null\n currency: string\n stageId: string | undefined\n } | null = null\n if (body.deal) {\n const title = String(body.deal.title ?? '')\n .trim()\n .slice(0, DEAL_TITLE_MAX)\n if (!title) {\n res.status(400).json({ error: 'A deal needs a title' })\n return\n }\n const amount = Number(body.deal.amountCents)\n const currency = String(body.deal.currency ?? '')\n .trim()\n .toLowerCase()\n deal = {\n title,\n amountCents:\n Number.isFinite(amount) && amount >= 0 ? Math.round(amount) : null,\n currency: CURRENCY_PATTERN.test(currency) ? currency : 'usd',\n stageId: body.deal.stageId ? String(body.deal.stageId) : undefined,\n }\n }\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const staff = decoded['staff'] === true\n /*\n * The console's own gate on the CRM is `data.manage`, asked of the\n * permission catalog (`holdsDataManage`), and additionally that the\n * caller has a role on THIS site — an org member scoped to a sibling site\n * holds the key but not the host, and a lead is the host's record.\n * `resolveOrgPermissions` answers the site role and fails closed on a\n * lookup error when a host is named, so an absent membership refuses.\n */\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (\n !staff &&\n !(membership.hostRole && (await holdsDataManage(membership.orgId, decoded.uid)))\n ) {\n res\n .status(403)\n .json({ error: 'Converting a lead requires the data permission on this site' })\n return\n }\n\n const resolved = await getOrgForHost(hostId)\n if (!resolved) {\n res.status(404).json({ error: 'Unknown site' })\n return\n }\n const { orgId, org } = resolved\n // A lead's conversion is the CRM's, like its status and owner (AGL-2787).\n const suite = crmSuiteRefusal(org, 'Converting a lead')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const result = await convertHostLead({\n firestore: firebaseAdmin.app().firestore(),\n hostId,\n orgId,\n org: org as Record<string, unknown>,\n leadId,\n actor: { uid: decoded.uid, email: decoded.email ?? null, kind: 'member' },\n ...(body.ownerUid ? { ownerUid: String(body.ownerUid) } : {}),\n ...(requestedCompanyId ? { companyId: requestedCompanyId } : {}),\n createCompany,\n deal,\n })\n if (result.ok === false) {\n const refusal = REFUSALS[result.reason]\n res.status(refusal.status).json(refusal.body)\n return\n }\n const answer: LeadConvertResponse = {\n ok: true,\n contactId: result.contactId,\n ...(result.companyId ? { companyId: result.companyId } : {}),\n ...(result.dealId ? { dealId: result.dealId } : {}),\n alreadyConverted: result.alreadyConverted,\n }\n res.status(200).json(answer)\n } catch (error) {\n console.error('lead-convert failed', error)\n res.status(500).json({ error: 'The lead could not be converted.' })\n }\n}\n\nexport default leadConvertHandler\n"],"names":["CONTACT_ERASED_MESSAGE","CRM_RECORDS_BAND_FULL_MESSAGE","normalizeCompanyDomain","firebaseAdmin","getOrgForHost","convertHostLead","resolveOrgPermissions","holdsDataManage","crmSuiteRefusal","stageForNewDeal","COMPANY_NAME_MAX","DEAL_TITLE_MAX","CURRENCY_PATTERN","REFUSALS","status","body","error","reason","erased","leadConvertHandler","req","res","method","setHeader","json","authorization","String","headers","idToken","startsWith","slice","length","undefined","JSON","parse","hostId","trim","leadId","requestedCompanyId","companyId","createCompany","name","rawDomain","domain","deal","title","amount","Number","amountCents","currency","toLowerCase","isFinite","Math","round","test","stageId","decoded","app","auth","verifyIdToken","staff","membership","uid","hostRole","orgId","resolved","org","suite","result","firestore","actor","email","kind","ownerUid","ok","refusal","answer","contactId","dealId","alreadyConverted","console"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/lead-convert.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 * `POST /api/crm/lead-convert` — a lead becomes a contact, and optionally a\n * company and a deal (AGL-2608).\n *\n * This is the console's door onto `convertHostLead` (the tenancy runtime),\n * which owns the four writes, their order, and the idempotency on the lead;\n * the REST API's `POST /v1/leads/{id}/convert` is the other door onto the\n * same function (AGL-2627). What lives here is what only this door knows:\n * the ID token, the body the convert dialog posts, and the statuses and\n * sentences the dialog shows.\n *\n * ## What the caller has to be\n *\n * A signed-in member of the host's org holding `data.manage` — the same key\n * the console gate on the CRM surface enforces — with a role on this site.\n * Checked here with the Admin SDK rather than trusted from the client,\n * because this route writes into collections the caller's own rules would\n * otherwise decide about.\n */\n\nimport {\n CONTACT_ERASED_MESSAGE,\n CRM_RECORDS_BAND_FULL_MESSAGE,\n judgeCrmPicklistValue,\n normalizeCompanyDomain,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport {\n type ConvertHostLeadRefusal,\n convertHostLead,\n} from './convert-host-lead'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { holdsDataManage } from './org-caller'\nimport { readCrmPicklist } from './read-picklist'\nimport { crmSuiteRefusal } from './suite-gate'\n\n// The stage picker moved to the runtime with the writes; re-exported so the\n// pipeline code that imports it from here keeps its import.\nexport { stageForNewDeal } from './convert-host-lead'\n\n/** The body the console's convert dialog posts. */\nexport interface LeadConvertRequest {\n hostId: string\n /** `hosts/{hostId}/leads/{leadId}` — the document id, which is the person key. */\n leadId: string\n /**\n * Who owns the resulting contact (and deal). Defaults to the lead's own\n * owner; failing that the org's assignment rules and the site's default\n * owner decide (AGL-2618), and failing those the caller — somebody\n * converted this person, and a record with no owner is one nobody\n * follows up.\n */\n ownerUid?: string\n /** Link an existing `orgs/{orgId}/companies/{companyId}`. */\n companyId?: string\n /** Or create one. Ignored when `companyId` is given. */\n createCompany?: { name: string; domain?: string }\n /** Open a deal in the org's default pipeline. */\n deal?: {\n title: string\n amountCents?: number\n /** Lowercase ISO 4217; `usd` when absent or malformed. */\n currency?: string\n /** A stage of the default pipeline; its first open stage when absent. */\n stageId?: string\n /**\n * One of the org's deal types (AGL-3516); the list's default when\n * absent. The deal's lead source is the lead's own.\n */\n type?: string\n }\n}\n\n/** What the route answers on success. */\nexport interface LeadConvertResponse {\n ok: true\n contactId: string\n companyId?: string\n dealId?: string\n /** The lead was converted before this call; nothing was created now. */\n alreadyConverted: boolean\n}\n\nconst COMPANY_NAME_MAX = 120\nconst DEAL_TITLE_MAX = 200\nconst CURRENCY_PATTERN = /^[a-z]{3}$/\n\n/**\n * How each refusal the writes can answer reads at this door — the status\n * and the sentence the convert dialog has always shown for it.\n */\nconst REFUSALS: Record<\n ConvertHostLeadRefusal,\n { status: number; body: Record<string, unknown> }\n> = {\n 'unknown-lead': { status: 404, body: { error: 'Unknown lead' } },\n 'no-email': {\n status: 422,\n body: { error: 'This lead has no usable email address to convert' },\n },\n 'contact-not-created': {\n status: 409,\n body: {\n error:\n 'The contact could not be created — the audience band may be ' +\n 'full. Nothing was changed.',\n },\n },\n 'band-full': {\n status: 409,\n body: { error: CRM_RECORDS_BAND_FULL_MESSAGE, reason: 'band' },\n },\n // The contact create route's own answer for an erased address (AGL-2623),\n // so the convert dialog and the add-contact drawer say one thing about\n // one person.\n erased: {\n status: 409,\n body: { error: CONTACT_ERASED_MESSAGE, reason: 'erased' },\n },\n 'unknown-company': { status: 404, body: { error: 'Unknown company' } },\n 'no-stages': {\n status: 409,\n body: { error: 'The default pipeline has no stages to open a deal in' },\n },\n}\n\nexport const leadConvertHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) {\n res.status(401).json({ error: 'Unauthenticated' })\n return\n }\n const body: Partial<LeadConvertRequest> =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '').trim()\n const leadId = String(body.leadId ?? '').trim()\n if (!hostId || !leadId) {\n res.status(400).json({ error: 'Missing hostId or leadId' })\n return\n }\n\n /*\n * Validate the optional parts BEFORE any read, so a malformed request costs\n * nothing and a bad company domain is refused rather than silently dropped\n * — a converter who typed a domain expects the company to carry it.\n */\n const requestedCompanyId = String(body.companyId ?? '').trim()\n let createCompany: { name: string; domain: string | null } | null = null\n if (!requestedCompanyId && body.createCompany) {\n const name = String(body.createCompany.name ?? '')\n .trim()\n .slice(0, COMPANY_NAME_MAX)\n if (!name) {\n res.status(400).json({ error: 'A company needs a name' })\n return\n }\n const rawDomain = String(body.createCompany.domain ?? '').trim()\n const domain = rawDomain ? normalizeCompanyDomain(rawDomain) : null\n if (rawDomain && !domain) {\n res.status(400).json({ error: 'That does not look like a domain' })\n return\n }\n createCompany = { name, domain }\n }\n let deal: {\n title: string\n amountCents: number | null\n currency: string\n stageId: string | undefined\n type?: string\n } | null = null\n if (body.deal) {\n const title = String(body.deal.title ?? '')\n .trim()\n .slice(0, DEAL_TITLE_MAX)\n if (!title) {\n res.status(400).json({ error: 'A deal needs a title' })\n return\n }\n const amount = Number(body.deal.amountCents)\n const currency = String(body.deal.currency ?? '')\n .trim()\n .toLowerCase()\n deal = {\n title,\n amountCents:\n Number.isFinite(amount) && amount >= 0 ? Math.round(amount) : null,\n currency: CURRENCY_PATTERN.test(currency) ? currency : 'usd',\n stageId: body.deal.stageId ? String(body.deal.stageId) : undefined,\n }\n }\n const requestedType = deal ? String(body.deal?.type ?? '').trim() : ''\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const staff = decoded['staff'] === true\n /*\n * The console's own gate on the CRM is `data.manage`, asked of the\n * permission catalog (`holdsDataManage`), and additionally that the\n * caller has a role on THIS site — an org member scoped to a sibling site\n * holds the key but not the host, and a lead is the host's record.\n * `resolveOrgPermissions` answers the site role and fails closed on a\n * lookup error when a host is named, so an absent membership refuses.\n */\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (\n !staff &&\n !(membership.hostRole && (await holdsDataManage(membership.orgId, decoded.uid)))\n ) {\n res\n .status(403)\n .json({ error: 'Converting a lead requires the data permission on this site' })\n return\n }\n\n const resolved = await getOrgForHost(hostId)\n if (!resolved) {\n res.status(404).json({ error: 'Unknown site' })\n return\n }\n const { orgId, org } = resolved\n // A lead's conversion is the CRM's, like its status and owner (AGL-2787).\n const suite = crmSuiteRefusal(org, 'Converting a lead')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const firestore = firebaseAdmin.app().firestore()\n // The deal's Type against the org's list (AGL-3516), before any write.\n if (deal && requestedType) {\n const judged = judgeCrmPicklistValue(\n 'opportunityType',\n await readCrmPicklist(firestore, orgId, 'opportunityType'),\n requestedType,\n )\n if (judged.ok === false) {\n res.status(400).json({ error: judged.error, field: 'deal.type' })\n return\n }\n if (judged.value) deal.type = judged.value\n }\n const result = await convertHostLead({\n firestore,\n hostId,\n orgId,\n org: org as Record<string, unknown>,\n leadId,\n actor: { uid: decoded.uid, email: decoded.email ?? null, kind: 'member' },\n ...(body.ownerUid ? { ownerUid: String(body.ownerUid) } : {}),\n ...(requestedCompanyId ? { companyId: requestedCompanyId } : {}),\n createCompany,\n deal,\n })\n if (result.ok === false) {\n const refusal = REFUSALS[result.reason]\n res.status(refusal.status).json(refusal.body)\n return\n }\n const answer: LeadConvertResponse = {\n ok: true,\n contactId: result.contactId,\n ...(result.companyId ? { companyId: result.companyId } : {}),\n ...(result.dealId ? { dealId: result.dealId } : {}),\n alreadyConverted: result.alreadyConverted,\n }\n res.status(200).json(answer)\n } catch (error) {\n console.error('lead-convert failed', error)\n res.status(500).json({ error: 'The lead could not be converted.' })\n }\n}\n\nexport default leadConvertHandler\n"],"names":["CONTACT_ERASED_MESSAGE","CRM_RECORDS_BAND_FULL_MESSAGE","judgeCrmPicklistValue","normalizeCompanyDomain","firebaseAdmin","getOrgForHost","convertHostLead","resolveOrgPermissions","holdsDataManage","readCrmPicklist","crmSuiteRefusal","stageForNewDeal","COMPANY_NAME_MAX","DEAL_TITLE_MAX","CURRENCY_PATTERN","REFUSALS","status","body","error","reason","erased","leadConvertHandler","req","res","method","setHeader","json","authorization","String","headers","idToken","startsWith","slice","length","undefined","JSON","parse","hostId","trim","leadId","requestedCompanyId","companyId","createCompany","name","rawDomain","domain","deal","title","amount","Number","amountCents","currency","toLowerCase","isFinite","Math","round","test","stageId","requestedType","type","decoded","app","auth","verifyIdToken","staff","membership","uid","hostRole","orgId","resolved","org","suite","firestore","judged","ok","field","value","result","actor","email","kind","ownerUid","refusal","answer","contactId","dealId","alreadyConverted","console"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;CAkBC,GAED,SACEA,sBAAsB,EACtBC,6BAA6B,EAC7BC,qBAAqB,EACrBC,sBAAsB,QAEjB,sBAAqB;AAC5B,SAASC,aAAa,EAAEC,aAAa,QAAQ,2BAA0B;AACvE,SAEEC,eAAe,QACV,yBAAqB;AAC5B,SAASC,qBAAqB,QAAQ,wCAAuC;AAC7E,SAASC,eAAe,QAAQ,kBAAc;AAC9C,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,eAAe,QAAQ,kBAAc;AAE9C,4EAA4E;AAC5E,4DAA4D;AAC5D,SAASC,eAAe,QAAQ,yBAAqB;AA6CrD,MAAMC,mBAAmB;AACzB,MAAMC,iBAAiB;AACvB,MAAMC,mBAAmB;AAEzB;;;CAGC,GACD,MAAMC,WAGF;IACF,gBAAgB;QAAEC,QAAQ;QAAKC,MAAM;YAAEC,OAAO;QAAe;IAAE;IAC/D,YAAY;QACVF,QAAQ;QACRC,MAAM;YAAEC,OAAO;QAAmD;IACpE;IACA,uBAAuB;QACrBF,QAAQ;QACRC,MAAM;YACJC,OACE,iEACA;QACJ;IACF;IACA,aAAa;QACXF,QAAQ;QACRC,MAAM;YAAEC,OAAOjB;YAA+BkB,QAAQ;QAAO;IAC/D;IACA,0EAA0E;IAC1E,uEAAuE;IACvE,cAAc;IACdC,QAAQ;QACNJ,QAAQ;QACRC,MAAM;YAAEC,OAAOlB;YAAwBmB,QAAQ;QAAS;IAC1D;IACA,mBAAmB;QAAEH,QAAQ;QAAKC,MAAM;YAAEC,OAAO;QAAkB;IAAE;IACrE,aAAa;QACXF,QAAQ;QACRC,MAAM;YAAEC,OAAO;QAAuD;IACxE;AACF;AAEA,OAAO,MAAMG,qBAAuC,OAAOC,KAAKC;QAMjCD,4BAS4BA,WACnCL,cACAA,cAWYA;QA6CEA;IAxEpC,IAAIK,IAAIE,MAAM,KAAK,QAAQ;QACzBD,IAAIE,SAAS,CAAC,SAAS;QACvBF,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;YAAER,OAAO;QAAqB;QACnD;IACF;IACA,MAAMS,gBAAgBC,QAAON,6BAAAA,IAAIO,OAAO,CAACF,aAAa,YAAzBL,6BAA6B;IAC1D,MAAMQ,UAAUH,cAAcI,UAAU,CAAC,aACrCJ,cAAcK,KAAK,CAAC,UAAUC,MAAM,IACpCC;IACJ,IAAI,CAACJ,SAAS;QACZP,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;YAAER,OAAO;QAAkB;QAChD;IACF;IACA,MAAMD,OACJ,OAAOK,IAAIL,IAAI,KAAK,WAAWkB,KAAKC,KAAK,CAACd,IAAIL,IAAI,KAAKK,YAAAA,IAAIL,IAAI,YAARK,YAAY,CAAC;IACtE,MAAMe,SAAST,QAAOX,eAAAA,KAAKoB,MAAM,YAAXpB,eAAe,IAAIqB,IAAI;IAC7C,MAAMC,SAASX,QAAOX,eAAAA,KAAKsB,MAAM,YAAXtB,eAAe,IAAIqB,IAAI;IAC7C,IAAI,CAACD,UAAU,CAACE,QAAQ;QACtBhB,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;YAAER,OAAO;QAA2B;QACzD;IACF;IAEA;;;;GAIC,GACD,MAAMsB,qBAAqBZ,QAAOX,kBAAAA,KAAKwB,SAAS,YAAdxB,kBAAkB,IAAIqB,IAAI;IAC5D,IAAII,gBAAgE;IACpE,IAAI,CAACF,sBAAsBvB,KAAKyB,aAAa,EAAE;YACzBzB,0BAOKA;QAPzB,MAAM0B,OAAOf,QAAOX,2BAAAA,KAAKyB,aAAa,CAACC,IAAI,YAAvB1B,2BAA2B,IAC5CqB,IAAI,GACJN,KAAK,CAAC,GAAGpB;QACZ,IAAI,CAAC+B,MAAM;YACTpB,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;gBAAER,OAAO;YAAyB;YACvD;QACF;QACA,MAAM0B,YAAYhB,QAAOX,6BAAAA,KAAKyB,aAAa,CAACG,MAAM,YAAzB5B,6BAA6B,IAAIqB,IAAI;QAC9D,MAAMO,SAASD,YAAYzC,uBAAuByC,aAAa;QAC/D,IAAIA,aAAa,CAACC,QAAQ;YACxBtB,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;gBAAER,OAAO;YAAmC;YACjE;QACF;QACAwB,gBAAgB;YAAEC;YAAME;QAAO;IACjC;IACA,IAAIC,OAMO;IACX,IAAI7B,KAAK6B,IAAI,EAAE;YACQ7B,kBAQGA;QARxB,MAAM8B,QAAQnB,QAAOX,mBAAAA,KAAK6B,IAAI,CAACC,KAAK,YAAf9B,mBAAmB,IACrCqB,IAAI,GACJN,KAAK,CAAC,GAAGnB;QACZ,IAAI,CAACkC,OAAO;YACVxB,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;gBAAER,OAAO;YAAuB;YACrD;QACF;QACA,MAAM8B,SAASC,OAAOhC,KAAK6B,IAAI,CAACI,WAAW;QAC3C,MAAMC,WAAWvB,QAAOX,sBAAAA,KAAK6B,IAAI,CAACK,QAAQ,YAAlBlC,sBAAsB,IAC3CqB,IAAI,GACJc,WAAW;QACdN,OAAO;YACLC;YACAG,aACED,OAAOI,QAAQ,CAACL,WAAWA,UAAU,IAAIM,KAAKC,KAAK,CAACP,UAAU;YAChEG,UAAUrC,iBAAiB0C,IAAI,CAACL,YAAYA,WAAW;YACvDM,SAASxC,KAAK6B,IAAI,CAACW,OAAO,GAAG7B,OAAOX,KAAK6B,IAAI,CAACW,OAAO,IAAIvB;QAC3D;IACF;IACA,MAAMwB,gBAAgBZ,OAAOlB,gBAAOX,aAAAA,KAAK6B,IAAI,qBAAT7B,WAAW0C,IAAI,mBAAI,IAAIrB,IAAI,KAAK;IAEpE,IAAI;YAsDkCsB;QArDpC,MAAMA,UAAU,MAAMxD,cAAcyD,GAAG,GAAGC,IAAI,GAAGC,aAAa,CAACjC;QAC/D,MAAMkC,QAAQJ,OAAO,CAAC,QAAQ,KAAK;QACnC;;;;;;;KAOC,GACD,MAAMK,aAAa,MAAM1D,sBAAsBqD,QAAQM,GAAG,EAAE;YAAE7B;QAAO;QACrE,IACE,CAAC2B,SACD,CAAEC,CAAAA,WAAWE,QAAQ,IAAK,MAAM3D,gBAAgByD,WAAWG,KAAK,EAAER,QAAQM,GAAG,CAAC,GAC9E;YACA3C,IACGP,MAAM,CAAC,KACPU,IAAI,CAAC;gBAAER,OAAO;YAA8D;YAC/E;QACF;QAEA,MAAMmD,WAAW,MAAMhE,cAAcgC;QACrC,IAAI,CAACgC,UAAU;YACb9C,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;gBAAER,OAAO;YAAe;YAC7C;QACF;QACA,MAAM,EAAEkD,KAAK,EAAEE,GAAG,EAAE,GAAGD;QACvB,0EAA0E;QAC1E,MAAME,QAAQ7D,gBAAgB4D,KAAK;QACnC,IAAIC,OAAO;YACThD,IAAIP,MAAM,CAACuD,MAAMvD,MAAM,EAAEU,IAAI,CAAC6C,MAAMtD,IAAI;YACxC;QACF;QACA,MAAMuD,YAAYpE,cAAcyD,GAAG,GAAGW,SAAS;QAC/C,uEAAuE;QACvE,IAAI1B,QAAQY,eAAe;YACzB,MAAMe,SAASvE,sBACb,mBACA,MAAMO,gBAAgB+D,WAAWJ,OAAO,oBACxCV;YAEF,IAAIe,OAAOC,EAAE,KAAK,OAAO;gBACvBnD,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;oBAAER,OAAOuD,OAAOvD,KAAK;oBAAEyD,OAAO;gBAAY;gBAC/D;YACF;YACA,IAAIF,OAAOG,KAAK,EAAE9B,KAAKa,IAAI,GAAGc,OAAOG,KAAK;QAC5C;QACA,MAAMC,SAAS,MAAMvE,gBAAgB;YACnCkE;YACAnC;YACA+B;YACAE,KAAKA;YACL/B;YACAuC,OAAO;gBAAEZ,KAAKN,QAAQM,GAAG;gBAAEa,KAAK,GAAEnB,iBAAAA,QAAQmB,KAAK,YAAbnB,iBAAiB;gBAAMoB,MAAM;YAAS;WACpE/D,KAAKgE,QAAQ,GAAG;YAAEA,UAAUrD,OAAOX,KAAKgE,QAAQ;QAAE,IAAI,CAAC,GACvDzC,qBAAqB;YAAEC,WAAWD;QAAmB,IAAI,CAAC;YAC9DE;YACAI;;QAEF,IAAI+B,OAAOH,EAAE,KAAK,OAAO;YACvB,MAAMQ,UAAUnE,QAAQ,CAAC8D,OAAO1D,MAAM,CAAC;YACvCI,IAAIP,MAAM,CAACkE,QAAQlE,MAAM,EAAEU,IAAI,CAACwD,QAAQjE,IAAI;YAC5C;QACF;QACA,MAAMkE,SAA8B;YAClCT,IAAI;YACJU,WAAWP,OAAOO,SAAS;WACvBP,OAAOpC,SAAS,GAAG;YAAEA,WAAWoC,OAAOpC,SAAS;QAAC,IAAI,CAAC,GACtDoC,OAAOQ,MAAM,GAAG;YAAEA,QAAQR,OAAOQ,MAAM;QAAC,IAAI,CAAC;YACjDC,kBAAkBT,OAAOS,gBAAgB;;QAE3C/D,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAACyD;IACvB,EAAE,OAAOjE,OAAO;QACdqE,QAAQrE,KAAK,CAAC,uBAAuBA;QACrCK,IAAIP,MAAM,CAAC,KAAKU,IAAI,CAAC;YAAER,OAAO;QAAmC;IACnE;AACF,EAAC;AAED,eAAeG,mBAAkB"}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* because a lead entered by hand is suite work: the capture doors that fill
|
|
46
46
|
* a Free workspace's leads do not come through here.
|
|
47
47
|
*/
|
|
48
|
-
import { type CrmCustomValue, type CrmLeadProfilePatch, type
|
|
48
|
+
import { type CrmCustomValue, type CrmLeadProfilePatch, type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
49
49
|
import type { CampaignFilingRef } from '../model/campaign-filing-activity';
|
|
50
50
|
/** The surface a hand-entered lead names, beside `signup`, `booking`, `form:{id}` and `import`. */
|
|
51
51
|
export declare const LEAD_MANUAL_SOURCE = "manual";
|
|
@@ -61,11 +61,26 @@ export interface LeadCreateRequest {
|
|
|
61
61
|
address?: Record<string, unknown> | null;
|
|
62
62
|
tags?: string[];
|
|
63
63
|
leadSource?: string;
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* One of the org's Lead status values by its label, or a meaning — New
|
|
66
|
+
* or Working either way (AGL-3512).
|
|
67
|
+
*/
|
|
68
|
+
status?: string;
|
|
65
69
|
ownerUid?: string;
|
|
66
70
|
notes?: string;
|
|
67
71
|
/** The org's campaigns to file the lead under (AGL-3254), by container id. */
|
|
68
72
|
campaignIds?: string[];
|
|
73
|
+
salutation?: string;
|
|
74
|
+
firstName?: string;
|
|
75
|
+
lastName?: string;
|
|
76
|
+
mobilePhone?: string;
|
|
77
|
+
fax?: string;
|
|
78
|
+
doNotCall?: boolean;
|
|
79
|
+
industry?: string;
|
|
80
|
+
rating?: string;
|
|
81
|
+
annualRevenueCents?: number;
|
|
82
|
+
currency?: string;
|
|
83
|
+
numberOfEmployees?: number;
|
|
69
84
|
/**
|
|
70
85
|
* The org's custom LEAD fields (AGL-3272), keyed by each definition's
|
|
71
86
|
* `key`. Judged against the definitions whose `object` is `lead`, so a
|