@aglyn/plugins-crm 1.0.0-beta.218 → 1.0.0-beta.219
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/package.json +14 -13
- 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-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-export-all-button.d.ts +1 -1
- package/src/lib/components/crm-export-all-button.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/deals-bulk-bar.js +13 -5
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.js +6 -0
- 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 +6 -0
- package/src/lib/components/lead-properties-card.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 +28 -3
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +36 -7
- package/src/lib/components/leads-section.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/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/declarations.console-server.d.ts +37 -0
- package/src/lib/declarations.console-server.js +103 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.server.d.ts +6 -0
- package/src/lib/declarations.server.js +33 -0
- 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-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/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 +6 -5
- 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 +185 -0
- package/src/lib/model/crm-digest.js +272 -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-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-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 +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 +3 -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 +832 -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/consent-group-participant.js +1 -1
- package/src/lib/server/consent-group-participant.js.map +1 -1
- 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 +2 -1
- 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/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-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 +107 -0
- package/src/lib/server/daily-digest-route.js +588 -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-route.d.ts +74 -0
- package/src/lib/server/inbound-route.js +257 -0
- package/src/lib/server/inbound-route.js.map +1 -0
- 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/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-facts.js +2 -1
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/task-reminders-route.d.ts +82 -0
- package/src/lib/server/task-reminders-route.js +497 -0
- package/src/lib/server/task-reminders-route.js.map +1 -0
- 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 +68 -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 @@
|
|
|
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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
* MERGING TWO CONTACT DOCUMENTS INTO ONE (AGL-2625) — the pure half.
|
|
19
|
+
*
|
|
20
|
+
* Contacts are keyed by email, so exact duplicates cannot exist; one human
|
|
21
|
+
* with `jane@acme.com` and `jane@gmail.com` is two documents, with tags,
|
|
22
|
+
* deals, tasks and a timeline split between them. A merge folds the second
|
|
23
|
+
* into the first: the SURVIVOR keeps its address as the identity, the MERGED
|
|
24
|
+
* record's address becomes an alternate on it, and every field is combined
|
|
25
|
+
* by one rule the dialog can show before the write happens.
|
|
26
|
+
*
|
|
27
|
+
* ## The rule
|
|
28
|
+
*
|
|
29
|
+
* - A scalar — a name, a phone, a title, a stage, an owner — is the
|
|
30
|
+
* survivor's where the survivor has one, and fills from the merged record
|
|
31
|
+
* where the survivor is empty. Nothing the survivor holds is overwritten.
|
|
32
|
+
* - A list — tags, campaign filings, the timeline, the sites that captured
|
|
33
|
+
* the person, the forms, the companies — is the UNION, deduplicated and
|
|
34
|
+
* bounded where the write paths bound it.
|
|
35
|
+
* - A figure that counts something — orders, lifetime value, refunds — is
|
|
36
|
+
* the SUM: two records of one person's purchases are one person's
|
|
37
|
+
* purchases. A "first" is the earlier, a "last" is the later.
|
|
38
|
+
* - Notes are appended, survivor first: a note is a list of sentences, and
|
|
39
|
+
* dropping the merged record's would be the one silent loss in a merge
|
|
40
|
+
* that is otherwise lossless.
|
|
41
|
+
* - A recorded REFUSAL of marketing mail on either record stands on the
|
|
42
|
+
* survivor. The merge asserts the two are one person, and that person
|
|
43
|
+
* said no.
|
|
44
|
+
*
|
|
45
|
+
* Applied per holder: each group's facet on the merged record is folded
|
|
46
|
+
* into the same group's facet on the survivor, and a group only the merged
|
|
47
|
+
* record had comes across whole. No holder's records reach another holder's
|
|
48
|
+
* facet, which is the guarantee the facet shape exists for.
|
|
49
|
+
*
|
|
50
|
+
* ## Why this is a plan and not a write
|
|
51
|
+
*
|
|
52
|
+
* The console shows the two records side by side and says which value wins
|
|
53
|
+
* before anything is committed; the server performs the write. Both read
|
|
54
|
+
* this function, so the preview cannot show one outcome and the write land
|
|
55
|
+
* another. The plan is plain values — no sentinels — so it can be rendered,
|
|
56
|
+
* and it is shaped for a merge-`set`, which deep-merges the facet map one
|
|
57
|
+
* holder at a time and replaces an array whole: exactly the two behaviors
|
|
58
|
+
* the rule above needs.
|
|
59
|
+
*/
|
|
60
|
+
import { type ContactInteraction } from '@aglyn/aglyn/app-utils/contacts';
|
|
61
|
+
export interface ContactMergePlan {
|
|
62
|
+
/**
|
|
63
|
+
* What the survivor becomes, as a merge-`set` payload: every derived
|
|
64
|
+
* field with its full value, and only the groups the merged record held
|
|
65
|
+
* under `facets`. Plain values throughout — the writer adds its own
|
|
66
|
+
* `updatedAt`.
|
|
67
|
+
*/
|
|
68
|
+
survivor: Record<string, unknown>;
|
|
69
|
+
/** Every address the survivor answers to afterwards, primary first. */
|
|
70
|
+
emails: string[];
|
|
71
|
+
/** The merged record's addresses — what its leads and index entries are keyed by. */
|
|
72
|
+
mergedEmails: string[];
|
|
73
|
+
/**
|
|
74
|
+
* The companies that lose a contact: those BOTH records were filed under,
|
|
75
|
+
* which counted two people and now count one. A company only one record
|
|
76
|
+
* named keeps its count — the survivor still names it.
|
|
77
|
+
*/
|
|
78
|
+
companyCounts: Array<{
|
|
79
|
+
companyId: string;
|
|
80
|
+
delta: -1;
|
|
81
|
+
}>;
|
|
82
|
+
/** The union of both records' scope tokens — who may read the survivor. */
|
|
83
|
+
visibleTo: string[];
|
|
84
|
+
}
|
|
85
|
+
type Doc = Record<string, unknown>;
|
|
86
|
+
/**
|
|
87
|
+
* Both timelines as one: deduplicated by event, newest first, and capped
|
|
88
|
+
* the way every capture caps it — so a merge of two full timelines keeps
|
|
89
|
+
* the fifty most recent visits rather than growing the document past what
|
|
90
|
+
* a capture may write.
|
|
91
|
+
*/
|
|
92
|
+
export declare function mergeInteractions(survivor: readonly ContactInteraction[] | undefined, merged: readonly ContactInteraction[] | undefined): ContactInteraction[];
|
|
93
|
+
/**
|
|
94
|
+
* One holder's facet after the merge.
|
|
95
|
+
*
|
|
96
|
+
* `survivor` is that holder's facet on the surviving record — possibly
|
|
97
|
+
* absent, when only the merged record was held by this group — and `merged`
|
|
98
|
+
* the same holder's facet on the record being folded in.
|
|
99
|
+
*/
|
|
100
|
+
export declare function mergeContactFacet(survivor: Doc | undefined, merged: Doc | undefined): Doc;
|
|
101
|
+
/**
|
|
102
|
+
* The plan for folding `merged` into `survivor`.
|
|
103
|
+
*
|
|
104
|
+
* Both are the stored documents as read. The survivor's `email` is the
|
|
105
|
+
* identity that stands; the merged record's becomes an alternate.
|
|
106
|
+
*/
|
|
107
|
+
export declare function planContactMerge(survivor: Doc, merged: Doc): ContactMergePlan;
|
|
108
|
+
/** Where a previewed value comes from. */
|
|
109
|
+
export type ContactMergeSource = 'survivor' | 'merged' | 'both' | 'none';
|
|
110
|
+
/** One line of the side-by-side the dialog shows. */
|
|
111
|
+
export interface ContactMergePreviewRow {
|
|
112
|
+
key: string;
|
|
113
|
+
label: string;
|
|
114
|
+
/** The surviving record's value, as text. */
|
|
115
|
+
survivor: string;
|
|
116
|
+
/** The merged record's value, as text. */
|
|
117
|
+
merged: string;
|
|
118
|
+
/** What the survivor carries afterwards. */
|
|
119
|
+
result: string;
|
|
120
|
+
from: ContactMergeSource;
|
|
121
|
+
}
|
|
122
|
+
export interface ContactMergePreviewOptions {
|
|
123
|
+
/** The owner's display name for a uid; the uid itself when absent. */
|
|
124
|
+
memberName?: (uid: string) => string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The merge as the viewing group would see it, field by field.
|
|
128
|
+
*
|
|
129
|
+
* Reads through ONE group's facet, the way the record page does, so a
|
|
130
|
+
* holder previewing a merge sees its own values and never another holder's.
|
|
131
|
+
* Every row carries the survivor's value, the merged record's, and the
|
|
132
|
+
* result the plan would write, so the dialog is a rendering and not a second
|
|
133
|
+
* opinion.
|
|
134
|
+
*/
|
|
135
|
+
export declare function contactMergePreview(survivor: Doc, merged: Doc, groupId: string, options?: ContactMergePreviewOptions): ContactMergePreviewRow[];
|
|
136
|
+
export {};
|