@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,73 @@
|
|
|
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/contacts-import` — one chunk of a contact file, written
|
|
19
|
+
* (AGL-2602).
|
|
20
|
+
*
|
|
21
|
+
* The browser has already read the file and applied the operator's column
|
|
22
|
+
* mapping; what arrives here is up to {@link CONTACT_IMPORT_CHUNK_SIZE} raw
|
|
23
|
+
* rows in the field vocabulary `crm-import.ts` defines. This route judges
|
|
24
|
+
* each one and writes it, and it is the ONLY place either happens: the
|
|
25
|
+
* drawer shows a preview from the same mapper but stores nothing, so a
|
|
26
|
+
* stale tab cannot store something the server would have refused.
|
|
27
|
+
*
|
|
28
|
+
* ## Every row goes through the door every capture goes through
|
|
29
|
+
*
|
|
30
|
+
* `captureHostContact` — the runtime's wrapper over `upsertHostContact`,
|
|
31
|
+
* the one writer of the contacts collection for every door that is not the
|
|
32
|
+
* v1 API: forms, checkout, bookings, the newsletter box — is the writer
|
|
33
|
+
* here too, so a row this file creates raises `contactCreated` like any
|
|
34
|
+
* other capture (AGL-2605). That is what makes an import
|
|
35
|
+
* dedupe on the address the way a second form submission does, land in the
|
|
36
|
+
* capturing group's facet the way a form capture does, record consent
|
|
37
|
+
* against the capturing site, and stop at a hard band where a form
|
|
38
|
+
* capture stops. A bulk path with its own `add()` would be the fastest way
|
|
39
|
+
* to grow an unscoped, unbanded, unconsented copy of the address book.
|
|
40
|
+
*
|
|
41
|
+
* What the door did not know how to write until now — a phone, a title, a
|
|
42
|
+
* company, an owner, a stage, custom values — it now takes as `facet`, and
|
|
43
|
+
* what it did not say until now — created or merged or refused — it now
|
|
44
|
+
* returns as a verdict. Both changes are the door's, so the v1 API and the
|
|
45
|
+
* manual add can reach them next.
|
|
46
|
+
*
|
|
47
|
+
* ## Two lookups paid once per request, not once per row
|
|
48
|
+
*
|
|
49
|
+
* An owner is named by email in the file and stored by uid on the record,
|
|
50
|
+
* so the org's roster is read once and every row resolves against it. A
|
|
51
|
+
* company is named by name and stored by id, so each distinct name is
|
|
52
|
+
* looked up once, created once when missing, and remembered for the rest
|
|
53
|
+
* of the request — two hundred rows at one company is one read and at most
|
|
54
|
+
* one write, not two hundred of each.
|
|
55
|
+
*
|
|
56
|
+
* ## Who may call it
|
|
57
|
+
*
|
|
58
|
+
* `resolveImportContext` (`import-context.ts`) — the two gates every CSV
|
|
59
|
+
* import route asks, shared with the companies import so one permission
|
|
60
|
+
* has one spelling.
|
|
61
|
+
*/
|
|
62
|
+
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
63
|
+
/** The one sentence every imported contact's timeline opens with. */
|
|
64
|
+
export declare const CONTACT_IMPORT_INTERACTION_SUMMARY = "Imported from CSV";
|
|
65
|
+
/**
|
|
66
|
+
* `POST crm/contacts-import` — `{ hostId, rows }` → a {@link ContactImportChunkResult}.
|
|
67
|
+
*
|
|
68
|
+
* Rows are written one after another rather than in parallel, because the
|
|
69
|
+
* door's create path counts the collection before it adds: two hundred
|
|
70
|
+
* creates racing one another would each count the same audience and each
|
|
71
|
+
* pass a band the last of them should have hit.
|
|
72
|
+
*/
|
|
73
|
+
export declare const crmContactsImportHandler: PluginApiHandler;
|
|
@@ -0,0 +1,263 @@
|
|
|
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/contacts-import` — one chunk of a contact file, written
|
|
19
|
+
* (AGL-2602).
|
|
20
|
+
*
|
|
21
|
+
* The browser has already read the file and applied the operator's column
|
|
22
|
+
* mapping; what arrives here is up to {@link CONTACT_IMPORT_CHUNK_SIZE} raw
|
|
23
|
+
* rows in the field vocabulary `crm-import.ts` defines. This route judges
|
|
24
|
+
* each one and writes it, and it is the ONLY place either happens: the
|
|
25
|
+
* drawer shows a preview from the same mapper but stores nothing, so a
|
|
26
|
+
* stale tab cannot store something the server would have refused.
|
|
27
|
+
*
|
|
28
|
+
* ## Every row goes through the door every capture goes through
|
|
29
|
+
*
|
|
30
|
+
* `captureHostContact` — the runtime's wrapper over `upsertHostContact`,
|
|
31
|
+
* the one writer of the contacts collection for every door that is not the
|
|
32
|
+
* v1 API: forms, checkout, bookings, the newsletter box — is the writer
|
|
33
|
+
* here too, so a row this file creates raises `contactCreated` like any
|
|
34
|
+
* other capture (AGL-2605). That is what makes an import
|
|
35
|
+
* dedupe on the address the way a second form submission does, land in the
|
|
36
|
+
* capturing group's facet the way a form capture does, record consent
|
|
37
|
+
* against the capturing site, and stop at a hard band where a form
|
|
38
|
+
* capture stops. A bulk path with its own `add()` would be the fastest way
|
|
39
|
+
* to grow an unscoped, unbanded, unconsented copy of the address book.
|
|
40
|
+
*
|
|
41
|
+
* What the door did not know how to write until now — a phone, a title, a
|
|
42
|
+
* company, an owner, a stage, custom values — it now takes as `facet`, and
|
|
43
|
+
* what it did not say until now — created or merged or refused — it now
|
|
44
|
+
* returns as a verdict. Both changes are the door's, so the v1 API and the
|
|
45
|
+
* manual add can reach them next.
|
|
46
|
+
*
|
|
47
|
+
* ## Two lookups paid once per request, not once per row
|
|
48
|
+
*
|
|
49
|
+
* An owner is named by email in the file and stored by uid on the record,
|
|
50
|
+
* so the org's roster is read once and every row resolves against it. A
|
|
51
|
+
* company is named by name and stored by id, so each distinct name is
|
|
52
|
+
* looked up once, created once when missing, and remembered for the rest
|
|
53
|
+
* of the request — two hundred rows at one company is one read and at most
|
|
54
|
+
* one write, not two hundred of each.
|
|
55
|
+
*
|
|
56
|
+
* ## Who may call it
|
|
57
|
+
*
|
|
58
|
+
* `resolveImportContext` (`import-context.ts`) — the two gates every CSV
|
|
59
|
+
* import route asks, shared with the companies import so one permission
|
|
60
|
+
* has one spelling.
|
|
61
|
+
*/ import { CRM_COLLECTIONS, nameSearchFields, visibleToTokens } from "@aglyn/aglyn/server";
|
|
62
|
+
import { CONTACT_IMPORT_CHUNK_SIZE, CONTACT_IMPORT_MAX_BODY_BYTES, normalizeContactImportRow } from "../model/crm-import.js";
|
|
63
|
+
import { crmRecordsQuotaForOrg, firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
64
|
+
import { captureHostContact } from "@aglyn/tenant-runtime";
|
|
65
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
66
|
+
import { ownerDirectory, readImportRows, resolveImportContext } from "./import-context.js";
|
|
67
|
+
/** The one sentence every imported contact's timeline opens with. */ export const CONTACT_IMPORT_INTERACTION_SUMMARY = 'Imported from CSV';
|
|
68
|
+
/**
|
|
69
|
+
* The holder's live custom-field definitions.
|
|
70
|
+
*
|
|
71
|
+
* Read in full and filtered in memory: a holder has a handful of fields,
|
|
72
|
+
* and a `where` on `retiredAt` would need an index for a collection that
|
|
73
|
+
* fits in one page. Definitions the caller's scope cannot see are left out
|
|
74
|
+
* for the same reason a value under an undefined key is — a value written
|
|
75
|
+
* under a field the holder's own form will never show is a value nobody
|
|
76
|
+
* can edit.
|
|
77
|
+
*/ async function loadFieldDefinitions(orgId, readTokens) {
|
|
78
|
+
const snapshot = await firebaseAdmin.app().firestore().collection('orgs').doc(orgId).collection(CRM_COLLECTIONS.contactFields).limit(200).get();
|
|
79
|
+
return snapshot.docs.map((doc)=>doc.data()).filter((field)=>!!field.key && !field.retiredAt && visibleToTokens(field.visibleTo, readTokens));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The company a name refers to in this scope, created when there is none.
|
|
83
|
+
*
|
|
84
|
+
* Looked up by `nameLower`, the search twin `nameSearchFields` writes on
|
|
85
|
+
* every company, and narrowed in memory to the ones the caller's scope can
|
|
86
|
+
* see — two clients of one agency may each have an "Acme" and must each get
|
|
87
|
+
* their own. Created with the same scope stamp every CRM creator uses, so
|
|
88
|
+
* the company lands exactly where a contact captured on this site would.
|
|
89
|
+
*/ async function resolveCompanyId(context, name, cache, tally) {
|
|
90
|
+
const fields = nameSearchFields(name);
|
|
91
|
+
const cached = cache.get(fields.nameLower);
|
|
92
|
+
if (cached) return cached;
|
|
93
|
+
const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(context.orgId);
|
|
94
|
+
const companies = orgRef.collection(CRM_COLLECTIONS.companies);
|
|
95
|
+
const matches = await companies.where('nameLower', '==', fields.nameLower).limit(10).get();
|
|
96
|
+
const seen = matches.docs.find((doc)=>visibleToTokens(doc.get('visibleTo'), context.readTokens));
|
|
97
|
+
if (seen) {
|
|
98
|
+
cache.set(fields.nameLower, seen.id);
|
|
99
|
+
return seen.id;
|
|
100
|
+
}
|
|
101
|
+
/*
|
|
102
|
+
* THE RECORDS BAND (AGL-2611). A company is a record of the same band
|
|
103
|
+
* the contact behind this row will meet at its own door, so on an org
|
|
104
|
+
* at a full hard band the row gets no company rather than a company it
|
|
105
|
+
* was not allowed to hold. Nothing is reported here: the contact is
|
|
106
|
+
* refused `audience-band` a few lines on and the row is listed skipped,
|
|
107
|
+
* which is the one message the operator needs. A plan with an overage
|
|
108
|
+
* rate never reaches this branch — a rate is what makes the band meter
|
|
109
|
+
* instead of refuse.
|
|
110
|
+
*/ const room = await crmRecordsQuotaForOrg(context.org, orgRef);
|
|
111
|
+
if (!room.allowed) return '';
|
|
112
|
+
const created = await companies.add(_extends({}, fields, {
|
|
113
|
+
hostId: context.hostId,
|
|
114
|
+
visibleTo: context.scopeTokens,
|
|
115
|
+
createdByUid: context.uid,
|
|
116
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
117
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
118
|
+
}));
|
|
119
|
+
tally.created += 1;
|
|
120
|
+
cache.set(fields.nameLower, created.id);
|
|
121
|
+
return created.id;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `POST crm/contacts-import` — `{ hostId, rows }` → a {@link ContactImportChunkResult}.
|
|
125
|
+
*
|
|
126
|
+
* Rows are written one after another rather than in parallel, because the
|
|
127
|
+
* door's create path counts the collection before it adds: two hundred
|
|
128
|
+
* creates racing one another would each count the same audience and each
|
|
129
|
+
* pass a band the last of them should have hit.
|
|
130
|
+
*/ export const crmContactsImportHandler = async (req, res)=>{
|
|
131
|
+
if (req.method !== 'POST') {
|
|
132
|
+
res.setHeader('Allow', 'POST');
|
|
133
|
+
return res.status(405).json({
|
|
134
|
+
error: 'Method not allowed'
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const read = readImportRows(req, {
|
|
138
|
+
maxBodyBytes: CONTACT_IMPORT_MAX_BODY_BYTES,
|
|
139
|
+
chunkSize: CONTACT_IMPORT_CHUNK_SIZE
|
|
140
|
+
});
|
|
141
|
+
if ('error' in read) return res.status(read.status).json({
|
|
142
|
+
error: read.error
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
const context = await resolveImportContext(req);
|
|
146
|
+
if (context.ok === false) return res.status(context.status).json(context.body);
|
|
147
|
+
const fields = await loadFieldDefinitions(context.orgId, context.readTokens);
|
|
148
|
+
const skipped = [];
|
|
149
|
+
const dropped = {};
|
|
150
|
+
const normalized = [];
|
|
151
|
+
/*
|
|
152
|
+
* A duplicate WITHIN the request is skipped rather than merged: the
|
|
153
|
+
* second row would merge onto the first's record and the count would
|
|
154
|
+
* read one created and one merged for one person in one file, which is
|
|
155
|
+
* a number the operator cannot reconcile against the rows they sent.
|
|
156
|
+
* Across requests the door's own dedupe answers, and reports a merge.
|
|
157
|
+
*/ const seen = new Set();
|
|
158
|
+
read.rows.forEach((raw, index)=>{
|
|
159
|
+
const verdict = normalizeContactImportRow(raw, fields);
|
|
160
|
+
if (verdict.ok === false) {
|
|
161
|
+
skipped.push({
|
|
162
|
+
index,
|
|
163
|
+
email: verdict.input,
|
|
164
|
+
reason: verdict.reason
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (seen.has(verdict.row.email)) {
|
|
169
|
+
skipped.push({
|
|
170
|
+
index,
|
|
171
|
+
email: verdict.row.email,
|
|
172
|
+
reason: 'duplicate'
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
seen.add(verdict.row.email);
|
|
177
|
+
for (const entry of verdict.row.dropped){
|
|
178
|
+
var _dropped_entry_field;
|
|
179
|
+
dropped[entry.field] = ((_dropped_entry_field = dropped[entry.field]) != null ? _dropped_entry_field : 0) + 1;
|
|
180
|
+
}
|
|
181
|
+
normalized.push({
|
|
182
|
+
index,
|
|
183
|
+
row: verdict.row
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
const owners = await ownerDirectory(context.orgId, normalized.map((entry)=>entry.row));
|
|
187
|
+
const ownersUnresolved = new Set();
|
|
188
|
+
const companies = new Map();
|
|
189
|
+
const companyTally = {
|
|
190
|
+
created: 0
|
|
191
|
+
};
|
|
192
|
+
let created = 0;
|
|
193
|
+
let merged = 0;
|
|
194
|
+
for (const { index, row } of normalized){
|
|
195
|
+
let ownerUid;
|
|
196
|
+
if (row.ownerEmail) {
|
|
197
|
+
ownerUid = owners.get(row.ownerEmail);
|
|
198
|
+
if (!ownerUid) ownersUnresolved.add(row.ownerEmail);
|
|
199
|
+
}
|
|
200
|
+
const companyId = row.companyName ? await resolveCompanyId(context, row.companyName, companies, companyTally) : undefined;
|
|
201
|
+
const verdict = await captureHostContact(_extends({
|
|
202
|
+
hostId: context.hostId,
|
|
203
|
+
email: row.email
|
|
204
|
+
}, row.name ? {
|
|
205
|
+
name: row.name
|
|
206
|
+
} : {}, {
|
|
207
|
+
source: 'import',
|
|
208
|
+
interaction: {
|
|
209
|
+
summary: CONTACT_IMPORT_INTERACTION_SUMMARY
|
|
210
|
+
},
|
|
211
|
+
marketingConsent: row.marketingConsent,
|
|
212
|
+
tags: row.tags,
|
|
213
|
+
facet: _extends({}, row.phone ? {
|
|
214
|
+
phone: row.phone
|
|
215
|
+
} : {}, row.jobTitle ? {
|
|
216
|
+
jobTitle: row.jobTitle
|
|
217
|
+
} : {}, companyId ? {
|
|
218
|
+
companyId
|
|
219
|
+
} : {}, row.address ? {
|
|
220
|
+
address: row.address
|
|
221
|
+
} : {}, ownerUid ? {
|
|
222
|
+
ownerUid
|
|
223
|
+
} : {}, row.lifecycleStage ? {
|
|
224
|
+
lifecycleStage: row.lifecycleStage
|
|
225
|
+
} : {}, Object.keys(row.custom).length ? {
|
|
226
|
+
custom: row.custom
|
|
227
|
+
} : {}),
|
|
228
|
+
// An import is the merchant's own act and files nobody under a
|
|
229
|
+
// campaign; the picker on the profile is where that happens.
|
|
230
|
+
campaignIds: []
|
|
231
|
+
}));
|
|
232
|
+
if ('refused' in verdict) {
|
|
233
|
+
skipped.push({
|
|
234
|
+
index,
|
|
235
|
+
email: row.email,
|
|
236
|
+
reason: verdict.refused === 'band' ? 'audience-band' : verdict.refused === 'invalid-email' ? 'invalid-email' : verdict.refused === 'erased' ? 'erased' : 'write-failed'
|
|
237
|
+
});
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (verdict.created) created += 1;
|
|
241
|
+
else merged += 1;
|
|
242
|
+
}
|
|
243
|
+
const result = {
|
|
244
|
+
received: read.rows.length,
|
|
245
|
+
created,
|
|
246
|
+
merged,
|
|
247
|
+
skipped: skipped.sort((a, b)=>a.index - b.index),
|
|
248
|
+
dropped,
|
|
249
|
+
companiesCreated: companyTally.created,
|
|
250
|
+
ownersUnresolved: [
|
|
251
|
+
...ownersUnresolved
|
|
252
|
+
]
|
|
253
|
+
};
|
|
254
|
+
return res.status(200).json(result);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('crm/contacts-import failed', error);
|
|
257
|
+
return res.status(500).json({
|
|
258
|
+
error: 'The import could not continue.'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=contacts-import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/contacts-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/contacts-import` — one chunk of a contact file, written\n * (AGL-2602).\n *\n * The browser has already read the file and applied the operator's column\n * mapping; what arrives here is up to {@link CONTACT_IMPORT_CHUNK_SIZE} raw\n * rows in the field vocabulary `crm-import.ts` defines. This route judges\n * each one and writes it, and it is the ONLY place either happens: the\n * drawer shows a preview from the same mapper but stores nothing, so a\n * stale tab cannot store something the server would have refused.\n *\n * ## Every row goes through the door every capture goes through\n *\n * `captureHostContact` — the runtime's wrapper over `upsertHostContact`,\n * the one writer of the contacts collection for every door that is not the\n * v1 API: forms, checkout, bookings, the newsletter box — is the writer\n * here too, so a row this file creates raises `contactCreated` like any\n * other capture (AGL-2605). That is what makes an import\n * dedupe on the address the way a second form submission does, land in the\n * capturing group's facet the way a form capture does, record consent\n * against the capturing site, and stop at a hard band where a form\n * capture stops. A bulk path with its own `add()` would be the fastest way\n * to grow an unscoped, unbanded, unconsented copy of the address book.\n *\n * What the door did not know how to write until now — a phone, a title, a\n * company, an owner, a stage, custom values — it now takes as `facet`, and\n * what it did not say until now — created or merged or refused — it now\n * returns as a verdict. Both changes are the door's, so the v1 API and the\n * manual add can reach them next.\n *\n * ## Two lookups paid once per request, not once per row\n *\n * An owner is named by email in the file and stored by uid on the record,\n * so the org's roster is read once and every row resolves against it. A\n * company is named by name and stored by id, so each distinct name is\n * looked up once, created once when missing, and remembered for the rest\n * of the request — two hundred rows at one company is one read and at most\n * one write, not two hundred of each.\n *\n * ## Who may call it\n *\n * `resolveImportContext` (`import-context.ts`) — the two gates every CSV\n * import route asks, shared with the companies import so one permission\n * has one spelling.\n */\n\nimport {\n type ContactFieldDefinition,\n CRM_COLLECTIONS,\n nameSearchFields,\n type PluginApiHandler,\n visibleToTokens,\n} from '@aglyn/aglyn/server'\nimport {\n CONTACT_IMPORT_CHUNK_SIZE,\n CONTACT_IMPORT_MAX_BODY_BYTES,\n type ContactImportChunkResult,\n type ContactImportRawRow,\n type ContactImportRow,\n type ContactImportSkippedRow,\n normalizeContactImportRow,\n} from '../model/crm-import'\nimport { crmRecordsQuotaForOrg, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { captureHostContact } from '@aglyn/tenant-runtime'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n type ImportContext,\n ownerDirectory,\n readImportRows,\n resolveImportContext,\n} from './import-context'\n\n/** The one sentence every imported contact's timeline opens with. */\nexport const CONTACT_IMPORT_INTERACTION_SUMMARY = 'Imported from CSV'\n\n/**\n * The holder's live custom-field definitions.\n *\n * Read in full and filtered in memory: a holder has a handful of fields,\n * and a `where` on `retiredAt` would need an index for a collection that\n * fits in one page. Definitions the caller's scope cannot see are left out\n * for the same reason a value under an undefined key is — a value written\n * under a field the holder's own form will never show is a value nobody\n * can edit.\n */\nasync function loadFieldDefinitions(\n orgId: string,\n readTokens: readonly string[],\n): Promise<ContactFieldDefinition[]> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection(CRM_COLLECTIONS.contactFields)\n .limit(200)\n .get()\n return snapshot.docs\n .map((doc) => doc.data() as ContactFieldDefinition)\n .filter(\n (field) =>\n !!field.key &&\n !field.retiredAt &&\n visibleToTokens(field.visibleTo, readTokens),\n )\n}\n\n/**\n * The company a name refers to in this scope, created when there is none.\n *\n * Looked up by `nameLower`, the search twin `nameSearchFields` writes on\n * every company, and narrowed in memory to the ones the caller's scope can\n * see — two clients of one agency may each have an \"Acme\" and must each get\n * their own. Created with the same scope stamp every CRM creator uses, so\n * the company lands exactly where a contact captured on this site would.\n */\nasync function resolveCompanyId(\n context: Extract<ImportContext, { ok: true }>,\n name: string,\n cache: Map<string, string>,\n tally: { created: number },\n): Promise<string> {\n const fields = nameSearchFields(name)\n const cached = cache.get(fields.nameLower)\n if (cached) return cached\n const orgRef = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(context.orgId)\n const companies = orgRef.collection(CRM_COLLECTIONS.companies)\n const matches = await companies\n .where('nameLower', '==', fields.nameLower)\n .limit(10)\n .get()\n const seen = matches.docs.find((doc) =>\n visibleToTokens(doc.get('visibleTo'), context.readTokens),\n )\n if (seen) {\n cache.set(fields.nameLower, seen.id)\n return seen.id\n }\n /*\n * THE RECORDS BAND (AGL-2611). A company is a record of the same band\n * the contact behind this row will meet at its own door, so on an org\n * at a full hard band the row gets no company rather than a company it\n * was not allowed to hold. Nothing is reported here: the contact is\n * refused `audience-band` a few lines on and the row is listed skipped,\n * which is the one message the operator needs. A plan with an overage\n * rate never reaches this branch — a rate is what makes the band meter\n * instead of refuse.\n */\n const room = await crmRecordsQuotaForOrg(context.org as never, orgRef)\n if (!room.allowed) return ''\n const created = await companies.add({\n ...fields,\n hostId: context.hostId,\n visibleTo: context.scopeTokens,\n createdByUid: context.uid,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n tally.created += 1\n cache.set(fields.nameLower, created.id)\n return created.id\n}\n\n/**\n * `POST crm/contacts-import` — `{ hostId, rows }` → a {@link ContactImportChunkResult}.\n *\n * Rows are written one after another rather than in parallel, because the\n * door's create path counts the collection before it adds: two hundred\n * creates racing one another would each count the same audience and each\n * pass a band the last of them should have hit.\n */\nexport const crmContactsImportHandler: 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<ContactImportRawRow>(req, {\n maxBodyBytes: CONTACT_IMPORT_MAX_BODY_BYTES,\n chunkSize: CONTACT_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 fields = await loadFieldDefinitions(context.orgId, context.readTokens)\n const skipped: ContactImportSkippedRow[] = []\n const dropped: Record<string, number> = {}\n const normalized: { index: number; row: ContactImportRow }[] = []\n /*\n * A duplicate WITHIN the request is skipped rather than merged: the\n * second row would merge onto the first's record and the count would\n * read one created and one merged for one person in one file, which is\n * a number the operator cannot reconcile against the rows they sent.\n * Across requests the door's own dedupe answers, and reports a merge.\n */\n const seen = new Set<string>()\n read.rows.forEach((raw, index) => {\n const verdict = normalizeContactImportRow(raw, fields)\n if (verdict.ok === false) {\n skipped.push({ index, email: verdict.input, reason: verdict.reason })\n return\n }\n if (seen.has(verdict.row.email)) {\n skipped.push({ index, email: verdict.row.email, reason: 'duplicate' })\n return\n }\n seen.add(verdict.row.email)\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 owners = await ownerDirectory(\n context.orgId,\n normalized.map((entry) => entry.row),\n )\n const ownersUnresolved = new Set<string>()\n const companies = new Map<string, string>()\n const companyTally = { created: 0 }\n let created = 0\n let merged = 0\n\n for (const { index, row } of normalized) {\n let ownerUid: string | undefined\n if (row.ownerEmail) {\n ownerUid = owners.get(row.ownerEmail)\n if (!ownerUid) ownersUnresolved.add(row.ownerEmail)\n }\n const companyId = row.companyName\n ? await resolveCompanyId(context, row.companyName, companies, companyTally)\n : undefined\n const verdict = await captureHostContact({\n hostId: context.hostId,\n email: row.email,\n ...(row.name ? { name: row.name } : {}),\n source: 'import',\n interaction: { summary: CONTACT_IMPORT_INTERACTION_SUMMARY },\n marketingConsent: row.marketingConsent,\n tags: row.tags,\n facet: {\n ...(row.phone ? { phone: row.phone } : {}),\n ...(row.jobTitle ? { jobTitle: row.jobTitle } : {}),\n ...(companyId ? { companyId } : {}),\n ...(row.address ? { address: row.address } : {}),\n ...(ownerUid ? { ownerUid } : {}),\n ...(row.lifecycleStage ? { lifecycleStage: row.lifecycleStage } : {}),\n ...(Object.keys(row.custom).length ? { custom: row.custom } : {}),\n },\n // An import is the merchant's own act and files nobody under a\n // campaign; the picker on the profile is where that happens.\n campaignIds: [],\n })\n if ('refused' in verdict) {\n skipped.push({\n index,\n email: row.email,\n reason:\n verdict.refused === 'band'\n ? 'audience-band'\n : verdict.refused === 'invalid-email'\n ? 'invalid-email'\n : verdict.refused === 'erased'\n ? 'erased'\n : 'write-failed',\n })\n continue\n }\n if (verdict.created) created += 1\n else merged += 1\n }\n\n const result: ContactImportChunkResult = {\n received: read.rows.length,\n created,\n merged,\n skipped: skipped.sort((a, b) => a.index - b.index),\n dropped,\n companiesCreated: companyTally.created,\n ownersUnresolved: [...ownersUnresolved],\n }\n return res.status(200).json(result)\n } catch (error) {\n console.error('crm/contacts-import failed', error)\n return res.status(500).json({ error: 'The import could not continue.' })\n }\n}\n"],"names":["CRM_COLLECTIONS","nameSearchFields","visibleToTokens","CONTACT_IMPORT_CHUNK_SIZE","CONTACT_IMPORT_MAX_BODY_BYTES","normalizeContactImportRow","crmRecordsQuotaForOrg","firebaseAdmin","captureHostContact","FieldValue","ownerDirectory","readImportRows","resolveImportContext","CONTACT_IMPORT_INTERACTION_SUMMARY","loadFieldDefinitions","orgId","readTokens","snapshot","app","firestore","collection","doc","contactFields","limit","get","docs","map","data","filter","field","key","retiredAt","visibleTo","resolveCompanyId","context","name","cache","tally","fields","cached","nameLower","orgRef","companies","matches","where","seen","find","set","id","room","org","allowed","created","add","hostId","scopeTokens","createdByUid","uid","createdAt","serverTimestamp","updatedAt","crmContactsImportHandler","req","res","method","setHeader","status","json","error","read","maxBodyBytes","chunkSize","ok","body","skipped","dropped","normalized","Set","rows","forEach","raw","index","verdict","push","email","input","reason","has","row","entry","owners","ownersUnresolved","Map","companyTally","merged","ownerUid","ownerEmail","companyId","companyName","undefined","source","interaction","summary","marketingConsent","tags","facet","phone","jobTitle","address","lifecycleStage","Object","keys","custom","length","campaignIds","refused","result","received","sort","a","b","companiesCreated","console"],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
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/contacts-merge` — two records for one person become one
|
|
19
|
+
* (AGL-2625).
|
|
20
|
+
*
|
|
21
|
+
* Body: `{ hostId, survivorId, mergedId }`. The survivor keeps its address
|
|
22
|
+
* as the identity; the merged record's address becomes an alternate on it,
|
|
23
|
+
* every deal, task, activity and lead naming the merged record is repointed,
|
|
24
|
+
* and the merged document is deleted. `mergeContacts` in the data library
|
|
25
|
+
* is the whole of the work, so the REST door performs the same merge.
|
|
26
|
+
*
|
|
27
|
+
* ## Who may call it
|
|
28
|
+
*
|
|
29
|
+
* An ORG-WIDE member holding `data.manage`. Narrower than the other CRM
|
|
30
|
+
* routes, which admit a site-scoped member for the site they reach, and
|
|
31
|
+
* deliberately: a merge is org-scoped by nature. Contacts are org documents
|
|
32
|
+
* shared by every site that captured the person, and folding two of them
|
|
33
|
+
* together touches every holder's facet, every site's consent entry and
|
|
34
|
+
* every deal in the org that named the merged record — a decision about
|
|
35
|
+
* records a site-scoped member cannot read, made on their behalf. The
|
|
36
|
+
* console offers the action to org-wide members only, and this is the door
|
|
37
|
+
* that makes that a rule rather than a hint.
|
|
38
|
+
*
|
|
39
|
+
* ## Why a server route
|
|
40
|
+
*
|
|
41
|
+
* A browser could write the survivor and delete the merged record under the
|
|
42
|
+
* rules, and could not do the rest: a scoped `array-contains-any` listener
|
|
43
|
+
* cannot repoint deals it may not list, the address index is closed to
|
|
44
|
+
* clients, and the transaction over both documents is what keeps two merges
|
|
45
|
+
* of one pair from both succeeding.
|
|
46
|
+
*
|
|
47
|
+
* ## The organization variant (AGL-2634)
|
|
48
|
+
*
|
|
49
|
+
* `{ orgId, hostId?, survivorId, mergedId }` from the org-level hub. The
|
|
50
|
+
* same org-wide caller, authorized by the org directly rather than through
|
|
51
|
+
* a site it names — so a record no site captured can be merged there — and
|
|
52
|
+
* the act logged in the org's feed. A site beside the org is the record's
|
|
53
|
+
* own, and is where the data library files the timeline note and the
|
|
54
|
+
* site's feed line when there is one.
|
|
55
|
+
*/
|
|
56
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
57
|
+
export declare const CONTACTS_MERGE_ROUTE = "crm/contacts-merge";
|
|
58
|
+
/** The body the console's merge dialog posts. */
|
|
59
|
+
export interface ContactsMergeRequest {
|
|
60
|
+
/** The site the merge runs as; at the organization level the record's own, or absent. */
|
|
61
|
+
hostId?: string;
|
|
62
|
+
/** The organization, at the organization level (AGL-2634). */
|
|
63
|
+
orgId?: string;
|
|
64
|
+
/** `orgs/{orgId}/contacts/{id}` — the record that stays. */
|
|
65
|
+
survivorId: string;
|
|
66
|
+
/** The record folded into it and deleted. */
|
|
67
|
+
mergedId: string;
|
|
68
|
+
}
|
|
69
|
+
/** What the route answers on success. */
|
|
70
|
+
export interface ContactsMergeResponse {
|
|
71
|
+
ok: true;
|
|
72
|
+
survivorId: string;
|
|
73
|
+
survivorEmail: string;
|
|
74
|
+
mergedId: string;
|
|
75
|
+
mergedEmail: string;
|
|
76
|
+
/** Every address the survivor answers to now, primary first. */
|
|
77
|
+
emails: string[];
|
|
78
|
+
repointed: {
|
|
79
|
+
deals: number;
|
|
80
|
+
tasks: number;
|
|
81
|
+
activities: number;
|
|
82
|
+
leads: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export declare const contactsMergeHandler: PluginApiHandler;
|
|
86
|
+
export default contactsMergeHandler;
|
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
* `POST /api/crm/contacts-merge` — two records for one person become one
|
|
18
|
+
* (AGL-2625).
|
|
19
|
+
*
|
|
20
|
+
* Body: `{ hostId, survivorId, mergedId }`. The survivor keeps its address
|
|
21
|
+
* as the identity; the merged record's address becomes an alternate on it,
|
|
22
|
+
* every deal, task, activity and lead naming the merged record is repointed,
|
|
23
|
+
* and the merged document is deleted. `mergeContacts` in the data library
|
|
24
|
+
* is the whole of the work, so the REST door performs the same merge.
|
|
25
|
+
*
|
|
26
|
+
* ## Who may call it
|
|
27
|
+
*
|
|
28
|
+
* An ORG-WIDE member holding `data.manage`. Narrower than the other CRM
|
|
29
|
+
* routes, which admit a site-scoped member for the site they reach, and
|
|
30
|
+
* deliberately: a merge is org-scoped by nature. Contacts are org documents
|
|
31
|
+
* shared by every site that captured the person, and folding two of them
|
|
32
|
+
* together touches every holder's facet, every site's consent entry and
|
|
33
|
+
* every deal in the org that named the merged record — a decision about
|
|
34
|
+
* records a site-scoped member cannot read, made on their behalf. The
|
|
35
|
+
* console offers the action to org-wide members only, and this is the door
|
|
36
|
+
* that makes that a rule rather than a hint.
|
|
37
|
+
*
|
|
38
|
+
* ## Why a server route
|
|
39
|
+
*
|
|
40
|
+
* A browser could write the survivor and delete the merged record under the
|
|
41
|
+
* rules, and could not do the rest: a scoped `array-contains-any` listener
|
|
42
|
+
* cannot repoint deals it may not list, the address index is closed to
|
|
43
|
+
* clients, and the transaction over both documents is what keeps two merges
|
|
44
|
+
* of one pair from both succeeding.
|
|
45
|
+
*
|
|
46
|
+
* ## The organization variant (AGL-2634)
|
|
47
|
+
*
|
|
48
|
+
* `{ orgId, hostId?, survivorId, mergedId }` from the org-level hub. The
|
|
49
|
+
* same org-wide caller, authorized by the org directly rather than through
|
|
50
|
+
* a site it names — so a record no site captured can be merged there — and
|
|
51
|
+
* the act logged in the org's feed. A site beside the org is the record's
|
|
52
|
+
* own, and is where the data library files the timeline note and the
|
|
53
|
+
* site's feed line when there is one.
|
|
54
|
+
*/ import { firebaseAdmin, getOrgForHost, logOrgActivity, mergeContacts } from "@aglyn/tenant-data-admin";
|
|
55
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
56
|
+
import { authorizeOrgCaller, holdsDataManage, readCrmRouteScope } from "./org-caller.js";
|
|
57
|
+
import { crmSuiteRefusal } from "./suite-gate.js";
|
|
58
|
+
export const CONTACTS_MERGE_ROUTE = 'crm/contacts-merge';
|
|
59
|
+
function typed(value, max) {
|
|
60
|
+
return String(value != null ? value : '').trim().slice(0, max);
|
|
61
|
+
}
|
|
62
|
+
export const contactsMergeHandler = async (req, res)=>{
|
|
63
|
+
var _req_body;
|
|
64
|
+
if (req.method !== 'POST') {
|
|
65
|
+
res.setHeader('Allow', 'POST');
|
|
66
|
+
res.status(405).json({
|
|
67
|
+
error: 'Method not allowed'
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const body = typeof req.body === 'string' ? JSON.parse(req.body) : (_req_body = req.body) != null ? _req_body : {};
|
|
72
|
+
const routeScope = readCrmRouteScope(body);
|
|
73
|
+
const survivorId = typed(body.survivorId, 200);
|
|
74
|
+
const mergedId = typed(body.mergedId, 200);
|
|
75
|
+
if (!routeScope || !survivorId || !mergedId) {
|
|
76
|
+
res.status(400).json({
|
|
77
|
+
error: 'Missing hostId, survivorId or mergedId'
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (survivorId === mergedId) {
|
|
82
|
+
res.status(400).json({
|
|
83
|
+
error: 'Pick two different contacts to merge'
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { hostId } = routeScope;
|
|
88
|
+
const refusal = 'Merging contacts requires the data permission across the whole workspace';
|
|
89
|
+
try {
|
|
90
|
+
let orgId;
|
|
91
|
+
let org;
|
|
92
|
+
let actor;
|
|
93
|
+
let actorName;
|
|
94
|
+
let orgActor = null;
|
|
95
|
+
if (routeScope.level === 'org') {
|
|
96
|
+
const caller = await authorizeOrgCaller(req, routeScope.orgId, {
|
|
97
|
+
needs: 'data.manage',
|
|
98
|
+
refusal
|
|
99
|
+
});
|
|
100
|
+
if (caller.ok === false) {
|
|
101
|
+
res.status(caller.status).json({
|
|
102
|
+
error: caller.error
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
orgId = caller.orgId;
|
|
107
|
+
org = caller.org;
|
|
108
|
+
actor = {
|
|
109
|
+
uid: caller.uid,
|
|
110
|
+
email: caller.email
|
|
111
|
+
};
|
|
112
|
+
actorName = caller.name || null;
|
|
113
|
+
orgActor = actor;
|
|
114
|
+
} else {
|
|
115
|
+
var _req_headers_authorization, _decoded_email;
|
|
116
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
117
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
118
|
+
if (!idToken) {
|
|
119
|
+
res.status(401).json({
|
|
120
|
+
error: 'Unauthenticated'
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
125
|
+
const staff = decoded['staff'] === true;
|
|
126
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
127
|
+
hostId
|
|
128
|
+
});
|
|
129
|
+
if (!staff && !(membership.orgWide && await holdsDataManage(membership.orgId, decoded.uid))) {
|
|
130
|
+
res.status(403).json({
|
|
131
|
+
error: refusal
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const resolved = await getOrgForHost(hostId);
|
|
136
|
+
if (!resolved) {
|
|
137
|
+
res.status(404).json({
|
|
138
|
+
error: 'Unknown site'
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
orgId = resolved.orgId;
|
|
143
|
+
org = resolved.org;
|
|
144
|
+
actor = {
|
|
145
|
+
uid: decoded.uid,
|
|
146
|
+
email: (_decoded_email = decoded.email) != null ? _decoded_email : null
|
|
147
|
+
};
|
|
148
|
+
actorName = typeof decoded['name'] === 'string' ? decoded['name'] : null;
|
|
149
|
+
}
|
|
150
|
+
// Merging is the CRM's at either level (AGL-2787).
|
|
151
|
+
const suite = crmSuiteRefusal(org, 'Merging two contacts');
|
|
152
|
+
if (suite) {
|
|
153
|
+
res.status(suite.status).json(suite.body);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
157
|
+
const result = await mergeContacts({
|
|
158
|
+
firestore,
|
|
159
|
+
orgRef: firestore.collection('orgs').doc(orgId),
|
|
160
|
+
survivorId,
|
|
161
|
+
mergedId,
|
|
162
|
+
actor,
|
|
163
|
+
hostId: hostId || null,
|
|
164
|
+
actorName
|
|
165
|
+
});
|
|
166
|
+
if (result.ok === false) {
|
|
167
|
+
if (result.reason === 'same-record') {
|
|
168
|
+
res.status(400).json({
|
|
169
|
+
error: 'Pick two different contacts to merge'
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
res.status(404).json({
|
|
174
|
+
error: result.reason === 'survivor-missing' ? 'The contact to keep could not be found' : 'The contact to merge could not be found — it may already have been merged'
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// The org-level act, in the org's feed: the data library wrote the
|
|
179
|
+
// site's line when a site was named, and the org's is this door's.
|
|
180
|
+
if (orgActor) {
|
|
181
|
+
await logOrgActivity(orgId, orgActor, `Merged with ${result.mergedEmail}`, {
|
|
182
|
+
type: 'contact',
|
|
183
|
+
id: result.survivorId,
|
|
184
|
+
name: result.survivorEmail
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const answer = {
|
|
188
|
+
ok: true,
|
|
189
|
+
survivorId: result.survivorId,
|
|
190
|
+
survivorEmail: result.survivorEmail,
|
|
191
|
+
mergedId: result.mergedId,
|
|
192
|
+
mergedEmail: result.mergedEmail,
|
|
193
|
+
emails: result.emails,
|
|
194
|
+
repointed: result.repointed
|
|
195
|
+
};
|
|
196
|
+
res.status(200).json(answer);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('[crm] contacts-merge failed', routeScope, survivorId, mergedId, error);
|
|
199
|
+
res.status(500).json({
|
|
200
|
+
error: 'The contacts could not be merged.'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
export default contactsMergeHandler;
|
|
205
|
+
|
|
206
|
+
//# sourceMappingURL=contacts-merge.js.map
|