@aglyn/plugins-crm 1.0.0-beta.218 → 1.0.0-beta.220
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +14 -14
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.js +12 -4
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/company-detail-page.js +11 -0
- package/src/lib/components/company-detail-page.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +12 -2
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-import-drawer.d.ts +1 -1
- package/src/lib/components/company-picker.js +10 -3
- package/src/lib/components/company-picker.js.map +1 -1
- package/src/lib/components/contact-associations-card.js +4 -3
- package/src/lib/components/contact-associations-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.d.ts +4 -4
- package/src/lib/components/contact-detail-page.js +50 -53
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-list-columns.d.ts +3 -0
- package/src/lib/components/contact-list-columns.js +21 -5
- package/src/lib/components/contact-list-columns.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +2 -1
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-timeline-card.js +4 -3
- package/src/lib/components/contact-timeline-card.js.map +1 -1
- package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
- package/src/lib/components/contacts-bulk-bar.js +24 -51
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +9 -3
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-booking-zone.d.ts +8 -2
- package/src/lib/components/crm-booking-zone.js.map +1 -1
- package/src/lib/components/crm-export-all-button.d.ts +1 -1
- package/src/lib/components/crm-export-all-button.js.map +1 -1
- package/src/lib/components/crm-glance-card.js +2 -1
- package/src/lib/components/crm-glance-card.js.map +1 -1
- package/src/lib/components/crm-send-email-dialog.js +9 -6
- package/src/lib/components/crm-send-email-dialog.js.map +1 -1
- package/src/lib/components/csv-import-drawer.js +2 -2
- package/src/lib/components/csv-import-drawer.js.map +1 -1
- package/src/lib/components/deal-detail-page.js +18 -2
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +10 -0
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-products-card.js +27 -16
- package/src/lib/components/deal-products-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.js +13 -5
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.d.ts +4 -4
- package/src/lib/components/lead-campaigns-card.js +15 -8
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +25 -5
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +10 -0
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-status-chip.js +3 -1
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-surfaces-note.js +4 -5
- package/src/lib/components/lead-surfaces-note.js.map +1 -1
- package/src/lib/components/lead-unqualify-dialog.js +6 -0
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.js +34 -11
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +41 -8
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/new-lead-drawer.js +5 -4
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/org-lead-surfaces-note.js +6 -2
- package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
- package/src/lib/components/recipes-card.js +9 -4
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-sharing-card.d.ts +57 -0
- package/src/lib/components/record-sharing-card.js +244 -0
- package/src/lib/components/record-sharing-card.js.map +1 -0
- package/src/lib/components/reports/activity-card.js +4 -3
- package/src/lib/components/reports/activity-card.js.map +1 -1
- package/src/lib/components/reports/closed-deals-card.js +4 -3
- package/src/lib/components/reports/closed-deals-card.js.map +1 -1
- package/src/lib/components/reports/contacts-mix-card.js +3 -2
- package/src/lib/components/reports/contacts-mix-card.js.map +1 -1
- package/src/lib/components/reports/contacts-trend-card.js +2 -1
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.js +3 -2
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.js +8 -6
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/pipeline-card.js +4 -3
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-format.d.ts +1 -1
- package/src/lib/components/reports/report-format.js.map +1 -1
- package/src/lib/components/reports/report-scope.d.ts +1 -1
- package/src/lib/components/reports/report-scope.js.map +1 -1
- package/src/lib/components/reports/source-conversion-card.js +2 -1
- package/src/lib/components/reports/source-conversion-card.js.map +1 -1
- package/src/lib/components/reports/tasks-card.js +2 -1
- package/src/lib/components/reports/tasks-card.js.map +1 -1
- package/src/lib/components/reports/won-lost-by-owner-card.js +3 -2
- package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -1
- package/src/lib/components/reports-section.js +4 -4
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/settings-section.js +7 -0
- package/src/lib/components/settings-section.js.map +1 -1
- package/src/lib/components/share-records-dialog.d.ts +28 -0
- package/src/lib/components/share-records-dialog.js +184 -0
- package/src/lib/components/share-records-dialog.js.map +1 -0
- package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
- package/src/lib/components/sharing-rule-drawer.js +374 -0
- package/src/lib/components/sharing-rule-drawer.js.map +1 -0
- package/src/lib/components/sharing-rules-card.d.ts +24 -0
- package/src/lib/components/sharing-rules-card.js +287 -0
- package/src/lib/components/sharing-rules-card.js.map +1 -0
- package/src/lib/components/use-crm-api.d.ts +1 -1
- package/src/lib/components/use-crm-api.js.map +1 -1
- package/src/lib/components/use-site-route-context.d.ts +25 -0
- package/src/lib/components/use-site-route-context.js +58 -0
- package/src/lib/components/use-site-route-context.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +31 -0
- package/src/lib/constants/api-routes.js +27 -1
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +2 -0
- package/src/lib/constants/bundle-common.js +1 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +41 -0
- package/src/lib/declarations.console-server.js +115 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.server.d.ts +12 -0
- package/src/lib/declarations.server.js +84 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-contact-remove.d.ts +35 -0
- package/src/lib/hooks/use-contact-remove.js +77 -0
- package/src/lib/hooks/use-contact-remove.js.map +1 -0
- package/src/lib/hooks/use-crm-campaigns.d.ts +2 -2
- package/src/lib/hooks/use-crm-campaigns.js +3 -3
- package/src/lib/hooks/use-crm-campaigns.js.map +1 -1
- package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
- package/src/lib/hooks/use-crm-sharing.js +109 -0
- package/src/lib/hooks/use-crm-sharing.js.map +1 -0
- package/src/lib/model/campaign-filing-activity.d.ts +1 -1
- package/src/lib/model/campaign-filing-activity.js +1 -1
- package/src/lib/model/campaign-filing-activity.js.map +1 -1
- package/src/lib/model/companies-csv.d.ts +1 -1
- package/src/lib/model/companies-csv.js +2 -2
- package/src/lib/model/companies-csv.js.map +1 -1
- package/src/lib/model/contact-holder.d.ts +68 -0
- package/src/lib/model/contact-holder.js +129 -0
- package/src/lib/model/contact-holder.js.map +1 -0
- package/src/lib/model/contact-interaction-links.d.ts +46 -0
- package/src/lib/model/contact-interaction-links.js +65 -0
- package/src/lib/model/contact-interaction-links.js.map +1 -0
- package/src/lib/model/contact-merge.d.ts +136 -0
- package/src/lib/model/contact-merge.js +412 -0
- package/src/lib/model/contact-merge.js.map +1 -0
- package/src/lib/model/contact-record.d.ts +7 -1
- package/src/lib/model/contact-record.js +7 -6
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-remove.d.ts +48 -0
- package/src/lib/model/contact-remove.js +22 -0
- package/src/lib/model/contact-remove.js.map +1 -0
- package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
- package/src/lib/model/contacts-bulk-writes.js +2 -82
- package/src/lib/model/contacts-bulk-writes.js.map +1 -1
- package/src/lib/model/contacts-csv.d.ts +2 -2
- package/src/lib/model/contacts-csv.js +2 -2
- package/src/lib/model/contacts-csv.js.map +1 -1
- package/src/lib/model/crm-csv.d.ts +239 -0
- package/src/lib/model/crm-csv.js +467 -0
- package/src/lib/model/crm-csv.js.map +1 -0
- package/src/lib/model/crm-digest.d.ts +227 -0
- package/src/lib/model/crm-digest.js +336 -0
- package/src/lib/model/crm-digest.js.map +1 -0
- package/src/lib/model/crm-import.d.ts +7 -1
- package/src/lib/model/crm-import.js +2 -1
- package/src/lib/model/crm-import.js.map +1 -1
- package/src/lib/model/crm-inbound.d.ts +162 -0
- package/src/lib/model/crm-inbound.js +208 -0
- package/src/lib/model/crm-inbound.js.map +1 -0
- package/src/lib/model/crm-lead-import.d.ts +1 -1
- package/src/lib/model/crm-lead-import.js +4 -4
- package/src/lib/model/crm-lead-import.js.map +1 -1
- package/src/lib/model/crm-record-routes.d.ts +5 -0
- package/src/lib/model/crm-record-routes.js +7 -0
- package/src/lib/model/crm-record-routes.js.map +1 -1
- package/src/lib/model/crm-reports.d.ts +467 -0
- package/src/lib/model/crm-reports.js +667 -0
- package/src/lib/model/crm-reports.js.map +1 -0
- package/src/lib/model/crm-routes.d.ts +6 -5
- package/src/lib/model/crm-routes.js +7 -7
- package/src/lib/model/crm-routes.js.map +1 -1
- package/src/lib/model/crm-search-sources.d.ts +15 -0
- package/src/lib/model/crm-search-sources.js +162 -0
- package/src/lib/model/crm-search-sources.js.map +1 -0
- package/src/lib/model/crm-sharing-api.d.ts +32 -0
- package/src/lib/model/crm-sharing-api.js +59 -0
- package/src/lib/model/crm-sharing-api.js.map +1 -0
- package/src/lib/model/crm-sharing.d.ts +333 -0
- package/src/lib/model/crm-sharing.js +603 -0
- package/src/lib/model/crm-sharing.js.map +1 -0
- package/src/lib/model/deals-csv.d.ts +1 -1
- package/src/lib/model/deals-csv.js +4 -5
- package/src/lib/model/deals-csv.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +5 -4
- package/src/lib/model/lead-filters.js +7 -5
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/leads-csv.d.ts +1 -1
- package/src/lib/model/leads-csv.js +1 -1
- package/src/lib/model/leads-csv.js.map +1 -1
- package/src/lib/model/tasks-csv.d.ts +1 -1
- package/src/lib/model/tasks-csv.js +1 -1
- package/src/lib/model/tasks-csv.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +75 -0
- package/src/lib/plan-entitlements.js +56 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +39 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
- package/src/lib/server/api-v1/contacts-merge.js +120 -0
- package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
- package/src/lib/server/api-v1/contacts.d.ts +18 -0
- package/src/lib/server/api-v1/contacts.js +1076 -0
- package/src/lib/server/api-v1/contacts.js.map +1 -0
- package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
- package/src/lib/server/api-v1/crm-activities.js +269 -0
- package/src/lib/server/api-v1/crm-activities.js.map +1 -0
- package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
- package/src/lib/server/api-v1/crm-companies.js +391 -0
- package/src/lib/server/api-v1/crm-companies.js.map +1 -0
- package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
- package/src/lib/server/api-v1/crm-deals.js +631 -0
- package/src/lib/server/api-v1/crm-deals.js.map +1 -0
- package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
- package/src/lib/server/api-v1/crm-email-templates.js +336 -0
- package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
- package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
- package/src/lib/server/api-v1/crm-leads.js +833 -0
- package/src/lib/server/api-v1/crm-leads.js.map +1 -0
- package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
- package/src/lib/server/api-v1/crm-pipelines.js +146 -0
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
- package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
- package/src/lib/server/api-v1/crm-shared.js +436 -0
- package/src/lib/server/api-v1/crm-shared.js.map +1 -0
- package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
- package/src/lib/server/api-v1/crm-tasks.js +398 -0
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
- package/src/lib/server/api-v1/openapi.d.ts +29 -0
- package/src/lib/server/api-v1/openapi.js +985 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -0
- package/src/lib/server/api-v1/usage.d.ts +26 -0
- package/src/lib/server/api-v1/usage.js +73 -0
- package/src/lib/server/api-v1/usage.js.map +1 -0
- package/src/lib/server/campaign-filing-activity.d.ts +1 -1
- package/src/lib/server/campaign-filing-activity.js +7 -11
- package/src/lib/server/campaign-filing-activity.js.map +1 -1
- package/src/lib/server/capture-contact.js +15 -4
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/consent-group-participant.js +1 -1
- package/src/lib/server/consent-group-participant.js.map +1 -1
- package/src/lib/server/contact-email-engagement.d.ts +33 -0
- package/src/lib/server/contact-email-engagement.js +146 -0
- package/src/lib/server/contact-email-engagement.js.map +1 -0
- package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
- package/src/lib/server/contact-lifecycle-floor.js +91 -0
- package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
- package/src/lib/server/contact-remove.d.ts +75 -0
- package/src/lib/server/contact-remove.js +244 -0
- package/src/lib/server/contact-remove.js.map +1 -0
- package/src/lib/server/contact-update.js +4 -3
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/contacts-import.d.ts +47 -11
- package/src/lib/server/contacts-import.js +171 -71
- package/src/lib/server/contacts-import.js.map +1 -1
- package/src/lib/server/contacts-merge.d.ts +1 -1
- package/src/lib/server/contacts-merge.js +3 -2
- package/src/lib/server/contacts-merge.js.map +1 -1
- package/src/lib/server/container-detach.d.ts +31 -0
- package/src/lib/server/container-detach.js +115 -0
- package/src/lib/server/container-detach.js.map +1 -0
- package/src/lib/server/convert-host-lead.d.ts +189 -0
- package/src/lib/server/convert-host-lead.js +518 -0
- package/src/lib/server/convert-host-lead.js.map +1 -0
- package/src/lib/server/crm-inbound-email.d.ts +162 -0
- package/src/lib/server/crm-inbound-email.js +259 -0
- package/src/lib/server/crm-inbound-email.js.map +1 -0
- package/src/lib/server/crm-records-meter.d.ts +50 -0
- package/src/lib/server/crm-records-meter.js +126 -0
- package/src/lib/server/crm-records-meter.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +108 -0
- package/src/lib/server/crm-sharing.js +790 -0
- package/src/lib/server/crm-sharing.js.map +1 -0
- package/src/lib/server/daily-digest-route.d.ts +101 -0
- package/src/lib/server/daily-digest-route.js +578 -0
- package/src/lib/server/daily-digest-route.js.map +1 -0
- package/src/lib/server/export-route.d.ts +60 -0
- package/src/lib/server/export-route.js +447 -0
- package/src/lib/server/export-route.js.map +1 -0
- package/src/lib/server/inbound-address.js +5 -3
- package/src/lib/server/inbound-address.js.map +1 -1
- package/src/lib/server/inbound-route.d.ts +74 -0
- package/src/lib/server/inbound-route.js +260 -0
- package/src/lib/server/inbound-route.js.map +1 -0
- package/src/lib/server/lead-campaign-carry.js +3 -3
- package/src/lib/server/lead-campaign-carry.js.map +1 -1
- package/src/lib/server/lead-convert.d.ts +1 -1
- package/src/lib/server/lead-convert.js +2 -2
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +1 -1
- package/src/lib/server/lead-create.js +10 -15
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +23 -0
- package/src/lib/server/lead-nurturing.js +82 -0
- package/src/lib/server/lead-nurturing.js.map +1 -0
- package/src/lib/server/leads-import.js +7 -7
- package/src/lib/server/leads-import.js.map +1 -1
- package/src/lib/server/merge-contacts.d.ts +55 -0
- package/src/lib/server/merge-contacts.js +274 -0
- package/src/lib/server/merge-contacts.js.map +1 -0
- package/src/lib/server/pipeline-record-index.d.ts +36 -0
- package/src/lib/server/pipeline-record-index.js +87 -0
- package/src/lib/server/pipeline-record-index.js.map +1 -0
- package/src/lib/server/record-email-state.js +23 -0
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.js +2 -1
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-timeline.d.ts +12 -0
- package/src/lib/server/record-timeline.js +127 -15
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/task-reminders-route.d.ts +82 -0
- package/src/lib/server/task-reminders-route.js +499 -0
- package/src/lib/server/task-reminders-route.js.map +1 -0
- package/src/lib/server/task-routes.d.ts +2 -0
- package/src/lib/server/task-routes.js +22 -3
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.js +2 -1
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +5 -2
- package/src/lib/server.js +47 -2
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
- package/src/lib/testing/stand-in-site-record-routes.js +55 -0
- package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
- package/src/lib/usage-axes.d.ts +35 -0
- package/src/lib/usage-axes.js +75 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +0 -24
- package/src/lib/components/use-emails-hub-path.js +0 -59
- package/src/lib/components/use-emails-hub-path.js.map +0 -1
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
*/ 'use client';
|
|
17
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
18
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
19
|
+
import { readCrmSharingRules } from "../model/crm-sharing.js";
|
|
20
|
+
import { callCrmSharing, followUpCrmSharing } from "../model/crm-sharing-api.js";
|
|
21
|
+
import { crmTaskCallScope } from "../model/task-routes.js";
|
|
22
|
+
import { useCrmOrgMount } from "./use-crm-org-mount.js";
|
|
23
|
+
/**
|
|
24
|
+
* The org's sites, for a sharing surface (AGL-3336).
|
|
25
|
+
*
|
|
26
|
+
* At the organization level the hub already holds them (`useCrmOrgMount`).
|
|
27
|
+
* Under a site a member cannot list the org's other sites, so a MANAGER's
|
|
28
|
+
* surface asks `crm/sharing` for them — the one reader a share is offered
|
|
29
|
+
* to — and every other reader names them as "another site".
|
|
30
|
+
*/ export function useCrmSharingSites(input) {
|
|
31
|
+
const { hostId, orgId, enabled } = input;
|
|
32
|
+
const mount = useCrmOrgMount();
|
|
33
|
+
const { data: user } = useUser();
|
|
34
|
+
const [fetched, setFetched] = useState(null);
|
|
35
|
+
const scope = crmTaskCallScope(hostId, orgId);
|
|
36
|
+
const key = scope ? JSON.stringify(scope) : '';
|
|
37
|
+
const fromMount = mount && !hostId;
|
|
38
|
+
useEffect(()=>{
|
|
39
|
+
if (fromMount || !enabled || !scope || !user) return;
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
callCrmSharing(user, scope, 'sites').then((answer)=>{
|
|
42
|
+
var _answer_sites;
|
|
43
|
+
if (!cancelled) setFetched({
|
|
44
|
+
key,
|
|
45
|
+
sites: (_answer_sites = answer.sites) != null ? _answer_sites : []
|
|
46
|
+
});
|
|
47
|
+
}).catch(()=>{
|
|
48
|
+
if (!cancelled) setFetched({
|
|
49
|
+
key,
|
|
50
|
+
sites: []
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
return ()=>{
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
56
|
+
// `scope` is keyed by `key`; the object itself is rebuilt every render.
|
|
57
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
58
|
+
}, [
|
|
59
|
+
fromMount,
|
|
60
|
+
enabled,
|
|
61
|
+
key,
|
|
62
|
+
user
|
|
63
|
+
]);
|
|
64
|
+
const sites = useMemo(()=>fromMount ? mount.hosts.map((host)=>({
|
|
65
|
+
id: host.id,
|
|
66
|
+
name: host.name
|
|
67
|
+
})) : (fetched == null ? void 0 : fetched.key) === key ? fetched.sites : [], [
|
|
68
|
+
fromMount,
|
|
69
|
+
mount,
|
|
70
|
+
fetched,
|
|
71
|
+
key
|
|
72
|
+
]);
|
|
73
|
+
const ready = fromMount ? mount.hostsReady : (fetched == null ? void 0 : fetched.key) === key || !enabled;
|
|
74
|
+
const siteName = useCallback((id)=>{
|
|
75
|
+
var _ref;
|
|
76
|
+
var _sites_find;
|
|
77
|
+
return (_ref = (_sites_find = sites.find((site)=>site.id === id)) == null ? void 0 : _sites_find.name) != null ? _ref : id === hostId ? 'This site' : 'another site';
|
|
78
|
+
}, [
|
|
79
|
+
sites,
|
|
80
|
+
hostId
|
|
81
|
+
]);
|
|
82
|
+
return {
|
|
83
|
+
sites,
|
|
84
|
+
ready,
|
|
85
|
+
siteName
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** The org's sharing rules, off the org document the shell already passed. */ export function useCrmSharingRules(org) {
|
|
89
|
+
return useMemo(()=>readCrmSharingRules(org != null ? org : null), [
|
|
90
|
+
org
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The follow-up a CLIENT-DIRECT write to a lead, a company or a deal owes
|
|
95
|
+
* the org's sharing rules (AGL-3336): a server write is followed by the
|
|
96
|
+
* core's record-written seam, a browser's by this. Fire-and-forget — the
|
|
97
|
+
* save has landed whether or not a rule then shares the record.
|
|
98
|
+
*/ export function useCrmSharingFollowUp(hostId, orgId) {
|
|
99
|
+
const { data: user } = useUser();
|
|
100
|
+
return useCallback((object, ids)=>{
|
|
101
|
+
void followUpCrmSharing(user, crmTaskCallScope(hostId, orgId), object, ids);
|
|
102
|
+
}, [
|
|
103
|
+
user,
|
|
104
|
+
hostId,
|
|
105
|
+
orgId
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=use-crm-sharing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-crm-sharing.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { type CrmSharingObject, type CrmSharingSite, readCrmSharingRules } from '../model/crm-sharing'\nimport { callCrmSharing, followUpCrmSharing } from '../model/crm-sharing-api'\nimport { crmTaskCallScope } from '../model/task-routes'\nimport { useCrmOrgMount } from './use-crm-org-mount'\nimport type { CrmOrgDoc } from './use-crm-scope'\n\nexport interface CrmSharingSites {\n sites: CrmSharingSite[]\n ready: boolean\n /** How a site reads: its name, or a neutral phrase for one this reader cannot name. */\n siteName: (hostId: string) => string\n}\n\n/**\n * The org's sites, for a sharing surface (AGL-3336).\n *\n * At the organization level the hub already holds them (`useCrmOrgMount`).\n * Under a site a member cannot list the org's other sites, so a MANAGER's\n * surface asks `crm/sharing` for them — the one reader a share is offered\n * to — and every other reader names them as \"another site\".\n */\nexport function useCrmSharingSites(input: {\n hostId: string | null\n orgId: string | null\n enabled: boolean\n}): CrmSharingSites {\n const { hostId, orgId, enabled } = input\n const mount = useCrmOrgMount()\n const { data: user } = useUser()\n const [fetched, setFetched] = useState<{ key: string; sites: CrmSharingSite[] } | null>(null)\n const scope = crmTaskCallScope(hostId, orgId)\n const key = scope ? JSON.stringify(scope) : ''\n const fromMount = mount && !hostId\n useEffect(() => {\n if (fromMount || !enabled || !scope || !user) return\n let cancelled = false\n callCrmSharing(user, scope, 'sites')\n .then((answer) => {\n if (!cancelled) setFetched({ key, sites: answer.sites ?? [] })\n })\n .catch(() => {\n if (!cancelled) setFetched({ key, sites: [] })\n })\n return () => {\n cancelled = true\n }\n // `scope` is keyed by `key`; the object itself is rebuilt every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fromMount, enabled, key, user])\n const sites = useMemo<CrmSharingSite[]>(\n () =>\n fromMount\n ? mount.hosts.map((host) => ({ id: host.id, name: host.name }))\n : fetched?.key === key\n ? fetched.sites\n : [],\n [fromMount, mount, fetched, key],\n )\n const ready = fromMount ? mount.hostsReady : fetched?.key === key || !enabled\n const siteName = useCallback(\n (id: string) => sites.find((site) => site.id === id)?.name ?? (id === hostId ? 'This site' : 'another site'),\n [sites, hostId],\n )\n return { sites, ready, siteName }\n}\n\n/** The org's sharing rules, off the org document the shell already passed. */\nexport function useCrmSharingRules(org: CrmOrgDoc) {\n return useMemo(\n () => readCrmSharingRules((org ?? null) as Record<string, unknown> | null),\n [org],\n )\n}\n\n\n/**\n * The follow-up a CLIENT-DIRECT write to a lead, a company or a deal owes\n * the org's sharing rules (AGL-3336): a server write is followed by the\n * core's record-written seam, a browser's by this. Fire-and-forget — the\n * save has landed whether or not a rule then shares the record.\n */\nexport function useCrmSharingFollowUp(\n hostId: string | null | undefined,\n orgId: string | null | undefined,\n): (object: CrmSharingObject, ids: readonly string[]) => void {\n const { data: user } = useUser()\n return useCallback(\n (object: CrmSharingObject, ids: readonly string[]) => {\n void followUpCrmSharing(user, crmTaskCallScope(hostId, orgId), object, ids)\n },\n [user, hostId, orgId],\n )\n}\n"],"names":["useUser","useCallback","useEffect","useMemo","useState","readCrmSharingRules","callCrmSharing","followUpCrmSharing","crmTaskCallScope","useCrmOrgMount","useCrmSharingSites","input","hostId","orgId","enabled","mount","data","user","fetched","setFetched","scope","key","JSON","stringify","fromMount","cancelled","then","answer","sites","catch","hosts","map","host","id","name","ready","hostsReady","siteName","find","site","useCrmSharingRules","org","useCrmSharingFollowUp","object","ids"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,SAAqDC,mBAAmB,QAAQ,0BAAsB;AACtG,SAASC,cAAc,EAAEC,kBAAkB,QAAQ,8BAA0B;AAC7E,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,SAASC,cAAc,QAAQ,yBAAqB;AAUpD;;;;;;;CAOC,GACD,OAAO,SAASC,mBAAmBC,KAIlC;IACC,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,OAAO,EAAE,GAAGH;IACnC,MAAMI,QAAQN;IACd,MAAM,EAAEO,MAAMC,IAAI,EAAE,GAAGjB;IACvB,MAAM,CAACkB,SAASC,WAAW,GAAGf,SAA0D;IACxF,MAAMgB,QAAQZ,iBAAiBI,QAAQC;IACvC,MAAMQ,MAAMD,QAAQE,KAAKC,SAAS,CAACH,SAAS;IAC5C,MAAMI,YAAYT,SAAS,CAACH;IAC5BV,UAAU;QACR,IAAIsB,aAAa,CAACV,WAAW,CAACM,SAAS,CAACH,MAAM;QAC9C,IAAIQ,YAAY;QAChBnB,eAAeW,MAAMG,OAAO,SACzBM,IAAI,CAAC,CAACC;gBACoCA;YAAzC,IAAI,CAACF,WAAWN,WAAW;gBAAEE;gBAAKO,KAAK,GAAED,gBAAAA,OAAOC,KAAK,YAAZD,gBAAgB,EAAE;YAAC;QAC9D,GACCE,KAAK,CAAC;YACL,IAAI,CAACJ,WAAWN,WAAW;gBAAEE;gBAAKO,OAAO,EAAE;YAAC;QAC9C;QACF,OAAO;YACLH,YAAY;QACd;IACA,wEAAwE;IACxE,uDAAuD;IACzD,GAAG;QAACD;QAAWV;QAASO;QAAKJ;KAAK;IAClC,MAAMW,QAAQzB,QACZ,IACEqB,YACIT,MAAMe,KAAK,CAACC,GAAG,CAAC,CAACC,OAAU,CAAA;gBAAEC,IAAID,KAAKC,EAAE;gBAAEC,MAAMF,KAAKE,IAAI;YAAC,CAAA,KAC1DhB,CAAAA,2BAAAA,QAASG,GAAG,MAAKA,MACfH,QAAQU,KAAK,GACb,EAAE,EACV;QAACJ;QAAWT;QAAOG;QAASG;KAAI;IAElC,MAAMc,QAAQX,YAAYT,MAAMqB,UAAU,GAAGlB,CAAAA,2BAAAA,QAASG,GAAG,MAAKA,OAAO,CAACP;IACtE,MAAMuB,WAAWpC,YACf,CAACgC;;YAAeL;wBAAAA,cAAAA,MAAMU,IAAI,CAAC,CAACC,OAASA,KAAKN,EAAE,KAAKA,wBAAjCL,YAAsCM,IAAI,mBAAKD,OAAOrB,SAAS,cAAc;OAC7F;QAACgB;QAAOhB;KAAO;IAEjB,OAAO;QAAEgB;QAAOO;QAAOE;IAAS;AAClC;AAEA,4EAA4E,GAC5E,OAAO,SAASG,mBAAmBC,GAAc;IAC/C,OAAOtC,QACL,IAAME,oBAAqBoC,cAAAA,MAAO,OAClC;QAACA;KAAI;AAET;AAGA;;;;;CAKC,GACD,OAAO,SAASC,sBACd9B,MAAiC,EACjCC,KAAgC;IAEhC,MAAM,EAAEG,MAAMC,IAAI,EAAE,GAAGjB;IACvB,OAAOC,YACL,CAAC0C,QAA0BC;QACzB,KAAKrC,mBAAmBU,MAAMT,iBAAiBI,QAAQC,QAAQ8B,QAAQC;IACzE,GACA;QAAC3B;QAAML;QAAQC;KAAM;AAEzB"}
|
|
@@ -58,7 +58,7 @@ export declare function campaignFilingBody(action: CampaignFilingAction, campaig
|
|
|
58
58
|
/**
|
|
59
59
|
* The membership a save changed, as the entries it owes: one `filed` per
|
|
60
60
|
* id the save added, one `removed` per id it dropped. Order-insensitive,
|
|
61
|
-
* like `
|
|
61
|
+
* like `containerMembershipUnchanged`; a reorder owes nothing.
|
|
62
62
|
*/
|
|
63
63
|
export declare function campaignFilingChanges(before: readonly string[], after: readonly string[]): {
|
|
64
64
|
added: string[];
|
|
@@ -50,7 +50,7 @@ import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
|
50
50
|
/**
|
|
51
51
|
* The membership a save changed, as the entries it owes: one `filed` per
|
|
52
52
|
* id the save added, one `removed` per id it dropped. Order-insensitive,
|
|
53
|
-
* like `
|
|
53
|
+
* like `containerMembershipUnchanged`; a reorder owes nothing.
|
|
54
54
|
*/ export function campaignFilingChanges(before, after) {
|
|
55
55
|
return {
|
|
56
56
|
added: after.filter((id)=>!before.includes(id)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/campaign-filing-activity.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { CrmActivity, CrmActivityLink, ScopeToken } from '@aglyn/aglyn'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * WHAT A FILING WRITES ON THE RECORD'S TIMELINE (AGL-3274).\n *\n * A lead or a contact filed under a campaign — from its own page, the bulk\n * bar, the New lead drawer, or by conversion carrying a lead's campaigns\n * onto the contact it became — showed nothing of it in Activity, so the\n * timeline read \"email sent\" with no line for the act that put the person\n * in the campaign in the first place. Every door now files one entry per\n * campaign changed, and this module is the one place their shape lives:\n * the client hook and the server helper both build the document here, so\n * an entry filed from the page and one filed by the conversion read alike.\n *\n * ## A `note`, not a kind of its own\n *\n * The timeline's kinds are what a PERSON logs — call, email, meeting, note\n * — and a filing is bookkeeping beside them, the way a sequence's \"Enrolled\n * in\" entry is. A `filing` kind would need its own label, glyph, report\n * column and a place in the log dialog where a member could log one by\n * hand, which is the one thing a filing must never be. So it is a note that\n * names its author (the member, or the conversion), carries the CRM's own\n * id as `sourcePluginId` the way a sequence's entries carry Outreach's, and\n * names the campaign twice: by name in the body, by id in `campaignId`.\n */\n\n/** The kind every filing entry is filed as. */\nexport const CAMPAIGN_FILING_ACTIVITY_KIND = 'note' as const\n\n/** What the entry says was done: put in the campaign, or taken out of it. */\nexport type CampaignFilingAction = 'filed' | 'removed'\n\n/** A campaign as a filing entry names it: the container's id and its name at the time. */\nexport interface CampaignFilingRef {\n id: string\n name: string\n}\n\n/**\n * The author a conversion's carry signs its entries with: nobody in the\n * room typed it, and \"A team member\" — the timeline's word for an\n * anonymous entry — would credit a person with what the conversion did.\n */\nexport const CAMPAIGN_FILING_CARRY_BY_NAME = 'Lead conversion'\n\n/** The one line of the entry, as the timeline shows it. */\nexport function campaignFilingBody(action: CampaignFilingAction, campaignName: string): string {\n const name = campaignName.trim() || 'a campaign'\n return action === 'filed' ? `Filed under ${name}` : `Removed from ${name}`\n}\n\n/**\n * The membership a save changed, as the entries it owes: one `filed` per\n * id the save added, one `removed` per id it dropped. Order-insensitive,\n * like `
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/campaign-filing-activity.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { CrmActivity, CrmActivityLink, ScopeToken } from '@aglyn/aglyn'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * WHAT A FILING WRITES ON THE RECORD'S TIMELINE (AGL-3274).\n *\n * A lead or a contact filed under a campaign — from its own page, the bulk\n * bar, the New lead drawer, or by conversion carrying a lead's campaigns\n * onto the contact it became — showed nothing of it in Activity, so the\n * timeline read \"email sent\" with no line for the act that put the person\n * in the campaign in the first place. Every door now files one entry per\n * campaign changed, and this module is the one place their shape lives:\n * the client hook and the server helper both build the document here, so\n * an entry filed from the page and one filed by the conversion read alike.\n *\n * ## A `note`, not a kind of its own\n *\n * The timeline's kinds are what a PERSON logs — call, email, meeting, note\n * — and a filing is bookkeeping beside them, the way a sequence's \"Enrolled\n * in\" entry is. A `filing` kind would need its own label, glyph, report\n * column and a place in the log dialog where a member could log one by\n * hand, which is the one thing a filing must never be. So it is a note that\n * names its author (the member, or the conversion), carries the CRM's own\n * id as `sourcePluginId` the way a sequence's entries carry Outreach's, and\n * names the campaign twice: by name in the body, by id in `campaignId`.\n */\n\n/** The kind every filing entry is filed as. */\nexport const CAMPAIGN_FILING_ACTIVITY_KIND = 'note' as const\n\n/** What the entry says was done: put in the campaign, or taken out of it. */\nexport type CampaignFilingAction = 'filed' | 'removed'\n\n/** A campaign as a filing entry names it: the container's id and its name at the time. */\nexport interface CampaignFilingRef {\n id: string\n name: string\n}\n\n/**\n * The author a conversion's carry signs its entries with: nobody in the\n * room typed it, and \"A team member\" — the timeline's word for an\n * anonymous entry — would credit a person with what the conversion did.\n */\nexport const CAMPAIGN_FILING_CARRY_BY_NAME = 'Lead conversion'\n\n/** The one line of the entry, as the timeline shows it. */\nexport function campaignFilingBody(action: CampaignFilingAction, campaignName: string): string {\n const name = campaignName.trim() || 'a campaign'\n return action === 'filed' ? `Filed under ${name}` : `Removed from ${name}`\n}\n\n/**\n * The membership a save changed, as the entries it owes: one `filed` per\n * id the save added, one `removed` per id it dropped. Order-insensitive,\n * like `containerMembershipUnchanged`; a reorder owes nothing.\n */\nexport function campaignFilingChanges(\n before: readonly string[],\n after: readonly string[],\n): { added: string[]; removed: string[] } {\n return {\n added: after.filter((id) => !before.includes(id)),\n removed: before.filter((id) => !after.includes(id)),\n }\n}\n\nexport interface CampaignFilingActivityInput {\n action: CampaignFilingAction\n campaign: CampaignFilingRef\n link: CrmActivityLink\n /** The site the entry carries as provenance — the lead's site, the contact's viewing site. */\n hostId: string\n visibleTo: readonly ScopeToken[]\n /** When it happened, epoch ms. */\n atMs: number\n /** The member who did it, or `''` for the conversion. */\n byUid: string\n /** The member's name as it read, or the carry's fixed author; absent rather than blank. */\n byName?: string | null\n}\n\n/** The document a filing entry is stored as, minus the timestamps the writer stamps. */\nexport function buildCampaignFilingActivity(input: CampaignFilingActivityInput): CrmActivity {\n const byName = String(input.byName ?? '').trim()\n return {\n kind: CAMPAIGN_FILING_ACTIVITY_KIND,\n body: campaignFilingBody(input.action, input.campaign.name),\n atMs: input.atMs,\n byUid: input.byUid,\n ...(byName ? { byName } : {}),\n ...(input.link.contactId ? { contactId: input.link.contactId } : {}),\n ...(input.link.companyId ? { companyId: input.link.companyId } : {}),\n ...(input.link.dealId ? { dealId: input.link.dealId } : {}),\n ...(input.link.leadId ? { leadId: input.link.leadId } : {}),\n hostId: input.hostId,\n visibleTo: [...input.visibleTo],\n sourcePluginId: BUNDLE_ID,\n campaignId: input.campaign.id,\n }\n}\n"],"names":["BUNDLE_ID","CAMPAIGN_FILING_ACTIVITY_KIND","CAMPAIGN_FILING_CARRY_BY_NAME","campaignFilingBody","action","campaignName","name","trim","campaignFilingChanges","before","after","added","filter","id","includes","removed","buildCampaignFilingActivity","input","byName","String","kind","body","campaign","atMs","byUid","link","contactId","companyId","dealId","leadId","hostId","visibleTo","sourcePluginId","campaignId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;;;;;;;;;;;;;;;CAsBC,GAED,6CAA6C,GAC7C,OAAO,MAAMC,gCAAgC,OAAe;AAW5D;;;;CAIC,GACD,OAAO,MAAMC,gCAAgC,kBAAiB;AAE9D,yDAAyD,GACzD,OAAO,SAASC,mBAAmBC,MAA4B,EAAEC,YAAoB;IACnF,MAAMC,OAAOD,aAAaE,IAAI,MAAM;IACpC,OAAOH,WAAW,UAAU,CAAC,YAAY,EAAEE,MAAM,GAAG,CAAC,aAAa,EAAEA,MAAM;AAC5E;AAEA;;;;CAIC,GACD,OAAO,SAASE,sBACdC,MAAyB,EACzBC,KAAwB;IAExB,OAAO;QACLC,OAAOD,MAAME,MAAM,CAAC,CAACC,KAAO,CAACJ,OAAOK,QAAQ,CAACD;QAC7CE,SAASN,OAAOG,MAAM,CAAC,CAACC,KAAO,CAACH,MAAMI,QAAQ,CAACD;IACjD;AACF;AAiBA,sFAAsF,GACtF,OAAO,SAASG,4BAA4BC,KAAkC;QACtDA;IAAtB,MAAMC,SAASC,QAAOF,gBAAAA,MAAMC,MAAM,YAAZD,gBAAgB,IAAIV,IAAI;IAC9C,OAAO;QACLa,MAAMnB;QACNoB,MAAMlB,mBAAmBc,MAAMb,MAAM,EAAEa,MAAMK,QAAQ,CAAChB,IAAI;QAC1DiB,MAAMN,MAAMM,IAAI;QAChBC,OAAOP,MAAMO,KAAK;OACdN,SAAS;QAAEA;IAAO,IAAI,CAAC,GACvBD,MAAMQ,IAAI,CAACC,SAAS,GAAG;QAAEA,WAAWT,MAAMQ,IAAI,CAACC,SAAS;IAAC,IAAI,CAAC,GAC9DT,MAAMQ,IAAI,CAACE,SAAS,GAAG;QAAEA,WAAWV,MAAMQ,IAAI,CAACE,SAAS;IAAC,IAAI,CAAC,GAC9DV,MAAMQ,IAAI,CAACG,MAAM,GAAG;QAAEA,QAAQX,MAAMQ,IAAI,CAACG,MAAM;IAAC,IAAI,CAAC,GACrDX,MAAMQ,IAAI,CAACI,MAAM,GAAG;QAAEA,QAAQZ,MAAMQ,IAAI,CAACI,MAAM;IAAC,IAAI,CAAC;QACzDC,QAAQb,MAAMa,MAAM;QACpBC,WAAW;eAAId,MAAMc,SAAS;SAAC;QAC/BC,gBAAgBhC;QAChBiC,YAAYhB,MAAMK,QAAQ,CAACT,EAAE;;AAEjC"}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* because it is what a merchant reads the file for, and maps to "Do not
|
|
29
29
|
* import" on the way back.
|
|
30
30
|
*/
|
|
31
|
-
export { COMPANY_CSV_COLUMNS, companiesCsv, type CompanyCsvOptions, type CompanyCsvRow, } from '
|
|
31
|
+
export { COMPANY_CSV_COLUMNS, companiesCsv, type CompanyCsvOptions, type CompanyCsvRow, } from './crm-csv';
|
|
32
32
|
/**
|
|
33
33
|
* The file the Import drawer hands out to start from: the export's header
|
|
34
34
|
* and nothing under it.
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* column the import has no field for, the contacts count, is written last
|
|
27
27
|
* because it is what a merchant reads the file for, and maps to "Do not
|
|
28
28
|
* import" on the way back.
|
|
29
|
-
*/ /* The writer
|
|
30
|
-
import { companiesCsv } from "
|
|
29
|
+
*/ /* The writer is `crm-csv.ts`, shared with the server's export — see `deals-csv.ts`. */ export { COMPANY_CSV_COLUMNS, companiesCsv } from "./crm-csv.js";
|
|
30
|
+
import { companiesCsv } from "./crm-csv.js";
|
|
31
31
|
/**
|
|
32
32
|
* The file the Import drawer hands out to start from: the export's header
|
|
33
33
|
* and nothing under it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/companies-csv.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The companies CSV — one file from the table's Export button and the bulk\n * bar's (AGL-2621).\n *\n * The same rule the contacts file follows: the header is the companies\n * IMPORT's own vocabulary — the first alias of every field\n * `guessCompanyImportMapping` reads — so an export re-imports without a\n * hand mapping, and the template the Import drawer hands out IS this header\n * over no rows. The owner is written by ADDRESS, because the import resolves\n * an owner by email and a uid is nothing a spreadsheet can read. The one\n * column the import has no field for, the contacts count, is written last\n * because it is what a merchant reads the file for, and maps to \"Do not\n * import\" on the way back.\n */\n\n/* The writer
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/companies-csv.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The companies CSV — one file from the table's Export button and the bulk\n * bar's (AGL-2621).\n *\n * The same rule the contacts file follows: the header is the companies\n * IMPORT's own vocabulary — the first alias of every field\n * `guessCompanyImportMapping` reads — so an export re-imports without a\n * hand mapping, and the template the Import drawer hands out IS this header\n * over no rows. The owner is written by ADDRESS, because the import resolves\n * an owner by email and a uid is nothing a spreadsheet can read. The one\n * column the import has no field for, the contacts count, is written last\n * because it is what a merchant reads the file for, and maps to \"Do not\n * import\" on the way back.\n */\n\n/* The writer is `crm-csv.ts`, shared with the server's export — see `deals-csv.ts`. */\nexport {\n COMPANY_CSV_COLUMNS,\n companiesCsv,\n type CompanyCsvOptions,\n type CompanyCsvRow,\n} from './crm-csv'\n\nimport { companiesCsv } from './crm-csv'\n\n/**\n * The file the Import drawer hands out to start from: the export's header\n * and nothing under it.\n */\nexport function companyImportTemplateCsv(): string {\n return companiesCsv([])\n}\n"],"names":["COMPANY_CSV_COLUMNS","companiesCsv","companyImportTemplateCsv"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;CAaC,GAED,qFAAqF,GACrF,SACEA,mBAAmB,EACnBC,YAAY,QAGP,eAAW;AAElB,SAASA,YAAY,QAAQ,eAAW;AAExC;;;CAGC,GACD,OAAO,SAASC;IACd,OAAOD,aAAa,EAAE;AACxB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
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 ConsentGroup } from '@aglyn/aglyn/app-utils/consent-groups';
|
|
18
|
+
/**
|
|
19
|
+
* The holder of nothing: the group a contact is flattened through when no
|
|
20
|
+
* site has captured it and no facet exists — a row written by a path that
|
|
21
|
+
* predates attribution, read at the org level (AGL-2630).
|
|
22
|
+
*
|
|
23
|
+
* An empty `groupId` reads an empty facet (`readContactFacet` answers the
|
|
24
|
+
* empty facet for a key the map lacks) and REFUSES a write
|
|
25
|
+
* (`contactFacetPath` throws on an empty holder), which is the right pair:
|
|
26
|
+
* the row still shows its shared identity, and nothing edits a facet
|
|
27
|
+
* nobody holds. Exported so a surface can test for it rather than for an
|
|
28
|
+
* empty string.
|
|
29
|
+
*/
|
|
30
|
+
export declare const NO_HOLDER_GROUP: ConsentGroup;
|
|
31
|
+
/**
|
|
32
|
+
* The group a LEAD is read and acted on through (AGL-3278).
|
|
33
|
+
*
|
|
34
|
+
* The same question {@link contactPrimaryGroup} answers, with a shorter
|
|
35
|
+
* answer: a lead is one flat document — no facets, one profile, one
|
|
36
|
+
* status — so there is nothing to choose between and the holder is simply
|
|
37
|
+
* the site that captured the person first. What it is FOR is the same: at
|
|
38
|
+
* the organization level the record page has no viewing site, and it needs
|
|
39
|
+
* one to read the consent basis against, to file an activity under, to
|
|
40
|
+
* offer a booking on and to convert as.
|
|
41
|
+
*
|
|
42
|
+
* A lead no site has captured — a row imported before attribution — reads
|
|
43
|
+
* through {@link NO_HOLDER_GROUP}, whose empty `hostId` is how a surface
|
|
44
|
+
* asks "is there a site here at all" without testing for an empty string.
|
|
45
|
+
*/
|
|
46
|
+
export declare function leadPrimaryGroup(lead: Record<string, unknown> | null | undefined, org: Record<string, unknown> | null | undefined): ConsentGroup;
|
|
47
|
+
/**
|
|
48
|
+
* The group a CROSS-HOLDER reader flattens one contact through (AGL-2630).
|
|
49
|
+
*
|
|
50
|
+
* Under a site there is one answer for every row — the site's own group,
|
|
51
|
+
* `consentGroupForHost` — and the reader never asks this. At the
|
|
52
|
+
* organization level there is no viewing site, and a contact captured by
|
|
53
|
+
* three sites has up to three facets; the org-wide member reading them all
|
|
54
|
+
* needs ONE to show the row's profile, owner and stage under, and to write
|
|
55
|
+
* an edit back to. This picks the person's PRIMARY holder: the first
|
|
56
|
+
* capturing site, in capture order, whose group actually holds a facet;
|
|
57
|
+
* then any facet at all, resolved back to its declared group or to the
|
|
58
|
+
* site whose id it is keyed by; then the first capturing site even with no
|
|
59
|
+
* facet yet (the facet is written on the first edit). A row nobody captured
|
|
60
|
+
* and nobody wrote a facet for reads through {@link NO_HOLDER_GROUP}.
|
|
61
|
+
*
|
|
62
|
+
* Capture order rather than sorted order, deliberately: `capturedByHostIds`
|
|
63
|
+
* is maintained by `arrayUnion`, so its first entry is the site that met
|
|
64
|
+
* the person first, and that site's profile is the one a person reading
|
|
65
|
+
* across brands would expect to see. `contactCaptureHostIds` sorts the same
|
|
66
|
+
* array for RENDERING, where a stable order matters more than a first.
|
|
67
|
+
*/
|
|
68
|
+
export declare function contactPrimaryGroup(row: Record<string, unknown> | null | undefined, org: Record<string, unknown> | null | undefined): ConsentGroup;
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
* WHICH HOLDER A CROSS-SITE READER SEES A CONTACT THROUGH (AGL-2630).
|
|
18
|
+
*
|
|
19
|
+
* A contact document carries one facet per holder, and every field but the
|
|
20
|
+
* shared identity lives in one of them. A surface standing on a site reads
|
|
21
|
+
* that site's group; a surface standing at the organization — the org-level
|
|
22
|
+
* Contacts list, and the whole-collection export that writes the same file
|
|
23
|
+
* — has no site to read as and needs one answer per row.
|
|
24
|
+
*
|
|
25
|
+
* In the library rather than in the CRM plugin because both of those
|
|
26
|
+
* readers need it and one of them is an app: an app may not import a
|
|
27
|
+
* feature plugin (AGL-2662).
|
|
28
|
+
*/ import { CONTACT_FACETS_FIELD } from "@aglyn/aglyn/app-utils/contacts";
|
|
29
|
+
import { CAPTURED_BY_HOST_FIELD } from "@aglyn/aglyn/app-utils/marketing-consent";
|
|
30
|
+
import { consentGroupForHost, consentGroupsAwaitConfirmation, readConsentGroups, soloConsentGroup } from "@aglyn/aglyn/app-utils/consent-groups";
|
|
31
|
+
/**
|
|
32
|
+
* The holder of nothing: the group a contact is flattened through when no
|
|
33
|
+
* site has captured it and no facet exists — a row written by a path that
|
|
34
|
+
* predates attribution, read at the org level (AGL-2630).
|
|
35
|
+
*
|
|
36
|
+
* An empty `groupId` reads an empty facet (`readContactFacet` answers the
|
|
37
|
+
* empty facet for a key the map lacks) and REFUSES a write
|
|
38
|
+
* (`contactFacetPath` throws on an empty holder), which is the right pair:
|
|
39
|
+
* the row still shows its shared identity, and nothing edits a facet
|
|
40
|
+
* nobody holds. Exported so a surface can test for it rather than for an
|
|
41
|
+
* empty string.
|
|
42
|
+
*/ export const NO_HOLDER_GROUP = Object.freeze({
|
|
43
|
+
hostId: '',
|
|
44
|
+
groupId: '',
|
|
45
|
+
name: null,
|
|
46
|
+
hostIds: [],
|
|
47
|
+
declared: false,
|
|
48
|
+
awaitsConfirmation: false
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* The sites that captured this person, in CAPTURE order — the order
|
|
52
|
+
* `arrayUnion` left, so the first entry is the site that met them first.
|
|
53
|
+
*
|
|
54
|
+
* `contactCaptureHostIds` sorts the same array for RENDERING, where a
|
|
55
|
+
* stable order matters more than a first. Both readers below want the
|
|
56
|
+
* first, so neither may use the sorted one.
|
|
57
|
+
*/ function captureOrder(record) {
|
|
58
|
+
const raw = (record != null ? record : {})[CAPTURED_BY_HOST_FIELD];
|
|
59
|
+
if (!Array.isArray(raw)) return [];
|
|
60
|
+
return raw.map((id)=>String(id != null ? id : '').trim()).filter(Boolean);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The group a LEAD is read and acted on through (AGL-3278).
|
|
64
|
+
*
|
|
65
|
+
* The same question {@link contactPrimaryGroup} answers, with a shorter
|
|
66
|
+
* answer: a lead is one flat document — no facets, one profile, one
|
|
67
|
+
* status — so there is nothing to choose between and the holder is simply
|
|
68
|
+
* the site that captured the person first. What it is FOR is the same: at
|
|
69
|
+
* the organization level the record page has no viewing site, and it needs
|
|
70
|
+
* one to read the consent basis against, to file an activity under, to
|
|
71
|
+
* offer a booking on and to convert as.
|
|
72
|
+
*
|
|
73
|
+
* A lead no site has captured — a row imported before attribution — reads
|
|
74
|
+
* through {@link NO_HOLDER_GROUP}, whose empty `hostId` is how a surface
|
|
75
|
+
* asks "is there a site here at all" without testing for an empty string.
|
|
76
|
+
*/ export function leadPrimaryGroup(lead, org) {
|
|
77
|
+
const captured = captureOrder(lead);
|
|
78
|
+
return captured.length ? consentGroupForHost(org, captured[0]) : NO_HOLDER_GROUP;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The group a CROSS-HOLDER reader flattens one contact through (AGL-2630).
|
|
82
|
+
*
|
|
83
|
+
* Under a site there is one answer for every row — the site's own group,
|
|
84
|
+
* `consentGroupForHost` — and the reader never asks this. At the
|
|
85
|
+
* organization level there is no viewing site, and a contact captured by
|
|
86
|
+
* three sites has up to three facets; the org-wide member reading them all
|
|
87
|
+
* needs ONE to show the row's profile, owner and stage under, and to write
|
|
88
|
+
* an edit back to. This picks the person's PRIMARY holder: the first
|
|
89
|
+
* capturing site, in capture order, whose group actually holds a facet;
|
|
90
|
+
* then any facet at all, resolved back to its declared group or to the
|
|
91
|
+
* site whose id it is keyed by; then the first capturing site even with no
|
|
92
|
+
* facet yet (the facet is written on the first edit). A row nobody captured
|
|
93
|
+
* and nobody wrote a facet for reads through {@link NO_HOLDER_GROUP}.
|
|
94
|
+
*
|
|
95
|
+
* Capture order rather than sorted order, deliberately: `capturedByHostIds`
|
|
96
|
+
* is maintained by `arrayUnion`, so its first entry is the site that met
|
|
97
|
+
* the person first, and that site's profile is the one a person reading
|
|
98
|
+
* across brands would expect to see. `contactCaptureHostIds` sorts the same
|
|
99
|
+
* array for RENDERING, where a stable order matters more than a first.
|
|
100
|
+
*/ export function contactPrimaryGroup(row, org) {
|
|
101
|
+
const record = row != null ? row : {};
|
|
102
|
+
const facets = record[CONTACT_FACETS_FIELD];
|
|
103
|
+
const facetKeys = facets && typeof facets === 'object' && !Array.isArray(facets) ? Object.keys(facets) : [];
|
|
104
|
+
const captured = captureOrder(record);
|
|
105
|
+
for (const hostId of captured){
|
|
106
|
+
const group = consentGroupForHost(org, hostId);
|
|
107
|
+
if (facetKeys.includes(group.groupId)) return group;
|
|
108
|
+
}
|
|
109
|
+
for (const key of facetKeys){
|
|
110
|
+
const declared = readConsentGroups(org)[key];
|
|
111
|
+
if (declared) {
|
|
112
|
+
return {
|
|
113
|
+
hostId: declared.hostIds[0],
|
|
114
|
+
groupId: key,
|
|
115
|
+
name: declared.name,
|
|
116
|
+
hostIds: [
|
|
117
|
+
...declared.hostIds
|
|
118
|
+
],
|
|
119
|
+
declared: true,
|
|
120
|
+
awaitsConfirmation: consentGroupsAwaitConfirmation(org)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return soloConsentGroup(key);
|
|
124
|
+
}
|
|
125
|
+
if (captured.length) return consentGroupForHost(org, captured[0]);
|
|
126
|
+
return NO_HOLDER_GROUP;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=contact-holder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/contact-holder.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 * WHICH HOLDER A CROSS-SITE READER SEES A CONTACT THROUGH (AGL-2630).\n *\n * A contact document carries one facet per holder, and every field but the\n * shared identity lives in one of them. A surface standing on a site reads\n * that site's group; a surface standing at the organization — the org-level\n * Contacts list, and the whole-collection export that writes the same file\n * — has no site to read as and needs one answer per row.\n *\n * In the library rather than in the CRM plugin because both of those\n * readers need it and one of them is an app: an app may not import a\n * feature plugin (AGL-2662).\n */\n\nimport { CONTACT_FACETS_FIELD } from '@aglyn/aglyn/app-utils/contacts'\nimport { CAPTURED_BY_HOST_FIELD } from '@aglyn/aglyn/app-utils/marketing-consent'\nimport {\n type ConsentGroup,\n consentGroupForHost,\n consentGroupsAwaitConfirmation,\n readConsentGroups,\n soloConsentGroup,\n} from '@aglyn/aglyn/app-utils/consent-groups'\n\n/**\n * The holder of nothing: the group a contact is flattened through when no\n * site has captured it and no facet exists — a row written by a path that\n * predates attribution, read at the org level (AGL-2630).\n *\n * An empty `groupId` reads an empty facet (`readContactFacet` answers the\n * empty facet for a key the map lacks) and REFUSES a write\n * (`contactFacetPath` throws on an empty holder), which is the right pair:\n * the row still shows its shared identity, and nothing edits a facet\n * nobody holds. Exported so a surface can test for it rather than for an\n * empty string.\n */\nexport const NO_HOLDER_GROUP: ConsentGroup = Object.freeze({\n hostId: '',\n groupId: '',\n name: null,\n hostIds: [],\n declared: false,\n awaitsConfirmation: false,\n}) as ConsentGroup\n\n/**\n * The sites that captured this person, in CAPTURE order — the order\n * `arrayUnion` left, so the first entry is the site that met them first.\n *\n * `contactCaptureHostIds` sorts the same array for RENDERING, where a\n * stable order matters more than a first. Both readers below want the\n * first, so neither may use the sorted one.\n */\nfunction captureOrder(\n record: Record<string, unknown> | null | undefined,\n): string[] {\n const raw = (record ?? {})[CAPTURED_BY_HOST_FIELD]\n if (!Array.isArray(raw)) return []\n return raw.map((id) => String(id ?? '').trim()).filter(Boolean)\n}\n\n/**\n * The group a LEAD is read and acted on through (AGL-3278).\n *\n * The same question {@link contactPrimaryGroup} answers, with a shorter\n * answer: a lead is one flat document — no facets, one profile, one\n * status — so there is nothing to choose between and the holder is simply\n * the site that captured the person first. What it is FOR is the same: at\n * the organization level the record page has no viewing site, and it needs\n * one to read the consent basis against, to file an activity under, to\n * offer a booking on and to convert as.\n *\n * A lead no site has captured — a row imported before attribution — reads\n * through {@link NO_HOLDER_GROUP}, whose empty `hostId` is how a surface\n * asks \"is there a site here at all\" without testing for an empty string.\n */\nexport function leadPrimaryGroup(\n lead: Record<string, unknown> | null | undefined,\n org: Record<string, unknown> | null | undefined,\n): ConsentGroup {\n const captured = captureOrder(lead)\n return captured.length ? consentGroupForHost(org, captured[0]) : NO_HOLDER_GROUP\n}\n\n/**\n * The group a CROSS-HOLDER reader flattens one contact through (AGL-2630).\n *\n * Under a site there is one answer for every row — the site's own group,\n * `consentGroupForHost` — and the reader never asks this. At the\n * organization level there is no viewing site, and a contact captured by\n * three sites has up to three facets; the org-wide member reading them all\n * needs ONE to show the row's profile, owner and stage under, and to write\n * an edit back to. This picks the person's PRIMARY holder: the first\n * capturing site, in capture order, whose group actually holds a facet;\n * then any facet at all, resolved back to its declared group or to the\n * site whose id it is keyed by; then the first capturing site even with no\n * facet yet (the facet is written on the first edit). A row nobody captured\n * and nobody wrote a facet for reads through {@link NO_HOLDER_GROUP}.\n *\n * Capture order rather than sorted order, deliberately: `capturedByHostIds`\n * is maintained by `arrayUnion`, so its first entry is the site that met\n * the person first, and that site's profile is the one a person reading\n * across brands would expect to see. `contactCaptureHostIds` sorts the same\n * array for RENDERING, where a stable order matters more than a first.\n */\nexport function contactPrimaryGroup(\n row: Record<string, unknown> | null | undefined,\n org: Record<string, unknown> | null | undefined,\n): ConsentGroup {\n const record = row ?? {}\n const facets = record[CONTACT_FACETS_FIELD]\n const facetKeys =\n facets && typeof facets === 'object' && !Array.isArray(facets)\n ? Object.keys(facets as Record<string, unknown>)\n : []\n const captured = captureOrder(record)\n for (const hostId of captured) {\n const group = consentGroupForHost(org, hostId)\n if (facetKeys.includes(group.groupId)) return group\n }\n for (const key of facetKeys) {\n const declared = readConsentGroups(org)[key]\n if (declared) {\n return {\n hostId: declared.hostIds[0],\n groupId: key,\n name: declared.name,\n hostIds: [...declared.hostIds],\n declared: true,\n awaitsConfirmation: consentGroupsAwaitConfirmation(org),\n }\n }\n return soloConsentGroup(key)\n }\n if (captured.length) return consentGroupForHost(org, captured[0])\n return NO_HOLDER_GROUP\n}"],"names":["CONTACT_FACETS_FIELD","CAPTURED_BY_HOST_FIELD","consentGroupForHost","consentGroupsAwaitConfirmation","readConsentGroups","soloConsentGroup","NO_HOLDER_GROUP","Object","freeze","hostId","groupId","name","hostIds","declared","awaitsConfirmation","captureOrder","record","raw","Array","isArray","map","id","String","trim","filter","Boolean","leadPrimaryGroup","lead","org","captured","length","contactPrimaryGroup","row","facets","facetKeys","keys","group","includes","key"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;CAYC,GAED,SAASA,oBAAoB,QAAQ,kCAAiC;AACtE,SAASC,sBAAsB,QAAQ,2CAA0C;AACjF,SAEEC,mBAAmB,EACnBC,8BAA8B,EAC9BC,iBAAiB,EACjBC,gBAAgB,QACX,wCAAuC;AAE9C;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,kBAAgCC,OAAOC,MAAM,CAAC;IACzDC,QAAQ;IACRC,SAAS;IACTC,MAAM;IACNC,SAAS,EAAE;IACXC,UAAU;IACVC,oBAAoB;AACtB,GAAkB;AAElB;;;;;;;CAOC,GACD,SAASC,aACPC,MAAkD;IAElD,MAAMC,MAAM,CAACD,iBAAAA,SAAU,CAAC,EAAE,CAACf,uBAAuB;IAClD,IAAI,CAACiB,MAAMC,OAAO,CAACF,MAAM,OAAO,EAAE;IAClC,OAAOA,IAAIG,GAAG,CAAC,CAACC,KAAOC,OAAOD,aAAAA,KAAM,IAAIE,IAAI,IAAIC,MAAM,CAACC;AACzD;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,iBACdC,IAAgD,EAChDC,GAA+C;IAE/C,MAAMC,WAAWd,aAAaY;IAC9B,OAAOE,SAASC,MAAM,GAAG5B,oBAAoB0B,KAAKC,QAAQ,CAAC,EAAE,IAAIvB;AACnE;AAEA;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASyB,oBACdC,GAA+C,EAC/CJ,GAA+C;IAE/C,MAAMZ,SAASgB,cAAAA,MAAO,CAAC;IACvB,MAAMC,SAASjB,MAAM,CAAChB,qBAAqB;IAC3C,MAAMkC,YACJD,UAAU,OAAOA,WAAW,YAAY,CAACf,MAAMC,OAAO,CAACc,UACnD1B,OAAO4B,IAAI,CAACF,UACZ,EAAE;IACR,MAAMJ,WAAWd,aAAaC;IAC9B,KAAK,MAAMP,UAAUoB,SAAU;QAC7B,MAAMO,QAAQlC,oBAAoB0B,KAAKnB;QACvC,IAAIyB,UAAUG,QAAQ,CAACD,MAAM1B,OAAO,GAAG,OAAO0B;IAChD;IACA,KAAK,MAAME,OAAOJ,UAAW;QAC3B,MAAMrB,WAAWT,kBAAkBwB,IAAI,CAACU,IAAI;QAC5C,IAAIzB,UAAU;YACZ,OAAO;gBACLJ,QAAQI,SAASD,OAAO,CAAC,EAAE;gBAC3BF,SAAS4B;gBACT3B,MAAME,SAASF,IAAI;gBACnBC,SAAS;uBAAIC,SAASD,OAAO;iBAAC;gBAC9BC,UAAU;gBACVC,oBAAoBX,+BAA+ByB;YACrD;QACF;QACA,OAAOvB,iBAAiBiC;IAC1B;IACA,IAAIT,SAASC,MAAM,EAAE,OAAO5B,oBAAoB0B,KAAKC,QAAQ,CAAC,EAAE;IAChE,OAAOvB;AACT"}
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { ContactInteraction, ContactSource } from '@aglyn/aglyn';
|
|
18
|
+
import { type PluginRecordRouteContext } from '@aglyn/aglyn/plugin-manager/plugin-record-routes';
|
|
19
|
+
/**
|
|
20
|
+
* Where a contact's captured history points back at (AGL-2622): the record
|
|
21
|
+
* each capture door left, where the console reads it.
|
|
22
|
+
*
|
|
23
|
+
* The submission, the order and the booking are other plugins' records, so
|
|
24
|
+
* their addresses are the ones those plugins publish on the record-route seam
|
|
25
|
+
* (`formSubmission`, `order`, `booking` — AGL-3080), and an entry whose owner
|
|
26
|
+
* is not loaded carries no link. A member sign-up lands on the platform's own
|
|
27
|
+
* Users page.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* What the link on a captured timeline entry says. Only the doors that
|
|
31
|
+
* leave a record the console can open are named; a newsletter opt-in, an
|
|
32
|
+
* API create, an import or a by-hand add leave nothing to open, so they
|
|
33
|
+
* carry no link and no label.
|
|
34
|
+
*/
|
|
35
|
+
export declare const INTERACTION_LINK_LABELS: Partial<Record<ContactSource, string>>;
|
|
36
|
+
/**
|
|
37
|
+
* The console page a captured interaction points back at, or `null` when
|
|
38
|
+
* the door left nothing to open, or its owner publishes no address here.
|
|
39
|
+
*
|
|
40
|
+
* A submission and an order are addressed by the `refId` the capture door
|
|
41
|
+
* stamped — `formSubmissions/{id}`, `orders/{id}` — and an interaction that
|
|
42
|
+
* predates the stamp lands on nothing rather than on a list that would read
|
|
43
|
+
* as "the record is gone". A booking and a member sign-up land on their
|
|
44
|
+
* pages, which list them; neither page addresses one row.
|
|
45
|
+
*/
|
|
46
|
+
export declare function contactInteractionHref(interaction: Pick<ContactInteraction, 'type' | 'refId'>, context: PluginRecordRouteContext): string | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
*/ import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
|
|
17
|
+
import { pluginRecordHref, pluginRecordListHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
|
|
18
|
+
/**
|
|
19
|
+
* Where a contact's captured history points back at (AGL-2622): the record
|
|
20
|
+
* each capture door left, where the console reads it.
|
|
21
|
+
*
|
|
22
|
+
* The submission, the order and the booking are other plugins' records, so
|
|
23
|
+
* their addresses are the ones those plugins publish on the record-route seam
|
|
24
|
+
* (`formSubmission`, `order`, `booking` — AGL-3080), and an entry whose owner
|
|
25
|
+
* is not loaded carries no link. A member sign-up lands on the platform's own
|
|
26
|
+
* Users page.
|
|
27
|
+
*/ /**
|
|
28
|
+
* What the link on a captured timeline entry says. Only the doors that
|
|
29
|
+
* leave a record the console can open are named; a newsletter opt-in, an
|
|
30
|
+
* API create, an import or a by-hand add leave nothing to open, so they
|
|
31
|
+
* carry no link and no label.
|
|
32
|
+
*/ export const INTERACTION_LINK_LABELS = {
|
|
33
|
+
form: 'Open submission',
|
|
34
|
+
order: 'Open order',
|
|
35
|
+
booking: 'Open bookings',
|
|
36
|
+
member: 'Open site users'
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The console page a captured interaction points back at, or `null` when
|
|
40
|
+
* the door left nothing to open, or its owner publishes no address here.
|
|
41
|
+
*
|
|
42
|
+
* A submission and an order are addressed by the `refId` the capture door
|
|
43
|
+
* stamped — `formSubmissions/{id}`, `orders/{id}` — and an interaction that
|
|
44
|
+
* predates the stamp lands on nothing rather than on a list that would read
|
|
45
|
+
* as "the record is gone". A booking and a member sign-up land on their
|
|
46
|
+
* pages, which list them; neither page addresses one row.
|
|
47
|
+
*/ export function contactInteractionHref(interaction, context) {
|
|
48
|
+
switch(interaction.type){
|
|
49
|
+
case 'form':
|
|
50
|
+
return interaction.refId ? pluginRecordHref('formSubmission', context, interaction.refId) : null;
|
|
51
|
+
case 'order':
|
|
52
|
+
return interaction.refId ? pluginRecordHref('order', context, interaction.refId) : null;
|
|
53
|
+
case 'booking':
|
|
54
|
+
return pluginRecordListHref('booking', context);
|
|
55
|
+
case 'member':
|
|
56
|
+
return context.host ? buildRoute(Route.HOST_USERS, {
|
|
57
|
+
orgSlug: context.orgSlug,
|
|
58
|
+
host: context.host
|
|
59
|
+
}) : null;
|
|
60
|
+
default:
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=contact-interaction-links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/contact-interaction-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ContactInteraction, ContactSource } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n type PluginRecordRouteContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\n\n/**\n * Where a contact's captured history points back at (AGL-2622): the record\n * each capture door left, where the console reads it.\n *\n * The submission, the order and the booking are other plugins' records, so\n * their addresses are the ones those plugins publish on the record-route seam\n * (`formSubmission`, `order`, `booking` — AGL-3080), and an entry whose owner\n * is not loaded carries no link. A member sign-up lands on the platform's own\n * Users page.\n */\n\n/**\n * What the link on a captured timeline entry says. Only the doors that\n * leave a record the console can open are named; a newsletter opt-in, an\n * API create, an import or a by-hand add leave nothing to open, so they\n * carry no link and no label.\n */\nexport const INTERACTION_LINK_LABELS: Partial<Record<ContactSource, string>> = {\n form: 'Open submission',\n order: 'Open order',\n booking: 'Open bookings',\n member: 'Open site users',\n}\n\n/**\n * The console page a captured interaction points back at, or `null` when\n * the door left nothing to open, or its owner publishes no address here.\n *\n * A submission and an order are addressed by the `refId` the capture door\n * stamped — `formSubmissions/{id}`, `orders/{id}` — and an interaction that\n * predates the stamp lands on nothing rather than on a list that would read\n * as \"the record is gone\". A booking and a member sign-up land on their\n * pages, which list them; neither page addresses one row.\n */\nexport function contactInteractionHref(\n interaction: Pick<ContactInteraction, 'type' | 'refId'>,\n context: PluginRecordRouteContext,\n): string | null {\n switch (interaction.type) {\n case 'form':\n return interaction.refId ? pluginRecordHref('formSubmission', context, interaction.refId) : null\n case 'order':\n return interaction.refId ? pluginRecordHref('order', context, interaction.refId) : null\n case 'booking':\n return pluginRecordListHref('booking', context)\n case 'member':\n return context.host\n ? buildRoute(Route.HOST_USERS, { orgSlug: context.orgSlug, host: context.host })\n : null\n default:\n return null\n }\n}\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","INTERACTION_LINK_LABELS","form","order","booking","member","contactInteractionHref","interaction","context","type","refId","host","HOST_USERS","orgSlug"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SACEC,gBAAgB,EAChBC,oBAAoB,QAEf,mDAAkD;AAEzD;;;;;;;;;CASC,GAED;;;;;CAKC,GACD,OAAO,MAAMC,0BAAkE;IAC7EC,MAAM;IACNC,OAAO;IACPC,SAAS;IACTC,QAAQ;AACV,EAAC;AAED;;;;;;;;;CASC,GACD,OAAO,SAASC,uBACdC,WAAuD,EACvDC,OAAiC;IAEjC,OAAQD,YAAYE,IAAI;QACtB,KAAK;YACH,OAAOF,YAAYG,KAAK,GAAGX,iBAAiB,kBAAkBS,SAASD,YAAYG,KAAK,IAAI;QAC9F,KAAK;YACH,OAAOH,YAAYG,KAAK,GAAGX,iBAAiB,SAASS,SAASD,YAAYG,KAAK,IAAI;QACrF,KAAK;YACH,OAAOV,qBAAqB,WAAWQ;QACzC,KAAK;YACH,OAAOA,QAAQG,IAAI,GACfd,WAAWC,MAAMc,UAAU,EAAE;gBAAEC,SAASL,QAAQK,OAAO;gBAAEF,MAAMH,QAAQG,IAAI;YAAC,KAC5E;QACN;YACE,OAAO;IACX;AACF"}
|