@aglyn/plugins-crm 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +57 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +29 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/activity-list.d.ts +98 -0
- package/src/lib/components/activity-list.js +321 -0
- package/src/lib/components/activity-list.js.map +1 -0
- package/src/lib/components/activity-queries.d.ts +117 -0
- package/src/lib/components/activity-queries.js +198 -0
- package/src/lib/components/activity-queries.js.map +1 -0
- package/src/lib/components/add-to-list-button.d.ts +25 -0
- package/src/lib/components/add-to-list-button.js +64 -0
- package/src/lib/components/add-to-list-button.js.map +1 -0
- package/src/lib/components/add-to-list-dialog.d.ts +24 -0
- package/src/lib/components/add-to-list-dialog.js +327 -0
- package/src/lib/components/add-to-list-dialog.js.map +1 -0
- package/src/lib/components/assignment-rule-drawer.d.ts +43 -0
- package/src/lib/components/assignment-rule-drawer.js +293 -0
- package/src/lib/components/assignment-rule-drawer.js.map +1 -0
- package/src/lib/components/companies-bulk-bar.d.ts +29 -0
- package/src/lib/components/companies-bulk-bar.js +274 -0
- package/src/lib/components/companies-bulk-bar.js.map +1 -0
- package/src/lib/components/companies-section.d.ts +54 -0
- package/src/lib/components/companies-section.js +522 -0
- package/src/lib/components/companies-section.js.map +1 -0
- package/src/lib/components/company-contacts-card.d.ts +49 -0
- package/src/lib/components/company-contacts-card.js +435 -0
- package/src/lib/components/company-contacts-card.js.map +1 -0
- package/src/lib/components/company-deals-card.d.ts +20 -0
- package/src/lib/components/company-deals-card.js +38 -0
- package/src/lib/components/company-deals-card.js.map +1 -0
- package/src/lib/components/company-detail-page.d.ts +20 -0
- package/src/lib/components/company-detail-page.js +165 -0
- package/src/lib/components/company-detail-page.js.map +1 -0
- package/src/lib/components/company-edit-drawer.d.ts +65 -0
- package/src/lib/components/company-edit-drawer.js +433 -0
- package/src/lib/components/company-edit-drawer.js.map +1 -0
- package/src/lib/components/company-import-drawer.d.ts +42 -0
- package/src/lib/components/company-import-drawer.js +99 -0
- package/src/lib/components/company-import-drawer.js.map +1 -0
- package/src/lib/components/company-picker.d.ts +127 -0
- package/src/lib/components/company-picker.js +323 -0
- package/src/lib/components/company-picker.js.map +1 -0
- package/src/lib/components/company-properties-card.d.ts +47 -0
- package/src/lib/components/company-properties-card.js +289 -0
- package/src/lib/components/company-properties-card.js.map +1 -0
- package/src/lib/components/contact-address-fields.d.ts +29 -0
- package/src/lib/components/contact-address-fields.js +149 -0
- package/src/lib/components/contact-address-fields.js.map +1 -0
- package/src/lib/components/contact-associations-card.d.ts +72 -0
- package/src/lib/components/contact-associations-card.js +296 -0
- package/src/lib/components/contact-associations-card.js.map +1 -0
- package/src/lib/components/contact-custom-columns.d.ts +38 -0
- package/src/lib/components/contact-custom-columns.js +102 -0
- package/src/lib/components/contact-custom-columns.js.map +1 -0
- package/src/lib/components/contact-custom-fields-card.d.ts +39 -0
- package/src/lib/components/contact-custom-fields-card.js +204 -0
- package/src/lib/components/contact-custom-fields-card.js.map +1 -0
- package/src/lib/components/contact-deals-card.d.ts +20 -0
- package/src/lib/components/contact-deals-card.js +38 -0
- package/src/lib/components/contact-deals-card.js.map +1 -0
- package/src/lib/components/contact-detail-page.d.ts +46 -0
- package/src/lib/components/contact-detail-page.js +535 -0
- package/src/lib/components/contact-detail-page.js.map +1 -0
- package/src/lib/components/contact-duplicates-card.d.ts +42 -0
- package/src/lib/components/contact-duplicates-card.js +167 -0
- package/src/lib/components/contact-duplicates-card.js.map +1 -0
- package/src/lib/components/contact-field-drawer.d.ts +64 -0
- package/src/lib/components/contact-field-drawer.js +251 -0
- package/src/lib/components/contact-field-drawer.js.map +1 -0
- package/src/lib/components/contact-import-drawer.d.ts +46 -0
- package/src/lib/components/contact-import-drawer.js +149 -0
- package/src/lib/components/contact-import-drawer.js.map +1 -0
- package/src/lib/components/contact-known-by-card.d.ts +39 -0
- package/src/lib/components/contact-known-by-card.js +142 -0
- package/src/lib/components/contact-known-by-card.js.map +1 -0
- package/src/lib/components/contact-list-columns.d.ts +53 -0
- package/src/lib/components/contact-list-columns.js +288 -0
- package/src/lib/components/contact-list-columns.js.map +1 -0
- package/src/lib/components/contact-merge-dialog.d.ts +79 -0
- package/src/lib/components/contact-merge-dialog.js +405 -0
- package/src/lib/components/contact-merge-dialog.js.map +1 -0
- package/src/lib/components/contact-properties-card.d.ts +89 -0
- package/src/lib/components/contact-properties-card.js +492 -0
- package/src/lib/components/contact-properties-card.js.map +1 -0
- package/src/lib/components/contact-timeline-card.d.ts +68 -0
- package/src/lib/components/contact-timeline-card.js +435 -0
- package/src/lib/components/contact-timeline-card.js.map +1 -0
- package/src/lib/components/contacts-bulk-bar.d.ts +91 -0
- package/src/lib/components/contacts-bulk-bar.js +493 -0
- package/src/lib/components/contacts-bulk-bar.js.map +1 -0
- package/src/lib/components/contacts-section.d.ts +26 -0
- package/src/lib/components/contacts-section.js +1025 -0
- package/src/lib/components/contacts-section.js.map +1 -0
- package/src/lib/components/copy-capture-address-button.d.ts +33 -0
- package/src/lib/components/copy-capture-address-button.js +60 -0
- package/src/lib/components/copy-capture-address-button.js.map +1 -0
- package/src/lib/components/crm-attribution-zone.d.ts +38 -0
- package/src/lib/components/crm-attribution-zone.js +36 -0
- package/src/lib/components/crm-attribution-zone.js.map +1 -0
- package/src/lib/components/crm-booking-zone.d.ts +61 -0
- package/src/lib/components/crm-booking-zone.js +50 -0
- package/src/lib/components/crm-booking-zone.js.map +1 -0
- package/src/lib/components/crm-bulk-bar-frame.d.ts +47 -0
- package/src/lib/components/crm-bulk-bar-frame.js +137 -0
- package/src/lib/components/crm-bulk-bar-frame.js.map +1 -0
- package/src/lib/components/crm-call-actions.d.ts +65 -0
- package/src/lib/components/crm-call-actions.js +110 -0
- package/src/lib/components/crm-call-actions.js.map +1 -0
- package/src/lib/components/crm-column-menu.d.ts +21 -0
- package/src/lib/components/crm-column-menu.js +103 -0
- package/src/lib/components/crm-column-menu.js.map +1 -0
- package/src/lib/components/crm-console-page.d.ts +19 -0
- package/src/lib/components/crm-console-page.js +151 -0
- package/src/lib/components/crm-console-page.js.map +1 -0
- package/src/lib/components/crm-console-sections.d.ts +52 -0
- package/src/lib/components/crm-console-sections.js +83 -0
- package/src/lib/components/crm-console-sections.js.map +1 -0
- package/src/lib/components/crm-custom-field-control.d.ts +26 -0
- package/src/lib/components/crm-custom-field-control.js +131 -0
- package/src/lib/components/crm-custom-field-control.js.map +1 -0
- package/src/lib/components/crm-email-template-tools.d.ts +69 -0
- package/src/lib/components/crm-email-template-tools.js +240 -0
- package/src/lib/components/crm-email-template-tools.js.map +1 -0
- package/src/lib/components/crm-export-all-button.d.ts +39 -0
- package/src/lib/components/crm-export-all-button.js +98 -0
- package/src/lib/components/crm-export-all-button.js.map +1 -0
- package/src/lib/components/crm-filter-bar.d.ts +53 -0
- package/src/lib/components/crm-filter-bar.js +294 -0
- package/src/lib/components/crm-filter-bar.js.map +1 -0
- package/src/lib/components/crm-glance-card.d.ts +54 -0
- package/src/lib/components/crm-glance-card.js +219 -0
- package/src/lib/components/crm-glance-card.js.map +1 -0
- package/src/lib/components/crm-next-activity-column.d.ts +35 -0
- package/src/lib/components/crm-next-activity-column.js +100 -0
- package/src/lib/components/crm-next-activity-column.js.map +1 -0
- package/src/lib/components/crm-record-header.d.ts +97 -0
- package/src/lib/components/crm-record-header.js +160 -0
- package/src/lib/components/crm-record-header.js.map +1 -0
- package/src/lib/components/crm-record-insights-zone.d.ts +50 -0
- package/src/lib/components/crm-record-insights-zone.js +169 -0
- package/src/lib/components/crm-record-insights-zone.js.map +1 -0
- package/src/lib/components/crm-record-picker.d.ts +42 -0
- package/src/lib/components/crm-record-picker.js +104 -0
- package/src/lib/components/crm-record-picker.js.map +1 -0
- package/src/lib/components/crm-send-email-button.d.ts +39 -0
- package/src/lib/components/crm-send-email-button.js +81 -0
- package/src/lib/components/crm-send-email-button.js.map +1 -0
- package/src/lib/components/crm-send-email-dialog.d.ts +61 -0
- package/src/lib/components/crm-send-email-dialog.js +851 -0
- package/src/lib/components/crm-send-email-dialog.js.map +1 -0
- package/src/lib/components/crm-site-picker.d.ts +61 -0
- package/src/lib/components/crm-site-picker.js +99 -0
- package/src/lib/components/crm-site-picker.js.map +1 -0
- package/src/lib/components/crm-suite-lock.d.ts +29 -0
- package/src/lib/components/crm-suite-lock.js +114 -0
- package/src/lib/components/crm-suite-lock.js.map +1 -0
- package/src/lib/components/crm-tasks-due-card.d.ts +42 -0
- package/src/lib/components/crm-tasks-due-card.js +225 -0
- package/src/lib/components/crm-tasks-due-card.js.map +1 -0
- package/src/lib/components/crm-views-control.d.ts +65 -0
- package/src/lib/components/crm-views-control.js +518 -0
- package/src/lib/components/crm-views-control.js.map +1 -0
- package/src/lib/components/csv-import-drawer.d.ts +132 -0
- package/src/lib/components/csv-import-drawer.js +746 -0
- package/src/lib/components/csv-import-drawer.js.map +1 -0
- package/src/lib/components/deal-board.d.ts +70 -0
- package/src/lib/components/deal-board.js +478 -0
- package/src/lib/components/deal-board.js.map +1 -0
- package/src/lib/components/deal-detail-page.d.ts +18 -0
- package/src/lib/components/deal-detail-page.js +323 -0
- package/src/lib/components/deal-detail-page.js.map +1 -0
- package/src/lib/components/deal-edit-drawer.d.ts +59 -0
- package/src/lib/components/deal-edit-drawer.js +640 -0
- package/src/lib/components/deal-edit-drawer.js.map +1 -0
- package/src/lib/components/deal-import-drawer.d.ts +24 -0
- package/src/lib/components/deal-import-drawer.js +83 -0
- package/src/lib/components/deal-import-drawer.js.map +1 -0
- package/src/lib/components/deal-products-card.d.ts +48 -0
- package/src/lib/components/deal-products-card.js +614 -0
- package/src/lib/components/deal-products-card.js.map +1 -0
- package/src/lib/components/deal-properties-card.d.ts +29 -0
- package/src/lib/components/deal-properties-card.js +144 -0
- package/src/lib/components/deal-properties-card.js.map +1 -0
- package/src/lib/components/deal-stage-card.d.ts +24 -0
- package/src/lib/components/deal-stage-card.js +199 -0
- package/src/lib/components/deal-stage-card.js.map +1 -0
- package/src/lib/components/deals-bulk-bar.d.ts +31 -0
- package/src/lib/components/deals-bulk-bar.js +340 -0
- package/src/lib/components/deals-bulk-bar.js.map +1 -0
- package/src/lib/components/deals-section.d.ts +49 -0
- package/src/lib/components/deals-section.js +767 -0
- package/src/lib/components/deals-section.js.map +1 -0
- package/src/lib/components/email-capture-card.d.ts +42 -0
- package/src/lib/components/email-capture-card.js +134 -0
- package/src/lib/components/email-capture-card.js.map +1 -0
- package/src/lib/components/email-template-drawer.d.ts +33 -0
- package/src/lib/components/email-template-drawer.js +209 -0
- package/src/lib/components/email-template-drawer.js.map +1 -0
- package/src/lib/components/email-templates-card.d.ts +33 -0
- package/src/lib/components/email-templates-card.js +370 -0
- package/src/lib/components/email-templates-card.js.map +1 -0
- package/src/lib/components/erase-person-action.d.ts +92 -0
- package/src/lib/components/erase-person-action.js +261 -0
- package/src/lib/components/erase-person-action.js.map +1 -0
- package/src/lib/components/fields-section.d.ts +37 -0
- package/src/lib/components/fields-section.js +641 -0
- package/src/lib/components/fields-section.js.map +1 -0
- package/src/lib/components/form-contact-fields-card.d.ts +46 -0
- package/src/lib/components/form-contact-fields-card.js +253 -0
- package/src/lib/components/form-contact-fields-card.js.map +1 -0
- package/src/lib/components/insert-link-at-caret.d.ts +28 -0
- package/src/lib/components/insert-link-at-caret.js +37 -0
- package/src/lib/components/insert-link-at-caret.js.map +1 -0
- package/src/lib/components/lead-convert-dialog.d.ts +32 -0
- package/src/lib/components/lead-convert-dialog.js +493 -0
- package/src/lib/components/lead-convert-dialog.js.map +1 -0
- package/src/lib/components/lead-detail-page.d.ts +16 -0
- package/src/lib/components/lead-detail-page.js +163 -0
- package/src/lib/components/lead-detail-page.js.map +1 -0
- package/src/lib/components/lead-history-card.d.ts +32 -0
- package/src/lib/components/lead-history-card.js +165 -0
- package/src/lib/components/lead-history-card.js.map +1 -0
- package/src/lib/components/lead-import-drawer.d.ts +24 -0
- package/src/lib/components/lead-import-drawer.js +95 -0
- package/src/lib/components/lead-import-drawer.js.map +1 -0
- package/src/lib/components/lead-owner-select.d.ts +35 -0
- package/src/lib/components/lead-owner-select.js +91 -0
- package/src/lib/components/lead-owner-select.js.map +1 -0
- package/src/lib/components/lead-properties-card.d.ts +61 -0
- package/src/lib/components/lead-properties-card.js +395 -0
- package/src/lib/components/lead-properties-card.js.map +1 -0
- package/src/lib/components/lead-status-chip.d.ts +12 -0
- package/src/lib/components/lead-status-chip.js +43 -0
- package/src/lib/components/lead-status-chip.js.map +1 -0
- package/src/lib/components/lead-surfaces-note.d.ts +126 -0
- package/src/lib/components/lead-surfaces-note.js +262 -0
- package/src/lib/components/lead-surfaces-note.js.map +1 -0
- package/src/lib/components/lead-unqualify-dialog.d.ts +24 -0
- package/src/lib/components/lead-unqualify-dialog.js +125 -0
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -0
- package/src/lib/components/leads-bulk-bar.d.ts +58 -0
- package/src/lib/components/leads-bulk-bar.js +307 -0
- package/src/lib/components/leads-bulk-bar.js.map +1 -0
- package/src/lib/components/leads-section.d.ts +23 -0
- package/src/lib/components/leads-section.js +664 -0
- package/src/lib/components/leads-section.js.map +1 -0
- package/src/lib/components/linked-deals-card.d.ts +32 -0
- package/src/lib/components/linked-deals-card.js +163 -0
- package/src/lib/components/linked-deals-card.js.map +1 -0
- package/src/lib/components/log-activity-dialog.d.ts +60 -0
- package/src/lib/components/log-activity-dialog.js +368 -0
- package/src/lib/components/log-activity-dialog.js.map +1 -0
- package/src/lib/components/lost-reason-dialog.d.ts +26 -0
- package/src/lib/components/lost-reason-dialog.js +95 -0
- package/src/lib/components/lost-reason-dialog.js.map +1 -0
- package/src/lib/components/new-contact-drawer.d.ts +78 -0
- package/src/lib/components/new-contact-drawer.js +343 -0
- package/src/lib/components/new-contact-drawer.js.map +1 -0
- package/src/lib/components/org-lead-surfaces-note.d.ts +57 -0
- package/src/lib/components/org-lead-surfaces-note.js +172 -0
- package/src/lib/components/org-lead-surfaces-note.js.map +1 -0
- package/src/lib/components/owner-avatar.d.ts +18 -0
- package/src/lib/components/owner-avatar.js +61 -0
- package/src/lib/components/owner-avatar.js.map +1 -0
- package/src/lib/components/pipeline-stages-dialog.d.ts +43 -0
- package/src/lib/components/pipeline-stages-dialog.js +339 -0
- package/src/lib/components/pipeline-stages-dialog.js.map +1 -0
- package/src/lib/components/pipelines-dialog.d.ts +55 -0
- package/src/lib/components/pipelines-dialog.js +517 -0
- package/src/lib/components/pipelines-dialog.js.map +1 -0
- package/src/lib/components/recent-activity-feed.d.ts +39 -0
- package/src/lib/components/recent-activity-feed.js +103 -0
- package/src/lib/components/recent-activity-feed.js.map +1 -0
- package/src/lib/components/recipes-card.d.ts +39 -0
- package/src/lib/components/recipes-card.js +485 -0
- package/src/lib/components/recipes-card.js.map +1 -0
- package/src/lib/components/record-activity-card.d.ts +26 -0
- package/src/lib/components/record-activity-card.js +121 -0
- package/src/lib/components/record-activity-card.js.map +1 -0
- package/src/lib/components/record-files-card.d.ts +67 -0
- package/src/lib/components/record-files-card.js +290 -0
- package/src/lib/components/record-files-card.js.map +1 -0
- package/src/lib/components/record-tasks-card.d.ts +32 -0
- package/src/lib/components/record-tasks-card.js +306 -0
- package/src/lib/components/record-tasks-card.js.map +1 -0
- package/src/lib/components/reports/activity-card.d.ts +57 -0
- package/src/lib/components/reports/activity-card.js +281 -0
- package/src/lib/components/reports/activity-card.js.map +1 -0
- package/src/lib/components/reports/closed-deals-card.d.ts +20 -0
- package/src/lib/components/reports/closed-deals-card.js +194 -0
- package/src/lib/components/reports/closed-deals-card.js.map +1 -0
- package/src/lib/components/reports/contacts-mix-card.d.ts +27 -0
- package/src/lib/components/reports/contacts-mix-card.js +161 -0
- package/src/lib/components/reports/contacts-mix-card.js.map +1 -0
- package/src/lib/components/reports/contacts-trend-card.d.ts +31 -0
- package/src/lib/components/reports/contacts-trend-card.js +144 -0
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -0
- package/src/lib/components/reports/forecast-card.d.ts +22 -0
- package/src/lib/components/reports/forecast-card.js +251 -0
- package/src/lib/components/reports/forecast-card.js.map +1 -0
- package/src/lib/components/reports/lead-counts.d.ts +32 -0
- package/src/lib/components/reports/lead-counts.js +44 -0
- package/src/lib/components/reports/lead-counts.js.map +1 -0
- package/src/lib/components/reports/lead-funnel-card.d.ts +38 -0
- package/src/lib/components/reports/lead-funnel-card.js +375 -0
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -0
- package/src/lib/components/reports/pipeline-card.d.ts +19 -0
- package/src/lib/components/reports/pipeline-card.js +338 -0
- package/src/lib/components/reports/pipeline-card.js.map +1 -0
- package/src/lib/components/reports/report-bar-chart.d.ts +39 -0
- package/src/lib/components/reports/report-bar-chart.js +133 -0
- package/src/lib/components/reports/report-bar-chart.js.map +1 -0
- package/src/lib/components/reports/report-breakdown.d.ts +32 -0
- package/src/lib/components/reports/report-breakdown.js +105 -0
- package/src/lib/components/reports/report-breakdown.js.map +1 -0
- package/src/lib/components/reports/report-export.d.ts +37 -0
- package/src/lib/components/reports/report-export.js +66 -0
- package/src/lib/components/reports/report-export.js.map +1 -0
- package/src/lib/components/reports/report-format.d.ts +36 -0
- package/src/lib/components/reports/report-format.js +43 -0
- package/src/lib/components/reports/report-format.js.map +1 -0
- package/src/lib/components/reports/report-scope.d.ts +81 -0
- package/src/lib/components/reports/report-scope.js +58 -0
- package/src/lib/components/reports/report-scope.js.map +1 -0
- package/src/lib/components/reports/report-stat-tile.d.ts +47 -0
- package/src/lib/components/reports/report-stat-tile.js +96 -0
- package/src/lib/components/reports/report-stat-tile.js.map +1 -0
- package/src/lib/components/reports/source-conversion-card.d.ts +28 -0
- package/src/lib/components/reports/source-conversion-card.js +205 -0
- package/src/lib/components/reports/source-conversion-card.js.map +1 -0
- package/src/lib/components/reports/tasks-card.d.ts +26 -0
- package/src/lib/components/reports/tasks-card.js +279 -0
- package/src/lib/components/reports/tasks-card.js.map +1 -0
- package/src/lib/components/reports/use-aggregate-read.d.ts +99 -0
- package/src/lib/components/reports/use-aggregate-read.js +195 -0
- package/src/lib/components/reports/use-aggregate-read.js.map +1 -0
- package/src/lib/components/reports/won-lost-by-owner-card.d.ts +30 -0
- package/src/lib/components/reports/won-lost-by-owner-card.js +168 -0
- package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -0
- package/src/lib/components/reports-section.d.ts +42 -0
- package/src/lib/components/reports-section.js +308 -0
- package/src/lib/components/reports-section.js.map +1 -0
- package/src/lib/components/sending-addresses-card.d.ts +49 -0
- package/src/lib/components/sending-addresses-card.js +262 -0
- package/src/lib/components/sending-addresses-card.js.map +1 -0
- package/src/lib/components/settings-section.d.ts +151 -0
- package/src/lib/components/settings-section.js +783 -0
- package/src/lib/components/settings-section.js.map +1 -0
- package/src/lib/components/task-cells.d.ts +51 -0
- package/src/lib/components/task-cells.js +139 -0
- package/src/lib/components/task-cells.js.map +1 -0
- package/src/lib/components/task-edit-drawer.d.ts +53 -0
- package/src/lib/components/task-edit-drawer.js +483 -0
- package/src/lib/components/task-edit-drawer.js.map +1 -0
- package/src/lib/components/task-import-drawer.d.ts +24 -0
- package/src/lib/components/task-import-drawer.js +82 -0
- package/src/lib/components/task-import-drawer.js.map +1 -0
- package/src/lib/components/task-snooze-menu.d.ts +65 -0
- package/src/lib/components/task-snooze-menu.js +207 -0
- package/src/lib/components/task-snooze-menu.js.map +1 -0
- package/src/lib/components/tasks-bulk-bar.d.ts +25 -0
- package/src/lib/components/tasks-bulk-bar.js +348 -0
- package/src/lib/components/tasks-bulk-bar.js.map +1 -0
- package/src/lib/components/tasks-calendar.d.ts +38 -0
- package/src/lib/components/tasks-calendar.js +228 -0
- package/src/lib/components/tasks-calendar.js.map +1 -0
- package/src/lib/components/tasks-section.d.ts +27 -0
- package/src/lib/components/tasks-section.js +579 -0
- package/src/lib/components/tasks-section.js.map +1 -0
- package/src/lib/components/use-contact-campaign-emails.d.ts +32 -0
- package/src/lib/components/use-contact-campaign-emails.js +83 -0
- package/src/lib/components/use-contact-campaign-emails.js.map +1 -0
- package/src/lib/components/use-crm-api.d.ts +44 -0
- package/src/lib/components/use-crm-api.js +84 -0
- package/src/lib/components/use-crm-api.js.map +1 -0
- package/src/lib/components/use-crm-inbound-address.d.ts +60 -0
- package/src/lib/components/use-crm-inbound-address.js +124 -0
- package/src/lib/components/use-crm-inbound-address.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +24 -0
- package/src/lib/components/use-emails-hub-path.js +59 -0
- package/src/lib/components/use-emails-hub-path.js.map +1 -0
- package/src/lib/components/use-member-email-aliases.d.ts +54 -0
- package/src/lib/components/use-member-email-aliases.js +186 -0
- package/src/lib/components/use-member-email-aliases.js.map +1 -0
- package/src/lib/components/use-org-members.d.ts +40 -0
- package/src/lib/components/use-org-members.js +100 -0
- package/src/lib/components/use-org-members.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +109 -0
- package/src/lib/constants/api-routes.js +74 -0
- package/src/lib/constants/api-routes.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +14 -0
- package/src/lib/constants/bundle-common.js +14 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/company-filters.d.ts +18 -0
- package/src/lib/constants/company-filters.js +51 -0
- package/src/lib/constants/company-filters.js.map +1 -0
- package/src/lib/constants/contact-filters.d.ts +38 -0
- package/src/lib/constants/contact-filters.js +294 -0
- package/src/lib/constants/contact-filters.js.map +1 -0
- package/src/lib/hooks/use-contact-field-definitions.d.ts +48 -0
- package/src/lib/hooks/use-contact-field-definitions.js +72 -0
- package/src/lib/hooks/use-contact-field-definitions.js.map +1 -0
- package/src/lib/hooks/use-contact-update.d.ts +25 -0
- package/src/lib/hooks/use-contact-update.js +80 -0
- package/src/lib/hooks/use-contact-update.js.map +1 -0
- package/src/lib/hooks/use-crm-activity-logger.d.ts +73 -0
- package/src/lib/hooks/use-crm-activity-logger.js +101 -0
- package/src/lib/hooks/use-crm-activity-logger.js.map +1 -0
- package/src/lib/hooks/use-crm-bulk-apply.d.ts +52 -0
- package/src/lib/hooks/use-crm-bulk-apply.js +112 -0
- package/src/lib/hooks/use-crm-bulk-apply.js.map +1 -0
- package/src/lib/hooks/use-crm-default-view.d.ts +24 -0
- package/src/lib/hooks/use-crm-default-view.js +64 -0
- package/src/lib/hooks/use-crm-default-view.js.map +1 -0
- package/src/lib/hooks/use-crm-email-templates.d.ts +59 -0
- package/src/lib/hooks/use-crm-email-templates.js +103 -0
- package/src/lib/hooks/use-crm-email-templates.js.map +1 -0
- package/src/lib/hooks/use-crm-hub-path.d.ts +29 -0
- package/src/lib/hooks/use-crm-hub-path.js +41 -0
- package/src/lib/hooks/use-crm-hub-path.js.map +1 -0
- package/src/lib/hooks/use-crm-org-mount.d.ts +97 -0
- package/src/lib/hooks/use-crm-org-mount.js +215 -0
- package/src/lib/hooks/use-crm-org-mount.js.map +1 -0
- package/src/lib/hooks/use-crm-record-names.d.ts +52 -0
- package/src/lib/hooks/use-crm-record-names.js +126 -0
- package/src/lib/hooks/use-crm-record-names.js.map +1 -0
- package/src/lib/hooks/use-crm-records-quota.d.ts +68 -0
- package/src/lib/hooks/use-crm-records-quota.js +118 -0
- package/src/lib/hooks/use-crm-records-quota.js.map +1 -0
- package/src/lib/hooks/use-crm-saved-view.d.ts +96 -0
- package/src/lib/hooks/use-crm-saved-view.js +329 -0
- package/src/lib/hooks/use-crm-saved-view.js.map +1 -0
- package/src/lib/hooks/use-crm-scope.d.ts +117 -0
- package/src/lib/hooks/use-crm-scope.js +147 -0
- package/src/lib/hooks/use-crm-scope.js.map +1 -0
- package/src/lib/hooks/use-crm-tasks.d.ts +85 -0
- package/src/lib/hooks/use-crm-tasks.js +153 -0
- package/src/lib/hooks/use-crm-tasks.js.map +1 -0
- package/src/lib/hooks/use-crm-view-grid.d.ts +55 -0
- package/src/lib/hooks/use-crm-view-grid.js +170 -0
- package/src/lib/hooks/use-crm-view-grid.js.map +1 -0
- package/src/lib/hooks/use-crm-views.d.ts +42 -0
- package/src/lib/hooks/use-crm-views.js +84 -0
- package/src/lib/hooks/use-crm-views.js.map +1 -0
- package/src/lib/hooks/use-deal-stage-api.d.ts +69 -0
- package/src/lib/hooks/use-deal-stage-api.js +78 -0
- package/src/lib/hooks/use-deal-stage-api.js.map +1 -0
- package/src/lib/hooks/use-deals.d.ts +47 -0
- package/src/lib/hooks/use-deals.js +105 -0
- package/src/lib/hooks/use-deals.js.map +1 -0
- package/src/lib/hooks/use-org-leads.d.ts +53 -0
- package/src/lib/hooks/use-org-leads.js +114 -0
- package/src/lib/hooks/use-org-leads.js.map +1 -0
- package/src/lib/hooks/use-org-member-directory.d.ts +36 -0
- package/src/lib/hooks/use-org-member-directory.js +116 -0
- package/src/lib/hooks/use-org-member-directory.js.map +1 -0
- package/src/lib/hooks/use-org-member-options.d.ts +38 -0
- package/src/lib/hooks/use-org-member-options.js +131 -0
- package/src/lib/hooks/use-org-member-options.js.map +1 -0
- package/src/lib/hooks/use-pipeline.d.ts +71 -0
- package/src/lib/hooks/use-pipeline.js +159 -0
- package/src/lib/hooks/use-pipeline.js.map +1 -0
- package/src/lib/model/companies-bulk-writes.d.ts +40 -0
- package/src/lib/model/companies-bulk-writes.js +103 -0
- package/src/lib/model/companies-bulk-writes.js.map +1 -0
- package/src/lib/model/companies-csv.d.ts +36 -0
- package/src/lib/model/companies-csv.js +38 -0
- package/src/lib/model/companies-csv.js.map +1 -0
- package/src/lib/model/companies.d.ts +99 -0
- package/src/lib/model/companies.js +168 -0
- package/src/lib/model/companies.js.map +1 -0
- package/src/lib/model/company-delete-route.d.ts +43 -0
- package/src/lib/model/company-delete-route.js +31 -0
- package/src/lib/model/company-delete-route.js.map +1 -0
- package/src/lib/model/company-delete.d.ts +42 -0
- package/src/lib/model/company-delete.js +52 -0
- package/src/lib/model/company-delete.js.map +1 -0
- package/src/lib/model/contact-duplicates.d.ts +58 -0
- package/src/lib/model/contact-duplicates.js +67 -0
- package/src/lib/model/contact-duplicates.js.map +1 -0
- package/src/lib/model/contact-record.d.ts +113 -0
- package/src/lib/model/contact-record.js +70 -0
- package/src/lib/model/contact-record.js.map +1 -0
- package/src/lib/model/contact-update.d.ts +93 -0
- package/src/lib/model/contact-update.js +50 -0
- package/src/lib/model/contact-update.js.map +1 -0
- package/src/lib/model/contacts-bulk-writes.d.ts +173 -0
- package/src/lib/model/contacts-bulk-writes.js +196 -0
- package/src/lib/model/contacts-bulk-writes.js.map +1 -0
- package/src/lib/model/contacts-csv.d.ts +54 -0
- package/src/lib/model/contacts-csv.js +67 -0
- package/src/lib/model/contacts-csv.js.map +1 -0
- package/src/lib/model/contacts-list-seed.d.ts +51 -0
- package/src/lib/model/contacts-list-seed.js +63 -0
- package/src/lib/model/contacts-list-seed.js.map +1 -0
- package/src/lib/model/crm-activity-report.d.ts +27 -0
- package/src/lib/model/crm-activity-report.js +56 -0
- package/src/lib/model/crm-activity-report.js.map +1 -0
- package/src/lib/model/crm-api.d.ts +49 -0
- package/src/lib/model/crm-api.js +47 -0
- package/src/lib/model/crm-api.js.map +1 -0
- package/src/lib/model/crm-bulk-writes.d.ts +147 -0
- package/src/lib/model/crm-bulk-writes.js +207 -0
- package/src/lib/model/crm-bulk-writes.js.map +1 -0
- package/src/lib/model/crm-company-import.d.ts +154 -0
- package/src/lib/model/crm-company-import.js +331 -0
- package/src/lib/model/crm-company-import.js.map +1 -0
- package/src/lib/model/crm-custom-draft.d.ts +62 -0
- package/src/lib/model/crm-custom-draft.js +67 -0
- package/src/lib/model/crm-custom-draft.js.map +1 -0
- package/src/lib/model/crm-deal-import.d.ts +112 -0
- package/src/lib/model/crm-deal-import.js +263 -0
- package/src/lib/model/crm-deal-import.js.map +1 -0
- package/src/lib/model/crm-import.d.ts +244 -0
- package/src/lib/model/crm-import.js +466 -0
- package/src/lib/model/crm-import.js.map +1 -0
- package/src/lib/model/crm-lead-import.d.ts +105 -0
- package/src/lib/model/crm-lead-import.js +255 -0
- package/src/lib/model/crm-lead-import.js.map +1 -0
- package/src/lib/model/crm-record-routes.d.ts +21 -0
- package/src/lib/model/crm-record-routes.js +70 -0
- package/src/lib/model/crm-record-routes.js.map +1 -0
- package/src/lib/model/crm-routes.d.ts +101 -0
- package/src/lib/model/crm-routes.js +92 -0
- package/src/lib/model/crm-routes.js.map +1 -0
- package/src/lib/model/crm-task-import.d.ts +94 -0
- package/src/lib/model/crm-task-import.js +275 -0
- package/src/lib/model/crm-task-import.js.map +1 -0
- package/src/lib/model/crm-view-param.d.ts +43 -0
- package/src/lib/model/crm-view-param.js +49 -0
- package/src/lib/model/crm-view-param.js.map +1 -0
- package/src/lib/model/deal-board-model.d.ts +241 -0
- package/src/lib/model/deal-board-model.js +439 -0
- package/src/lib/model/deal-board-model.js.map +1 -0
- package/src/lib/model/deal-form-model.d.ts +114 -0
- package/src/lib/model/deal-form-model.js +218 -0
- package/src/lib/model/deal-form-model.js.map +1 -0
- package/src/lib/model/deals-csv.d.ts +32 -0
- package/src/lib/model/deals-csv.js +37 -0
- package/src/lib/model/deals-csv.js.map +1 -0
- package/src/lib/model/import-column-shapes.d.ts +36 -0
- package/src/lib/model/import-column-shapes.js +76 -0
- package/src/lib/model/import-column-shapes.js.map +1 -0
- package/src/lib/model/lead-company-suggestion.d.ts +61 -0
- package/src/lib/model/lead-company-suggestion.js +65 -0
- package/src/lib/model/lead-company-suggestion.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +29 -0
- package/src/lib/model/lead-filters.js +39 -0
- package/src/lib/model/lead-filters.js.map +1 -0
- package/src/lib/model/lead-surfaces.d.ts +58 -0
- package/src/lib/model/lead-surfaces.js +67 -0
- package/src/lib/model/lead-surfaces.js.map +1 -0
- package/src/lib/model/leads-csv.d.ts +40 -0
- package/src/lib/model/leads-csv.js +40 -0
- package/src/lib/model/leads-csv.js.map +1 -0
- package/src/lib/model/next-activity-api.d.ts +31 -0
- package/src/lib/model/next-activity-api.js +57 -0
- package/src/lib/model/next-activity-api.js.map +1 -0
- package/src/lib/model/next-activity.d.ts +64 -0
- package/src/lib/model/next-activity.js +47 -0
- package/src/lib/model/next-activity.js.map +1 -0
- package/src/lib/model/person-key-web.d.ts +43 -0
- package/src/lib/model/person-key-web.js +52 -0
- package/src/lib/model/person-key-web.js.map +1 -0
- package/src/lib/model/record-facts.d.ts +282 -0
- package/src/lib/model/record-facts.js +434 -0
- package/src/lib/model/record-facts.js.map +1 -0
- package/src/lib/model/task-api.d.ts +28 -0
- package/src/lib/model/task-api.js +57 -0
- package/src/lib/model/task-api.js.map +1 -0
- package/src/lib/model/task-calendar.d.ts +108 -0
- package/src/lib/model/task-calendar.js +139 -0
- package/src/lib/model/task-calendar.js.map +1 -0
- package/src/lib/model/task-routes.d.ts +177 -0
- package/src/lib/model/task-routes.js +197 -0
- package/src/lib/model/task-routes.js.map +1 -0
- package/src/lib/model/task-scope.d.ts +45 -0
- package/src/lib/model/task-scope.js +51 -0
- package/src/lib/model/task-scope.js.map +1 -0
- package/src/lib/model/task-views.d.ts +149 -0
- package/src/lib/model/task-views.js +286 -0
- package/src/lib/model/task-views.js.map +1 -0
- package/src/lib/model/tasks-csv.d.ts +28 -0
- package/src/lib/model/tasks-csv.js +28 -0
- package/src/lib/model/tasks-csv.js.map +1 -0
- package/src/lib/plugin.d.ts +32 -0
- package/src/lib/plugin.js +208 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/companies-import.d.ts +57 -0
- package/src/lib/server/companies-import.js +242 -0
- package/src/lib/server/companies-import.js.map +1 -0
- package/src/lib/server/company-delete.d.ts +59 -0
- package/src/lib/server/company-delete.js +146 -0
- package/src/lib/server/company-delete.js.map +1 -0
- package/src/lib/server/contact-email-history.d.ts +82 -0
- package/src/lib/server/contact-email-history.js +264 -0
- package/src/lib/server/contact-email-history.js.map +1 -0
- package/src/lib/server/contact-profile.d.ts +41 -0
- package/src/lib/server/contact-profile.js +45 -0
- package/src/lib/server/contact-profile.js.map +1 -0
- package/src/lib/server/contact-update.d.ts +92 -0
- package/src/lib/server/contact-update.js +512 -0
- package/src/lib/server/contact-update.js.map +1 -0
- package/src/lib/server/contacts-import.d.ts +73 -0
- package/src/lib/server/contacts-import.js +263 -0
- package/src/lib/server/contacts-import.js.map +1 -0
- package/src/lib/server/contacts-merge.d.ts +86 -0
- package/src/lib/server/contacts-merge.js +206 -0
- package/src/lib/server/contacts-merge.js.map +1 -0
- package/src/lib/server/deals-import.d.ts +76 -0
- package/src/lib/server/deals-import.js +246 -0
- package/src/lib/server/deals-import.js.map +1 -0
- package/src/lib/server/email-send.d.ts +119 -0
- package/src/lib/server/email-send.js +667 -0
- package/src/lib/server/email-send.js.map +1 -0
- package/src/lib/server/email-template-duplicate.d.ts +19 -0
- package/src/lib/server/email-template-duplicate.js +206 -0
- package/src/lib/server/email-template-duplicate.js.map +1 -0
- package/src/lib/server/erase-person.d.ts +108 -0
- package/src/lib/server/erase-person.js +309 -0
- package/src/lib/server/erase-person.js.map +1 -0
- package/src/lib/server/import-context.d.ts +85 -0
- package/src/lib/server/import-context.js +181 -0
- package/src/lib/server/import-context.js.map +1 -0
- package/src/lib/server/inbound-address.d.ts +61 -0
- package/src/lib/server/inbound-address.js +188 -0
- package/src/lib/server/inbound-address.js.map +1 -0
- package/src/lib/server/lead-convert.d.ts +78 -0
- package/src/lib/server/lead-convert.js +250 -0
- package/src/lib/server/lead-convert.js.map +1 -0
- package/src/lib/server/leads-import.d.ts +84 -0
- package/src/lib/server/leads-import.js +261 -0
- package/src/lib/server/leads-import.js.map +1 -0
- package/src/lib/server/next-activity-routes.d.ts +33 -0
- package/src/lib/server/next-activity-routes.js +110 -0
- package/src/lib/server/next-activity-routes.js.map +1 -0
- package/src/lib/server/org-activity.d.ts +61 -0
- package/src/lib/server/org-activity.js +120 -0
- package/src/lib/server/org-activity.js.map +1 -0
- package/src/lib/server/org-caller.d.ts +110 -0
- package/src/lib/server/org-caller.js +154 -0
- package/src/lib/server/org-caller.js.map +1 -0
- package/src/lib/server/recipe-routes.d.ts +75 -0
- package/src/lib/server/recipe-routes.js +442 -0
- package/src/lib/server/recipe-routes.js.map +1 -0
- package/src/lib/server/record-facts.d.ts +65 -0
- package/src/lib/server/record-facts.js +337 -0
- package/src/lib/server/record-facts.js.map +1 -0
- package/src/lib/server/record-timeline.d.ts +51 -0
- package/src/lib/server/record-timeline.js +215 -0
- package/src/lib/server/record-timeline.js.map +1 -0
- package/src/lib/server/suite-gate.d.ts +42 -0
- package/src/lib/server/suite-gate.js +87 -0
- package/src/lib/server/suite-gate.js.map +1 -0
- package/src/lib/server/task-routes.d.ts +132 -0
- package/src/lib/server/task-routes.js +699 -0
- package/src/lib/server/task-routes.js.map +1 -0
- package/src/lib/server/tasks-import.d.ts +54 -0
- package/src/lib/server/tasks-import.js +157 -0
- package/src/lib/server/tasks-import.js.map +1 -0
- package/src/lib/server-deal-stage.d.ts +88 -0
- package/src/lib/server-deal-stage.js +316 -0
- package/src/lib/server-deal-stage.js.map +1 -0
- package/src/lib/server.d.ts +149 -0
- package/src/lib/server.js +615 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/contacts-merge.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/contacts-merge` — two records for one person become one\n * (AGL-2625).\n *\n * Body: `{ hostId, survivorId, mergedId }`. The survivor keeps its address\n * as the identity; the merged record's address becomes an alternate on it,\n * every deal, task, activity and lead naming the merged record is repointed,\n * and the merged document is deleted. `mergeContacts` in the data library\n * is the whole of the work, so the REST door performs the same merge.\n *\n * ## Who may call it\n *\n * An ORG-WIDE member holding `data.manage`. Narrower than the other CRM\n * routes, which admit a site-scoped member for the site they reach, and\n * deliberately: a merge is org-scoped by nature. Contacts are org documents\n * shared by every site that captured the person, and folding two of them\n * together touches every holder's facet, every site's consent entry and\n * every deal in the org that named the merged record — a decision about\n * records a site-scoped member cannot read, made on their behalf. The\n * console offers the action to org-wide members only, and this is the door\n * that makes that a rule rather than a hint.\n *\n * ## Why a server route\n *\n * A browser could write the survivor and delete the merged record under the\n * rules, and could not do the rest: a scoped `array-contains-any` listener\n * cannot repoint deals it may not list, the address index is closed to\n * clients, and the transaction over both documents is what keeps two merges\n * of one pair from both succeeding.\n *\n * ## The organization variant (AGL-2634)\n *\n * `{ orgId, hostId?, survivorId, mergedId }` from the org-level hub. The\n * same org-wide caller, authorized by the org directly rather than through\n * a site it names — so a record no site captured can be merged there — and\n * the act logged in the org's feed. A site beside the org is the record's\n * own, and is where the data library files the timeline note and the\n * site's feed line when there is one.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n getOrgForHost,\n logOrgActivity,\n mergeContacts,\n} from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { authorizeOrgCaller, holdsDataManage, readCrmRouteScope } from './org-caller'\nimport { crmSuiteRefusal } from './suite-gate'\n\nexport const CONTACTS_MERGE_ROUTE = 'crm/contacts-merge'\n\n/** The body the console's merge dialog posts. */\nexport interface ContactsMergeRequest {\n /** The site the merge runs as; at the organization level the record's own, or absent. */\n hostId?: string\n /** The organization, at the organization level (AGL-2634). */\n orgId?: string\n /** `orgs/{orgId}/contacts/{id}` — the record that stays. */\n survivorId: string\n /** The record folded into it and deleted. */\n mergedId: string\n}\n\n/** What the route answers on success. */\nexport interface ContactsMergeResponse {\n ok: true\n survivorId: string\n survivorEmail: string\n mergedId: string\n mergedEmail: string\n /** Every address the survivor answers to now, primary first. */\n emails: string[]\n repointed: { deals: number; tasks: number; activities: number; leads: number }\n}\n\nfunction typed(value: unknown, max: number): string {\n return String(value ?? '')\n .trim()\n .slice(0, max)\n}\n\nexport const contactsMergeHandler: 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: Partial<ContactsMergeRequest> =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const routeScope = readCrmRouteScope(body as Record<string, unknown>)\n const survivorId = typed(body.survivorId, 200)\n const mergedId = typed(body.mergedId, 200)\n if (!routeScope || !survivorId || !mergedId) {\n res.status(400).json({ error: 'Missing hostId, survivorId or mergedId' })\n return\n }\n if (survivorId === mergedId) {\n res.status(400).json({ error: 'Pick two different contacts to merge' })\n return\n }\n const { hostId } = routeScope\n const refusal =\n 'Merging contacts requires the data permission across the whole workspace'\n\n try {\n let orgId: string\n let org: unknown\n let actor: { uid: string; email: string | null }\n let actorName: string | null\n let orgActor: typeof actor | null = null\n if (routeScope.level === 'org') {\n const caller = await authorizeOrgCaller(req, routeScope.orgId, {\n needs: 'data.manage',\n refusal,\n })\n if (caller.ok === false) {\n res.status(caller.status).json({ error: caller.error })\n return\n }\n orgId = caller.orgId\n org = caller.org\n actor = { uid: caller.uid, email: caller.email }\n actorName = caller.name || null\n orgActor = actor\n } else {\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 decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const staff = decoded['staff'] === true\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (\n !staff &&\n !(membership.orgWide && (await holdsDataManage(membership.orgId, decoded.uid)))\n ) {\n res.status(403).json({ error: refusal })\n return\n }\n const resolved = await getOrgForHost(hostId)\n if (!resolved) {\n res.status(404).json({ error: 'Unknown site' })\n return\n }\n orgId = resolved.orgId\n org = resolved.org\n actor = { uid: decoded.uid, email: decoded.email ?? null }\n actorName = typeof decoded['name'] === 'string' ? decoded['name'] : null\n }\n // Merging is the CRM's at either level (AGL-2787).\n const suite = crmSuiteRefusal(org, 'Merging two contacts')\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n const firestore = firebaseAdmin.app().firestore()\n const result = await mergeContacts({\n firestore,\n orgRef: firestore.collection('orgs').doc(orgId),\n survivorId,\n mergedId,\n actor,\n hostId: hostId || null,\n actorName,\n })\n if (result.ok === false) {\n if (result.reason === 'same-record') {\n res.status(400).json({ error: 'Pick two different contacts to merge' })\n return\n }\n res.status(404).json({\n error:\n result.reason === 'survivor-missing'\n ? 'The contact to keep could not be found'\n : 'The contact to merge could not be found — it may already have been merged',\n })\n return\n }\n // The org-level act, in the org's feed: the data library wrote the\n // site's line when a site was named, and the org's is this door's.\n if (orgActor) {\n await logOrgActivity(orgId, orgActor, `Merged with ${result.mergedEmail}`, {\n type: 'contact',\n id: result.survivorId,\n name: result.survivorEmail,\n })\n }\n const answer: ContactsMergeResponse = {\n ok: true,\n survivorId: result.survivorId,\n survivorEmail: result.survivorEmail,\n mergedId: result.mergedId,\n mergedEmail: result.mergedEmail,\n emails: result.emails,\n repointed: result.repointed,\n }\n res.status(200).json(answer)\n } catch (error) {\n console.error('[crm] contacts-merge failed', routeScope, survivorId, mergedId, error)\n res.status(500).json({ error: 'The contacts could not be merged.' })\n }\n}\n\nexport default contactsMergeHandler\n"],"names":["firebaseAdmin","getOrgForHost","logOrgActivity","mergeContacts","resolveOrgPermissions","authorizeOrgCaller","holdsDataManage","readCrmRouteScope","crmSuiteRefusal","CONTACTS_MERGE_ROUTE","typed","value","max","String","trim","slice","contactsMergeHandler","req","res","method","setHeader","status","json","error","body","JSON","parse","routeScope","survivorId","mergedId","hostId","refusal","orgId","org","actor","actorName","orgActor","level","caller","needs","ok","uid","email","name","decoded","authorization","headers","idToken","startsWith","length","undefined","app","auth","verifyIdToken","staff","membership","orgWide","resolved","suite","firestore","result","orgRef","collection","doc","reason","mergedEmail","type","id","survivorEmail","answer","emails","repointed","console"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
* `POST /api/crm/deals-import` — one chunk of a deals file, written
|
|
19
|
+
* (AGL-2662).
|
|
20
|
+
*
|
|
21
|
+
* The browser has already read the file and applied the operator's column
|
|
22
|
+
* mapping; what arrives here is up to {@link DEAL_IMPORT_CHUNK_SIZE} raw
|
|
23
|
+
* rows in the vocabulary `crm-deal-import.ts` defines. This route judges
|
|
24
|
+
* each one through the same normalizer, resolves the two names a deal is
|
|
25
|
+
* filed under, and writes it with the stamp every CRM creator writes.
|
|
26
|
+
*
|
|
27
|
+
* ## The pipeline and the stage are looked up ONCE per request
|
|
28
|
+
*
|
|
29
|
+
* The org's pipelines are read once — the active ones the caller's scope
|
|
30
|
+
* can see — and every row resolves against that list by name. A row
|
|
31
|
+
* naming no pipeline lands in the default one (or the only one); a row
|
|
32
|
+
* naming no stage lands in its pipeline's first open stage. A name that
|
|
33
|
+
* matches nothing refuses the row by name, before any write, so the
|
|
34
|
+
* operator's skipped file says exactly which cell to fix.
|
|
35
|
+
*
|
|
36
|
+
* ## A deal's status is its stage's kind
|
|
37
|
+
*
|
|
38
|
+
* Landing in a `won` or `lost` stage closes the deal on creation, stamped
|
|
39
|
+
* with the same clock as `stageChangedAtMs`, the way the REST create does.
|
|
40
|
+
* No `dealWon` event fires for an imported win: the deal was won before it
|
|
41
|
+
* was filed here, and an automation on the event is for what happens next.
|
|
42
|
+
*
|
|
43
|
+
* ## The band is counted once per request
|
|
44
|
+
*
|
|
45
|
+
* A deal is a CRM record and meets the records band (AGL-2611), judged
|
|
46
|
+
* the way the companies import judges it: one aggregate at the first
|
|
47
|
+
* create, re-judged locally as the request creates more.
|
|
48
|
+
*/
|
|
49
|
+
import { type CrmDealStage, type CrmPipeline, type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
50
|
+
import { type DealImportRow } from '../model/crm-deal-import';
|
|
51
|
+
/** One pipeline the request can file into, with its stages by name. */
|
|
52
|
+
interface PipelineChoice {
|
|
53
|
+
id: string;
|
|
54
|
+
pipeline: CrmPipeline;
|
|
55
|
+
stages: CrmDealStage[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Where one row lands: its pipeline and its stage, or which of the two
|
|
59
|
+
* names the org does not have.
|
|
60
|
+
*/
|
|
61
|
+
export declare function placeDealImportRow(choices: readonly PipelineChoice[], row: Pick<DealImportRow, 'pipeline' | 'stage'>): {
|
|
62
|
+
ok: true;
|
|
63
|
+
pipeline: PipelineChoice;
|
|
64
|
+
stage: CrmDealStage;
|
|
65
|
+
} | {
|
|
66
|
+
ok: false;
|
|
67
|
+
reason: 'unknown-pipeline' | 'unknown-stage';
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `POST crm/deals-import` — `{ hostId, rows }` → a {@link DealImportChunkResult}.
|
|
71
|
+
*
|
|
72
|
+
* Rows are written one after another so the band's local count stays
|
|
73
|
+
* honest across the request.
|
|
74
|
+
*/
|
|
75
|
+
export declare const crmDealsImportHandler: PluginApiHandler;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,246 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* `POST /api/crm/deals-import` — one chunk of a deals file, written
|
|
19
|
+
* (AGL-2662).
|
|
20
|
+
*
|
|
21
|
+
* The browser has already read the file and applied the operator's column
|
|
22
|
+
* mapping; what arrives here is up to {@link DEAL_IMPORT_CHUNK_SIZE} raw
|
|
23
|
+
* rows in the vocabulary `crm-deal-import.ts` defines. This route judges
|
|
24
|
+
* each one through the same normalizer, resolves the two names a deal is
|
|
25
|
+
* filed under, and writes it with the stamp every CRM creator writes.
|
|
26
|
+
*
|
|
27
|
+
* ## The pipeline and the stage are looked up ONCE per request
|
|
28
|
+
*
|
|
29
|
+
* The org's pipelines are read once — the active ones the caller's scope
|
|
30
|
+
* can see — and every row resolves against that list by name. A row
|
|
31
|
+
* naming no pipeline lands in the default one (or the only one); a row
|
|
32
|
+
* naming no stage lands in its pipeline's first open stage. A name that
|
|
33
|
+
* matches nothing refuses the row by name, before any write, so the
|
|
34
|
+
* operator's skipped file says exactly which cell to fix.
|
|
35
|
+
*
|
|
36
|
+
* ## A deal's status is its stage's kind
|
|
37
|
+
*
|
|
38
|
+
* Landing in a `won` or `lost` stage closes the deal on creation, stamped
|
|
39
|
+
* with the same clock as `stageChangedAtMs`, the way the REST create does.
|
|
40
|
+
* No `dealWon` event fires for an imported win: the deal was won before it
|
|
41
|
+
* was filed here, and an automation on the event is for what happens next.
|
|
42
|
+
*
|
|
43
|
+
* ## The band is counted once per request
|
|
44
|
+
*
|
|
45
|
+
* A deal is a CRM record and meets the records band (AGL-2611), judged
|
|
46
|
+
* the way the companies import judges it: one aggregate at the first
|
|
47
|
+
* create, re-judged locally as the request creates more.
|
|
48
|
+
*/ import { checkCrmRecordsQuota, CRM_COLLECTIONS, isPipelineArchived, nameSearchKey, visibleToTokens } from "@aglyn/aglyn/server";
|
|
49
|
+
import { DEAL_IMPORT_CHUNK_SIZE, DEAL_IMPORT_MAX_BODY_BYTES, dealImportNameKey, normalizeDealImportRow } from "../model/crm-deal-import.js";
|
|
50
|
+
import { crmRecordsQuotaForOrg, firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
51
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
52
|
+
import { ownerDirectory, readImportRows, resolveImportContext } from "./import-context.js";
|
|
53
|
+
/** The default currency a deal carries when the file names none. */ const DEFAULT_CURRENCY = 'usd';
|
|
54
|
+
/** The active pipelines the caller's scope can see, in read order. */ async function readPipelines(orgRef, readTokens) {
|
|
55
|
+
const snapshot = await orgRef.collection(CRM_COLLECTIONS.pipelines).get();
|
|
56
|
+
const choices = [];
|
|
57
|
+
for (const doc of snapshot.docs){
|
|
58
|
+
var _pipeline_stages;
|
|
59
|
+
const pipeline = doc.data();
|
|
60
|
+
if (isPipelineArchived(pipeline)) continue;
|
|
61
|
+
if (!visibleToTokens(pipeline.visibleTo, readTokens)) continue;
|
|
62
|
+
const stages = [
|
|
63
|
+
...(_pipeline_stages = pipeline.stages) != null ? _pipeline_stages : []
|
|
64
|
+
].sort((a, b)=>a.order - b.order);
|
|
65
|
+
choices.push({
|
|
66
|
+
id: doc.id,
|
|
67
|
+
pipeline,
|
|
68
|
+
stages
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return choices;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Where one row lands: its pipeline and its stage, or which of the two
|
|
75
|
+
* names the org does not have.
|
|
76
|
+
*/ export function placeDealImportRow(choices, row) {
|
|
77
|
+
let pipeline;
|
|
78
|
+
if (row.pipeline) {
|
|
79
|
+
const wanted = dealImportNameKey(row.pipeline);
|
|
80
|
+
pipeline = choices.find((choice)=>{
|
|
81
|
+
var _choice_pipeline_name;
|
|
82
|
+
return dealImportNameKey((_choice_pipeline_name = choice.pipeline.name) != null ? _choice_pipeline_name : '') === wanted;
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
var _choices_find;
|
|
86
|
+
pipeline = (_choices_find = choices.find((choice)=>choice.pipeline.isDefault)) != null ? _choices_find : choices.length === 1 ? choices[0] : undefined;
|
|
87
|
+
}
|
|
88
|
+
if (!pipeline) return {
|
|
89
|
+
ok: false,
|
|
90
|
+
reason: 'unknown-pipeline'
|
|
91
|
+
};
|
|
92
|
+
let stage;
|
|
93
|
+
if (row.stage) {
|
|
94
|
+
const wanted = dealImportNameKey(row.stage);
|
|
95
|
+
stage = pipeline.stages.find((candidate)=>{
|
|
96
|
+
var _candidate_name;
|
|
97
|
+
return dealImportNameKey((_candidate_name = candidate.name) != null ? _candidate_name : '') === wanted;
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
var _pipeline_stages_find;
|
|
101
|
+
stage = (_pipeline_stages_find = pipeline.stages.find((candidate)=>candidate.kind === 'open')) != null ? _pipeline_stages_find : pipeline.stages[0];
|
|
102
|
+
}
|
|
103
|
+
if (!stage) return {
|
|
104
|
+
ok: false,
|
|
105
|
+
reason: 'unknown-stage'
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
pipeline,
|
|
110
|
+
stage
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `POST crm/deals-import` — `{ hostId, rows }` → a {@link DealImportChunkResult}.
|
|
115
|
+
*
|
|
116
|
+
* Rows are written one after another so the band's local count stays
|
|
117
|
+
* honest across the request.
|
|
118
|
+
*/ export const crmDealsImportHandler = async (req, res)=>{
|
|
119
|
+
if (req.method !== 'POST') {
|
|
120
|
+
res.setHeader('Allow', 'POST');
|
|
121
|
+
return res.status(405).json({
|
|
122
|
+
error: 'Method not allowed'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const read = readImportRows(req, {
|
|
126
|
+
maxBodyBytes: DEAL_IMPORT_MAX_BODY_BYTES,
|
|
127
|
+
chunkSize: DEAL_IMPORT_CHUNK_SIZE
|
|
128
|
+
});
|
|
129
|
+
if ('error' in read) return res.status(read.status).json({
|
|
130
|
+
error: read.error
|
|
131
|
+
});
|
|
132
|
+
try {
|
|
133
|
+
const context = await resolveImportContext(req);
|
|
134
|
+
if (context.ok === false) return res.status(context.status).json(context.body);
|
|
135
|
+
const skipped = [];
|
|
136
|
+
const dropped = {};
|
|
137
|
+
const normalized = [];
|
|
138
|
+
read.rows.forEach((raw, index)=>{
|
|
139
|
+
const verdict = normalizeDealImportRow(raw);
|
|
140
|
+
if (verdict.ok === false) {
|
|
141
|
+
skipped.push({
|
|
142
|
+
index,
|
|
143
|
+
title: verdict.input,
|
|
144
|
+
reason: verdict.reason
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const entry of verdict.row.dropped){
|
|
149
|
+
var _dropped_entry_field;
|
|
150
|
+
dropped[entry.field] = ((_dropped_entry_field = dropped[entry.field]) != null ? _dropped_entry_field : 0) + 1;
|
|
151
|
+
}
|
|
152
|
+
normalized.push({
|
|
153
|
+
index,
|
|
154
|
+
row: verdict.row
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(context.orgId);
|
|
158
|
+
const [owners, pipelines] = await Promise.all([
|
|
159
|
+
ownerDirectory(context.orgId, normalized.map((entry)=>entry.row)),
|
|
160
|
+
normalized.length ? readPipelines(orgRef, context.readTokens) : Promise.resolve([])
|
|
161
|
+
]);
|
|
162
|
+
const ownersUnresolved = new Set();
|
|
163
|
+
const deals = orgRef.collection(CRM_COLLECTIONS.deals);
|
|
164
|
+
const stamp = creationStamp(context);
|
|
165
|
+
let counted = null;
|
|
166
|
+
let createdHere = 0;
|
|
167
|
+
let created = 0;
|
|
168
|
+
for (const { index, row } of normalized){
|
|
169
|
+
var _row_currency;
|
|
170
|
+
const placed = placeDealImportRow(pipelines, row);
|
|
171
|
+
if (placed.ok === false) {
|
|
172
|
+
skipped.push({
|
|
173
|
+
index,
|
|
174
|
+
title: row.title,
|
|
175
|
+
reason: placed.reason
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
let ownerUid;
|
|
180
|
+
if (row.ownerEmail) {
|
|
181
|
+
ownerUid = owners.get(row.ownerEmail);
|
|
182
|
+
if (!ownerUid) ownersUnresolved.add(row.ownerEmail);
|
|
183
|
+
}
|
|
184
|
+
if (!counted) counted = await crmRecordsQuotaForOrg(context.org, orgRef);
|
|
185
|
+
const room = checkCrmRecordsQuota(context.org, counted.crmRecordsCount + createdHere);
|
|
186
|
+
if (!room.allowed) {
|
|
187
|
+
skipped.push({
|
|
188
|
+
index,
|
|
189
|
+
title: row.title,
|
|
190
|
+
reason: 'records-band'
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const nowMs = Date.now();
|
|
195
|
+
const { stage } = placed;
|
|
196
|
+
const status = stage.kind === 'won' ? 'won' : stage.kind === 'lost' ? 'lost' : 'open';
|
|
197
|
+
await deals.add(_extends({
|
|
198
|
+
title: row.title,
|
|
199
|
+
titleLower: nameSearchKey(row.title),
|
|
200
|
+
pipelineId: placed.pipeline.id,
|
|
201
|
+
stageId: stage.id,
|
|
202
|
+
status,
|
|
203
|
+
stageChangedAtMs: nowMs,
|
|
204
|
+
closedAtMs: status === 'open' ? null : nowMs,
|
|
205
|
+
currency: (_row_currency = row.currency) != null ? _row_currency : DEFAULT_CURRENCY
|
|
206
|
+
}, typeof row.amountCents === 'number' ? {
|
|
207
|
+
amountCents: row.amountCents
|
|
208
|
+
} : {}, ownerUid ? {
|
|
209
|
+
ownerUid
|
|
210
|
+
} : {}, typeof row.expectedCloseAtMs === 'number' ? {
|
|
211
|
+
expectedCloseAtMs: row.expectedCloseAtMs
|
|
212
|
+
} : {}, row.notes ? {
|
|
213
|
+
notes: row.notes
|
|
214
|
+
} : {}, stamp));
|
|
215
|
+
createdHere += 1;
|
|
216
|
+
created += 1;
|
|
217
|
+
}
|
|
218
|
+
const result = {
|
|
219
|
+
received: read.rows.length,
|
|
220
|
+
created,
|
|
221
|
+
merged: 0,
|
|
222
|
+
skipped: skipped.sort((a, b)=>a.index - b.index),
|
|
223
|
+
dropped,
|
|
224
|
+
ownersUnresolved: [
|
|
225
|
+
...ownersUnresolved
|
|
226
|
+
]
|
|
227
|
+
};
|
|
228
|
+
return res.status(200).json(result);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('crm/deals-import failed', error);
|
|
231
|
+
return res.status(500).json({
|
|
232
|
+
error: 'The import could not continue.'
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
/** The scope, the site, the importer and the clocks every created deal carries. */ function creationStamp(context) {
|
|
237
|
+
return {
|
|
238
|
+
hostId: context.hostId,
|
|
239
|
+
visibleTo: context.scopeTokens,
|
|
240
|
+
createdByUid: context.uid,
|
|
241
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
242
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
//# sourceMappingURL=deals-import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/deals-import.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/deals-import` — one chunk of a deals file, written\n * (AGL-2662).\n *\n * The browser has already read the file and applied the operator's column\n * mapping; what arrives here is up to {@link DEAL_IMPORT_CHUNK_SIZE} raw\n * rows in the vocabulary `crm-deal-import.ts` defines. This route judges\n * each one through the same normalizer, resolves the two names a deal is\n * filed under, and writes it with the stamp every CRM creator writes.\n *\n * ## The pipeline and the stage are looked up ONCE per request\n *\n * The org's pipelines are read once — the active ones the caller's scope\n * can see — and every row resolves against that list by name. A row\n * naming no pipeline lands in the default one (or the only one); a row\n * naming no stage lands in its pipeline's first open stage. A name that\n * matches nothing refuses the row by name, before any write, so the\n * operator's skipped file says exactly which cell to fix.\n *\n * ## A deal's status is its stage's kind\n *\n * Landing in a `won` or `lost` stage closes the deal on creation, stamped\n * with the same clock as `stageChangedAtMs`, the way the REST create does.\n * No `dealWon` event fires for an imported win: the deal was won before it\n * was filed here, and an automation on the event is for what happens next.\n *\n * ## The band is counted once per request\n *\n * A deal is a CRM record and meets the records band (AGL-2611), judged\n * the way the companies import judges it: one aggregate at the first\n * create, re-judged locally as the request creates more.\n */\n\nimport {\n checkCrmRecordsQuota,\n CRM_COLLECTIONS,\n type CrmDealStage,\n type CrmPipeline,\n isPipelineArchived,\n nameSearchKey,\n type PluginApiHandler,\n visibleToTokens,\n} from '@aglyn/aglyn/server'\nimport {\n DEAL_IMPORT_CHUNK_SIZE,\n DEAL_IMPORT_MAX_BODY_BYTES,\n type DealImportChunkResult,\n type DealImportRawRow,\n type DealImportRow,\n type DealImportSkippedRow,\n dealImportNameKey,\n normalizeDealImportRow,\n} from '../model/crm-deal-import'\nimport { crmRecordsQuotaForOrg, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n type ImportContext,\n ownerDirectory,\n readImportRows,\n resolveImportContext,\n} from './import-context'\n\n/** The default currency a deal carries when the file names none. */\nconst DEFAULT_CURRENCY = 'usd'\n\n/** One pipeline the request can file into, with its stages by name. */\ninterface PipelineChoice {\n id: string\n pipeline: CrmPipeline\n stages: CrmDealStage[]\n}\n\n/** The active pipelines the caller's scope can see, in read order. */\nasync function readPipelines(\n orgRef: FirebaseFirestore.DocumentReference,\n readTokens: readonly string[],\n): Promise<PipelineChoice[]> {\n const snapshot = await orgRef.collection(CRM_COLLECTIONS.pipelines).get()\n const choices: PipelineChoice[] = []\n for (const doc of snapshot.docs) {\n const pipeline = doc.data() as CrmPipeline\n if (isPipelineArchived(pipeline)) continue\n if (!visibleToTokens(pipeline.visibleTo, readTokens)) continue\n const stages = [...(pipeline.stages ?? [])].sort((a, b) => a.order - b.order)\n choices.push({ id: doc.id, pipeline, stages })\n }\n return choices\n}\n\n/**\n * Where one row lands: its pipeline and its stage, or which of the two\n * names the org does not have.\n */\nexport function placeDealImportRow(\n choices: readonly PipelineChoice[],\n row: Pick<DealImportRow, 'pipeline' | 'stage'>,\n):\n | { ok: true; pipeline: PipelineChoice; stage: CrmDealStage }\n | { ok: false; reason: 'unknown-pipeline' | 'unknown-stage' } {\n let pipeline: PipelineChoice | undefined\n if (row.pipeline) {\n const wanted = dealImportNameKey(row.pipeline)\n pipeline = choices.find((choice) => dealImportNameKey(choice.pipeline.name ?? '') === wanted)\n } else {\n pipeline = choices.find((choice) => choice.pipeline.isDefault) ?? (choices.length === 1 ? choices[0] : undefined)\n }\n if (!pipeline) return { ok: false, reason: 'unknown-pipeline' }\n let stage: CrmDealStage | undefined\n if (row.stage) {\n const wanted = dealImportNameKey(row.stage)\n stage = pipeline.stages.find((candidate) => dealImportNameKey(candidate.name ?? '') === wanted)\n } else {\n stage = pipeline.stages.find((candidate) => candidate.kind === 'open') ?? pipeline.stages[0]\n }\n if (!stage) return { ok: false, reason: 'unknown-stage' }\n return { ok: true, pipeline, stage }\n}\n\n/**\n * `POST crm/deals-import` — `{ hostId, rows }` → a {@link DealImportChunkResult}.\n *\n * Rows are written one after another so the band's local count stays\n * honest across the request.\n */\nexport const crmDealsImportHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const read = readImportRows<DealImportRawRow>(req, {\n maxBodyBytes: DEAL_IMPORT_MAX_BODY_BYTES,\n chunkSize: DEAL_IMPORT_CHUNK_SIZE,\n })\n if ('error' in read) return res.status(read.status).json({ error: read.error })\n try {\n const context = await resolveImportContext(req)\n if (context.ok === false) return res.status(context.status).json(context.body)\n\n const skipped: DealImportSkippedRow[] = []\n const dropped: Record<string, number> = {}\n const normalized: { index: number; row: DealImportRow }[] = []\n read.rows.forEach((raw, index) => {\n const verdict = normalizeDealImportRow(raw)\n if (verdict.ok === false) {\n skipped.push({ index, title: verdict.input, reason: verdict.reason })\n return\n }\n for (const entry of verdict.row.dropped) {\n dropped[entry.field] = (dropped[entry.field] ?? 0) + 1\n }\n normalized.push({ index, row: verdict.row })\n })\n\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(context.orgId)\n const [owners, pipelines] = await Promise.all([\n ownerDirectory(\n context.orgId,\n normalized.map((entry) => entry.row),\n ),\n normalized.length ? readPipelines(orgRef, context.readTokens) : Promise.resolve([]),\n ])\n const ownersUnresolved = new Set<string>()\n const deals = orgRef.collection(CRM_COLLECTIONS.deals)\n const stamp = creationStamp(context)\n let counted: { crmRecordsCount: number } | null = null\n let createdHere = 0\n let created = 0\n\n for (const { index, row } of normalized) {\n const placed = placeDealImportRow(pipelines, row)\n if (placed.ok === false) {\n skipped.push({ index, title: row.title, reason: placed.reason })\n continue\n }\n let ownerUid: string | undefined\n if (row.ownerEmail) {\n ownerUid = owners.get(row.ownerEmail)\n if (!ownerUid) ownersUnresolved.add(row.ownerEmail)\n }\n if (!counted) counted = await crmRecordsQuotaForOrg(context.org as never, orgRef)\n const room = checkCrmRecordsQuota(\n context.org as never,\n counted.crmRecordsCount + createdHere,\n )\n if (!room.allowed) {\n skipped.push({ index, title: row.title, reason: 'records-band' })\n continue\n }\n const nowMs = Date.now()\n const { stage } = placed\n const status = stage.kind === 'won' ? 'won' : stage.kind === 'lost' ? 'lost' : 'open'\n await deals.add({\n title: row.title,\n titleLower: nameSearchKey(row.title),\n pipelineId: placed.pipeline.id,\n stageId: stage.id,\n status,\n stageChangedAtMs: nowMs,\n closedAtMs: status === 'open' ? null : nowMs,\n currency: row.currency ?? DEFAULT_CURRENCY,\n ...(typeof row.amountCents === 'number' ? { amountCents: row.amountCents } : {}),\n ...(ownerUid ? { ownerUid } : {}),\n ...(typeof row.expectedCloseAtMs === 'number'\n ? { expectedCloseAtMs: row.expectedCloseAtMs }\n : {}),\n ...(row.notes ? { notes: row.notes } : {}),\n ...stamp,\n })\n createdHere += 1\n created += 1\n }\n\n const result: DealImportChunkResult = {\n received: read.rows.length,\n created,\n merged: 0,\n skipped: skipped.sort((a, b) => a.index - b.index),\n dropped,\n ownersUnresolved: [...ownersUnresolved],\n }\n return res.status(200).json(result)\n } catch (error) {\n console.error('crm/deals-import failed', error)\n return res.status(500).json({ error: 'The import could not continue.' })\n }\n}\n\n/** The scope, the site, the importer and the clocks every created deal carries. */\nfunction creationStamp(\n context: Extract<ImportContext, { ok: true }>,\n): Record<string, unknown> {\n return {\n hostId: context.hostId,\n visibleTo: context.scopeTokens,\n createdByUid: context.uid,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n}\n"],"names":["checkCrmRecordsQuota","CRM_COLLECTIONS","isPipelineArchived","nameSearchKey","visibleToTokens","DEAL_IMPORT_CHUNK_SIZE","DEAL_IMPORT_MAX_BODY_BYTES","dealImportNameKey","normalizeDealImportRow","crmRecordsQuotaForOrg","firebaseAdmin","FieldValue","ownerDirectory","readImportRows","resolveImportContext","DEFAULT_CURRENCY","readPipelines","orgRef","readTokens","snapshot","collection","pipelines","get","choices","doc","docs","pipeline","data","visibleTo","stages","sort","a","b","order","push","id","placeDealImportRow","row","wanted","find","choice","name","isDefault","length","undefined","ok","reason","stage","candidate","kind","crmDealsImportHandler","req","res","method","setHeader","status","json","error","read","maxBodyBytes","chunkSize","context","body","skipped","dropped","normalized","rows","forEach","raw","index","verdict","title","input","entry","field","app","firestore","orgId","owners","Promise","all","map","resolve","ownersUnresolved","Set","deals","stamp","creationStamp","counted","createdHere","created","placed","ownerUid","ownerEmail","add","org","room","crmRecordsCount","allowed","nowMs","Date","now","titleLower","pipelineId","stageId","stageChangedAtMs","closedAtMs","currency","amountCents","expectedCloseAtMs","notes","result","received","merged","console","hostId","scopeTokens","createdByUid","uid","createdAt","serverTimestamp","updatedAt"],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* `POST /api/crm/email-send` — one email to one person, from their record
|
|
20
|
+
* (AGL-2615).
|
|
21
|
+
*
|
|
22
|
+
* Body: `{ hostId, contactId? | leadId? | dealId?, subject, body }`. Answers
|
|
23
|
+
* `{ ok: true, activityId, to, from, logged }` once the provider accepted
|
|
24
|
+
* the message, and a refusal with a `reason` the dialog can act on
|
|
25
|
+
* otherwise. The subject and the body may carry merge fields —
|
|
26
|
+
* `{{contact.firstName}}`, `{{deal.amount}}` (AGL-2658) — which this route
|
|
27
|
+
* fills from the record before the send, so the timeline row is the letter
|
|
28
|
+
* as it left; see `crm-email-templates.ts` for the grammar.
|
|
29
|
+
*
|
|
30
|
+
* ## What it is, and what it is not
|
|
31
|
+
*
|
|
32
|
+
* A message a teammate wrote to one person they have a relationship with —
|
|
33
|
+
* a reply to a lead, a follow-up on a deal. It is not marketing: no
|
|
34
|
+
* campaign audience, no unsubscribe pair, no frequency window, no consent
|
|
35
|
+
* split. What it does owe is everything a bounce or a complaint owes the
|
|
36
|
+
* shared sending domain, which is why BOTH suppression lists are consulted
|
|
37
|
+
* and why a person's own stated refusal — a `declined` basis — is a hard
|
|
38
|
+
* stop even though the message is not the kind of mail that basis governs.
|
|
39
|
+
* The recipient asked this site not to write; the kind of writing is not
|
|
40
|
+
* the point.
|
|
41
|
+
*
|
|
42
|
+
* ## Why the recipient is resolved here and not taken from the body
|
|
43
|
+
*
|
|
44
|
+
* The dialog shows the address the record carries, and this route reads it
|
|
45
|
+
* again off the record the caller named, through the same visibility check
|
|
46
|
+
* every other CRM route makes. A `to` in the body would make this a route
|
|
47
|
+
* that sends the site's mail to any address a member types, which is a
|
|
48
|
+
* different product with a different abuse surface.
|
|
49
|
+
*
|
|
50
|
+
* ## The organization variant (AGL-2634)
|
|
51
|
+
*
|
|
52
|
+
* `{ orgId, hostId?, … }` from the org-level hub. The caller is authorized
|
|
53
|
+
* by the org — an org-wide member holding `data.manage` — and the record is
|
|
54
|
+
* read with no site's visibility to check, because an org-wide member reads
|
|
55
|
+
* every row. What the org level cannot do away with is the SITE the message
|
|
56
|
+
* leaves from: the sending identity, the suppression list and the person's
|
|
57
|
+
* stated refusal are each a site's. So the org variant still names one —
|
|
58
|
+
* the record's own capturing site or the site the reader picked, in the
|
|
59
|
+
* body; failing that the record's own site read off the document; failing
|
|
60
|
+
* that the org's only site — and refuses, with a `site` reason, when the
|
|
61
|
+
* org has several and none was named. The row it writes is visible to
|
|
62
|
+
* whoever may see the RECORD, so an email to a person another site
|
|
63
|
+
* captured shows on their timeline under that site's hub; the act is
|
|
64
|
+
* logged in the org's feed.
|
|
65
|
+
*
|
|
66
|
+
* ## The order of the gates
|
|
67
|
+
*
|
|
68
|
+
* Cheap and certain first. A malformed body and a missing token cost
|
|
69
|
+
* nothing; the per-user pace is one counter; the record is one read and
|
|
70
|
+
* decides everything after it; the ceiling one aggregate; the two
|
|
71
|
+
* suppression lists two keyed reads; the sending identity next, because it
|
|
72
|
+
* is the most expensive and the one a workspace fixes once. The daily cap
|
|
73
|
+
* comes LAST and is the one gate that writes before the provider is called
|
|
74
|
+
* (AGL-2645): the send is reserved on today's counter inside a transaction,
|
|
75
|
+
* so two reps at the last slot cannot both pass one reading of it, and a
|
|
76
|
+
* message the provider refuses gives the slot back. After the provider has
|
|
77
|
+
* accepted, two more things are written, neither able to fail the send:
|
|
78
|
+
* the activity row and the org's cost meter.
|
|
79
|
+
*
|
|
80
|
+
* ## The new-workspace ramp is part of that cap, not a gate beside it
|
|
81
|
+
*
|
|
82
|
+
* A message from a workspace created this morning lands on the sending
|
|
83
|
+
* domain every other tenant's receipts leave on, so the plan's day is held
|
|
84
|
+
* to the share of it the workspace has earned — the same ramp the campaign
|
|
85
|
+
* path is paced by, `readOrgEmailRamp`, taken to the allowance this surface
|
|
86
|
+
* meters instead of the campaign ceiling. It is handed to the reservation
|
|
87
|
+
* rather than checked before it, so the ceiling and the count it is judged
|
|
88
|
+
* against come out of the one transactional read; a ramp consulted
|
|
89
|
+
* separately would be a second ceiling and two reps could pass it at once.
|
|
90
|
+
* A workspace past its first week is not ramped and pays nothing to find
|
|
91
|
+
* that out.
|
|
92
|
+
*/
|
|
93
|
+
/** How many one-to-one emails one person may send in one minute. */
|
|
94
|
+
export declare const CRM_EMAIL_SENDS_PER_MINUTE = 20;
|
|
95
|
+
/** What the route says when the plan carries no one-to-one email at all. */
|
|
96
|
+
export declare const CRM_EMAIL_NOT_INCLUDED_MESSAGE: string;
|
|
97
|
+
/** What the route says at the daily cap; `included` is the plan's figure. */
|
|
98
|
+
export declare function crmEmailCapReachedMessage(included: number): string;
|
|
99
|
+
/**
|
|
100
|
+
* What the route says when the NEW-WORKSPACE RAMP is the ceiling that was
|
|
101
|
+
* reached, rather than the plan's own.
|
|
102
|
+
*
|
|
103
|
+
* The campaign path's wording, pointed at this surface: the number, what was
|
|
104
|
+
* spent against it, that nothing was sent or counted, and when it lifts. A
|
|
105
|
+
* workspace told only "limit reached" against a figure far below the one its
|
|
106
|
+
* plan page advertises would read it as a billing fault.
|
|
107
|
+
*/
|
|
108
|
+
export declare function crmEmailRampReachedMessage(ceiling: number, used: number): string;
|
|
109
|
+
/** What the route says when the ramp's own history could not be read. */
|
|
110
|
+
export declare const CRM_EMAIL_RAMP_UNAVAILABLE_MESSAGE: string;
|
|
111
|
+
export declare const CRM_EMAIL_SUPPRESSED_MESSAGE: string;
|
|
112
|
+
export declare const CRM_EMAIL_DECLINED_MESSAGE = "This person asked not to be emailed by this site.";
|
|
113
|
+
export declare const CRM_EMAIL_RATE_MESSAGE = "You have sent too many emails in the last minute. Wait a moment and try again.";
|
|
114
|
+
/** What the org variant says when no site was named and the org has several. */
|
|
115
|
+
export declare const CRM_EMAIL_PICK_SITE_MESSAGE: string;
|
|
116
|
+
/** What the route says when a subject or a message is nothing once its fields are filled. */
|
|
117
|
+
export declare const CRM_EMAIL_EMPTY_AFTER_MERGE_MESSAGE: string;
|
|
118
|
+
export declare const crmEmailSendHandler: PluginApiHandler;
|
|
119
|
+
export default crmEmailSendHandler;
|