@aglyn/plugins-crm 1.0.0-beta.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +57 -0
  4. package/src/index.d.ts +21 -0
  5. package/src/index.js +29 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/activity-list.d.ts +98 -0
  8. package/src/lib/components/activity-list.js +321 -0
  9. package/src/lib/components/activity-list.js.map +1 -0
  10. package/src/lib/components/activity-queries.d.ts +117 -0
  11. package/src/lib/components/activity-queries.js +198 -0
  12. package/src/lib/components/activity-queries.js.map +1 -0
  13. package/src/lib/components/add-to-list-button.d.ts +25 -0
  14. package/src/lib/components/add-to-list-button.js +64 -0
  15. package/src/lib/components/add-to-list-button.js.map +1 -0
  16. package/src/lib/components/add-to-list-dialog.d.ts +24 -0
  17. package/src/lib/components/add-to-list-dialog.js +327 -0
  18. package/src/lib/components/add-to-list-dialog.js.map +1 -0
  19. package/src/lib/components/assignment-rule-drawer.d.ts +43 -0
  20. package/src/lib/components/assignment-rule-drawer.js +293 -0
  21. package/src/lib/components/assignment-rule-drawer.js.map +1 -0
  22. package/src/lib/components/companies-bulk-bar.d.ts +29 -0
  23. package/src/lib/components/companies-bulk-bar.js +274 -0
  24. package/src/lib/components/companies-bulk-bar.js.map +1 -0
  25. package/src/lib/components/companies-section.d.ts +54 -0
  26. package/src/lib/components/companies-section.js +522 -0
  27. package/src/lib/components/companies-section.js.map +1 -0
  28. package/src/lib/components/company-contacts-card.d.ts +49 -0
  29. package/src/lib/components/company-contacts-card.js +435 -0
  30. package/src/lib/components/company-contacts-card.js.map +1 -0
  31. package/src/lib/components/company-deals-card.d.ts +20 -0
  32. package/src/lib/components/company-deals-card.js +38 -0
  33. package/src/lib/components/company-deals-card.js.map +1 -0
  34. package/src/lib/components/company-detail-page.d.ts +20 -0
  35. package/src/lib/components/company-detail-page.js +165 -0
  36. package/src/lib/components/company-detail-page.js.map +1 -0
  37. package/src/lib/components/company-edit-drawer.d.ts +65 -0
  38. package/src/lib/components/company-edit-drawer.js +433 -0
  39. package/src/lib/components/company-edit-drawer.js.map +1 -0
  40. package/src/lib/components/company-import-drawer.d.ts +42 -0
  41. package/src/lib/components/company-import-drawer.js +99 -0
  42. package/src/lib/components/company-import-drawer.js.map +1 -0
  43. package/src/lib/components/company-picker.d.ts +127 -0
  44. package/src/lib/components/company-picker.js +323 -0
  45. package/src/lib/components/company-picker.js.map +1 -0
  46. package/src/lib/components/company-properties-card.d.ts +47 -0
  47. package/src/lib/components/company-properties-card.js +289 -0
  48. package/src/lib/components/company-properties-card.js.map +1 -0
  49. package/src/lib/components/contact-address-fields.d.ts +29 -0
  50. package/src/lib/components/contact-address-fields.js +149 -0
  51. package/src/lib/components/contact-address-fields.js.map +1 -0
  52. package/src/lib/components/contact-associations-card.d.ts +72 -0
  53. package/src/lib/components/contact-associations-card.js +296 -0
  54. package/src/lib/components/contact-associations-card.js.map +1 -0
  55. package/src/lib/components/contact-custom-columns.d.ts +38 -0
  56. package/src/lib/components/contact-custom-columns.js +102 -0
  57. package/src/lib/components/contact-custom-columns.js.map +1 -0
  58. package/src/lib/components/contact-custom-fields-card.d.ts +39 -0
  59. package/src/lib/components/contact-custom-fields-card.js +204 -0
  60. package/src/lib/components/contact-custom-fields-card.js.map +1 -0
  61. package/src/lib/components/contact-deals-card.d.ts +20 -0
  62. package/src/lib/components/contact-deals-card.js +38 -0
  63. package/src/lib/components/contact-deals-card.js.map +1 -0
  64. package/src/lib/components/contact-detail-page.d.ts +46 -0
  65. package/src/lib/components/contact-detail-page.js +535 -0
  66. package/src/lib/components/contact-detail-page.js.map +1 -0
  67. package/src/lib/components/contact-duplicates-card.d.ts +42 -0
  68. package/src/lib/components/contact-duplicates-card.js +167 -0
  69. package/src/lib/components/contact-duplicates-card.js.map +1 -0
  70. package/src/lib/components/contact-field-drawer.d.ts +64 -0
  71. package/src/lib/components/contact-field-drawer.js +251 -0
  72. package/src/lib/components/contact-field-drawer.js.map +1 -0
  73. package/src/lib/components/contact-import-drawer.d.ts +46 -0
  74. package/src/lib/components/contact-import-drawer.js +149 -0
  75. package/src/lib/components/contact-import-drawer.js.map +1 -0
  76. package/src/lib/components/contact-known-by-card.d.ts +39 -0
  77. package/src/lib/components/contact-known-by-card.js +142 -0
  78. package/src/lib/components/contact-known-by-card.js.map +1 -0
  79. package/src/lib/components/contact-list-columns.d.ts +53 -0
  80. package/src/lib/components/contact-list-columns.js +288 -0
  81. package/src/lib/components/contact-list-columns.js.map +1 -0
  82. package/src/lib/components/contact-merge-dialog.d.ts +79 -0
  83. package/src/lib/components/contact-merge-dialog.js +405 -0
  84. package/src/lib/components/contact-merge-dialog.js.map +1 -0
  85. package/src/lib/components/contact-properties-card.d.ts +89 -0
  86. package/src/lib/components/contact-properties-card.js +492 -0
  87. package/src/lib/components/contact-properties-card.js.map +1 -0
  88. package/src/lib/components/contact-timeline-card.d.ts +68 -0
  89. package/src/lib/components/contact-timeline-card.js +435 -0
  90. package/src/lib/components/contact-timeline-card.js.map +1 -0
  91. package/src/lib/components/contacts-bulk-bar.d.ts +91 -0
  92. package/src/lib/components/contacts-bulk-bar.js +493 -0
  93. package/src/lib/components/contacts-bulk-bar.js.map +1 -0
  94. package/src/lib/components/contacts-section.d.ts +26 -0
  95. package/src/lib/components/contacts-section.js +1025 -0
  96. package/src/lib/components/contacts-section.js.map +1 -0
  97. package/src/lib/components/copy-capture-address-button.d.ts +33 -0
  98. package/src/lib/components/copy-capture-address-button.js +60 -0
  99. package/src/lib/components/copy-capture-address-button.js.map +1 -0
  100. package/src/lib/components/crm-attribution-zone.d.ts +38 -0
  101. package/src/lib/components/crm-attribution-zone.js +36 -0
  102. package/src/lib/components/crm-attribution-zone.js.map +1 -0
  103. package/src/lib/components/crm-booking-zone.d.ts +61 -0
  104. package/src/lib/components/crm-booking-zone.js +50 -0
  105. package/src/lib/components/crm-booking-zone.js.map +1 -0
  106. package/src/lib/components/crm-bulk-bar-frame.d.ts +47 -0
  107. package/src/lib/components/crm-bulk-bar-frame.js +137 -0
  108. package/src/lib/components/crm-bulk-bar-frame.js.map +1 -0
  109. package/src/lib/components/crm-call-actions.d.ts +65 -0
  110. package/src/lib/components/crm-call-actions.js +110 -0
  111. package/src/lib/components/crm-call-actions.js.map +1 -0
  112. package/src/lib/components/crm-column-menu.d.ts +21 -0
  113. package/src/lib/components/crm-column-menu.js +103 -0
  114. package/src/lib/components/crm-column-menu.js.map +1 -0
  115. package/src/lib/components/crm-console-page.d.ts +19 -0
  116. package/src/lib/components/crm-console-page.js +151 -0
  117. package/src/lib/components/crm-console-page.js.map +1 -0
  118. package/src/lib/components/crm-console-sections.d.ts +52 -0
  119. package/src/lib/components/crm-console-sections.js +83 -0
  120. package/src/lib/components/crm-console-sections.js.map +1 -0
  121. package/src/lib/components/crm-custom-field-control.d.ts +26 -0
  122. package/src/lib/components/crm-custom-field-control.js +131 -0
  123. package/src/lib/components/crm-custom-field-control.js.map +1 -0
  124. package/src/lib/components/crm-email-template-tools.d.ts +69 -0
  125. package/src/lib/components/crm-email-template-tools.js +240 -0
  126. package/src/lib/components/crm-email-template-tools.js.map +1 -0
  127. package/src/lib/components/crm-export-all-button.d.ts +39 -0
  128. package/src/lib/components/crm-export-all-button.js +98 -0
  129. package/src/lib/components/crm-export-all-button.js.map +1 -0
  130. package/src/lib/components/crm-filter-bar.d.ts +53 -0
  131. package/src/lib/components/crm-filter-bar.js +294 -0
  132. package/src/lib/components/crm-filter-bar.js.map +1 -0
  133. package/src/lib/components/crm-glance-card.d.ts +54 -0
  134. package/src/lib/components/crm-glance-card.js +219 -0
  135. package/src/lib/components/crm-glance-card.js.map +1 -0
  136. package/src/lib/components/crm-next-activity-column.d.ts +35 -0
  137. package/src/lib/components/crm-next-activity-column.js +100 -0
  138. package/src/lib/components/crm-next-activity-column.js.map +1 -0
  139. package/src/lib/components/crm-record-header.d.ts +97 -0
  140. package/src/lib/components/crm-record-header.js +160 -0
  141. package/src/lib/components/crm-record-header.js.map +1 -0
  142. package/src/lib/components/crm-record-insights-zone.d.ts +50 -0
  143. package/src/lib/components/crm-record-insights-zone.js +169 -0
  144. package/src/lib/components/crm-record-insights-zone.js.map +1 -0
  145. package/src/lib/components/crm-record-picker.d.ts +42 -0
  146. package/src/lib/components/crm-record-picker.js +104 -0
  147. package/src/lib/components/crm-record-picker.js.map +1 -0
  148. package/src/lib/components/crm-send-email-button.d.ts +39 -0
  149. package/src/lib/components/crm-send-email-button.js +81 -0
  150. package/src/lib/components/crm-send-email-button.js.map +1 -0
  151. package/src/lib/components/crm-send-email-dialog.d.ts +61 -0
  152. package/src/lib/components/crm-send-email-dialog.js +851 -0
  153. package/src/lib/components/crm-send-email-dialog.js.map +1 -0
  154. package/src/lib/components/crm-site-picker.d.ts +61 -0
  155. package/src/lib/components/crm-site-picker.js +99 -0
  156. package/src/lib/components/crm-site-picker.js.map +1 -0
  157. package/src/lib/components/crm-suite-lock.d.ts +29 -0
  158. package/src/lib/components/crm-suite-lock.js +114 -0
  159. package/src/lib/components/crm-suite-lock.js.map +1 -0
  160. package/src/lib/components/crm-tasks-due-card.d.ts +42 -0
  161. package/src/lib/components/crm-tasks-due-card.js +225 -0
  162. package/src/lib/components/crm-tasks-due-card.js.map +1 -0
  163. package/src/lib/components/crm-views-control.d.ts +65 -0
  164. package/src/lib/components/crm-views-control.js +518 -0
  165. package/src/lib/components/crm-views-control.js.map +1 -0
  166. package/src/lib/components/csv-import-drawer.d.ts +132 -0
  167. package/src/lib/components/csv-import-drawer.js +746 -0
  168. package/src/lib/components/csv-import-drawer.js.map +1 -0
  169. package/src/lib/components/deal-board.d.ts +70 -0
  170. package/src/lib/components/deal-board.js +478 -0
  171. package/src/lib/components/deal-board.js.map +1 -0
  172. package/src/lib/components/deal-detail-page.d.ts +18 -0
  173. package/src/lib/components/deal-detail-page.js +323 -0
  174. package/src/lib/components/deal-detail-page.js.map +1 -0
  175. package/src/lib/components/deal-edit-drawer.d.ts +59 -0
  176. package/src/lib/components/deal-edit-drawer.js +640 -0
  177. package/src/lib/components/deal-edit-drawer.js.map +1 -0
  178. package/src/lib/components/deal-import-drawer.d.ts +24 -0
  179. package/src/lib/components/deal-import-drawer.js +83 -0
  180. package/src/lib/components/deal-import-drawer.js.map +1 -0
  181. package/src/lib/components/deal-products-card.d.ts +48 -0
  182. package/src/lib/components/deal-products-card.js +614 -0
  183. package/src/lib/components/deal-products-card.js.map +1 -0
  184. package/src/lib/components/deal-properties-card.d.ts +29 -0
  185. package/src/lib/components/deal-properties-card.js +144 -0
  186. package/src/lib/components/deal-properties-card.js.map +1 -0
  187. package/src/lib/components/deal-stage-card.d.ts +24 -0
  188. package/src/lib/components/deal-stage-card.js +199 -0
  189. package/src/lib/components/deal-stage-card.js.map +1 -0
  190. package/src/lib/components/deals-bulk-bar.d.ts +31 -0
  191. package/src/lib/components/deals-bulk-bar.js +340 -0
  192. package/src/lib/components/deals-bulk-bar.js.map +1 -0
  193. package/src/lib/components/deals-section.d.ts +49 -0
  194. package/src/lib/components/deals-section.js +767 -0
  195. package/src/lib/components/deals-section.js.map +1 -0
  196. package/src/lib/components/email-capture-card.d.ts +42 -0
  197. package/src/lib/components/email-capture-card.js +134 -0
  198. package/src/lib/components/email-capture-card.js.map +1 -0
  199. package/src/lib/components/email-template-drawer.d.ts +33 -0
  200. package/src/lib/components/email-template-drawer.js +209 -0
  201. package/src/lib/components/email-template-drawer.js.map +1 -0
  202. package/src/lib/components/email-templates-card.d.ts +33 -0
  203. package/src/lib/components/email-templates-card.js +370 -0
  204. package/src/lib/components/email-templates-card.js.map +1 -0
  205. package/src/lib/components/erase-person-action.d.ts +92 -0
  206. package/src/lib/components/erase-person-action.js +261 -0
  207. package/src/lib/components/erase-person-action.js.map +1 -0
  208. package/src/lib/components/fields-section.d.ts +37 -0
  209. package/src/lib/components/fields-section.js +641 -0
  210. package/src/lib/components/fields-section.js.map +1 -0
  211. package/src/lib/components/form-contact-fields-card.d.ts +46 -0
  212. package/src/lib/components/form-contact-fields-card.js +253 -0
  213. package/src/lib/components/form-contact-fields-card.js.map +1 -0
  214. package/src/lib/components/insert-link-at-caret.d.ts +28 -0
  215. package/src/lib/components/insert-link-at-caret.js +37 -0
  216. package/src/lib/components/insert-link-at-caret.js.map +1 -0
  217. package/src/lib/components/lead-convert-dialog.d.ts +32 -0
  218. package/src/lib/components/lead-convert-dialog.js +493 -0
  219. package/src/lib/components/lead-convert-dialog.js.map +1 -0
  220. package/src/lib/components/lead-detail-page.d.ts +16 -0
  221. package/src/lib/components/lead-detail-page.js +163 -0
  222. package/src/lib/components/lead-detail-page.js.map +1 -0
  223. package/src/lib/components/lead-history-card.d.ts +32 -0
  224. package/src/lib/components/lead-history-card.js +165 -0
  225. package/src/lib/components/lead-history-card.js.map +1 -0
  226. package/src/lib/components/lead-import-drawer.d.ts +24 -0
  227. package/src/lib/components/lead-import-drawer.js +95 -0
  228. package/src/lib/components/lead-import-drawer.js.map +1 -0
  229. package/src/lib/components/lead-owner-select.d.ts +35 -0
  230. package/src/lib/components/lead-owner-select.js +91 -0
  231. package/src/lib/components/lead-owner-select.js.map +1 -0
  232. package/src/lib/components/lead-properties-card.d.ts +61 -0
  233. package/src/lib/components/lead-properties-card.js +395 -0
  234. package/src/lib/components/lead-properties-card.js.map +1 -0
  235. package/src/lib/components/lead-status-chip.d.ts +12 -0
  236. package/src/lib/components/lead-status-chip.js +43 -0
  237. package/src/lib/components/lead-status-chip.js.map +1 -0
  238. package/src/lib/components/lead-surfaces-note.d.ts +126 -0
  239. package/src/lib/components/lead-surfaces-note.js +262 -0
  240. package/src/lib/components/lead-surfaces-note.js.map +1 -0
  241. package/src/lib/components/lead-unqualify-dialog.d.ts +24 -0
  242. package/src/lib/components/lead-unqualify-dialog.js +125 -0
  243. package/src/lib/components/lead-unqualify-dialog.js.map +1 -0
  244. package/src/lib/components/leads-bulk-bar.d.ts +58 -0
  245. package/src/lib/components/leads-bulk-bar.js +307 -0
  246. package/src/lib/components/leads-bulk-bar.js.map +1 -0
  247. package/src/lib/components/leads-section.d.ts +23 -0
  248. package/src/lib/components/leads-section.js +664 -0
  249. package/src/lib/components/leads-section.js.map +1 -0
  250. package/src/lib/components/linked-deals-card.d.ts +32 -0
  251. package/src/lib/components/linked-deals-card.js +163 -0
  252. package/src/lib/components/linked-deals-card.js.map +1 -0
  253. package/src/lib/components/log-activity-dialog.d.ts +60 -0
  254. package/src/lib/components/log-activity-dialog.js +368 -0
  255. package/src/lib/components/log-activity-dialog.js.map +1 -0
  256. package/src/lib/components/lost-reason-dialog.d.ts +26 -0
  257. package/src/lib/components/lost-reason-dialog.js +95 -0
  258. package/src/lib/components/lost-reason-dialog.js.map +1 -0
  259. package/src/lib/components/new-contact-drawer.d.ts +78 -0
  260. package/src/lib/components/new-contact-drawer.js +343 -0
  261. package/src/lib/components/new-contact-drawer.js.map +1 -0
  262. package/src/lib/components/org-lead-surfaces-note.d.ts +57 -0
  263. package/src/lib/components/org-lead-surfaces-note.js +172 -0
  264. package/src/lib/components/org-lead-surfaces-note.js.map +1 -0
  265. package/src/lib/components/owner-avatar.d.ts +18 -0
  266. package/src/lib/components/owner-avatar.js +61 -0
  267. package/src/lib/components/owner-avatar.js.map +1 -0
  268. package/src/lib/components/pipeline-stages-dialog.d.ts +43 -0
  269. package/src/lib/components/pipeline-stages-dialog.js +339 -0
  270. package/src/lib/components/pipeline-stages-dialog.js.map +1 -0
  271. package/src/lib/components/pipelines-dialog.d.ts +55 -0
  272. package/src/lib/components/pipelines-dialog.js +517 -0
  273. package/src/lib/components/pipelines-dialog.js.map +1 -0
  274. package/src/lib/components/recent-activity-feed.d.ts +39 -0
  275. package/src/lib/components/recent-activity-feed.js +103 -0
  276. package/src/lib/components/recent-activity-feed.js.map +1 -0
  277. package/src/lib/components/recipes-card.d.ts +39 -0
  278. package/src/lib/components/recipes-card.js +485 -0
  279. package/src/lib/components/recipes-card.js.map +1 -0
  280. package/src/lib/components/record-activity-card.d.ts +26 -0
  281. package/src/lib/components/record-activity-card.js +121 -0
  282. package/src/lib/components/record-activity-card.js.map +1 -0
  283. package/src/lib/components/record-files-card.d.ts +67 -0
  284. package/src/lib/components/record-files-card.js +290 -0
  285. package/src/lib/components/record-files-card.js.map +1 -0
  286. package/src/lib/components/record-tasks-card.d.ts +32 -0
  287. package/src/lib/components/record-tasks-card.js +306 -0
  288. package/src/lib/components/record-tasks-card.js.map +1 -0
  289. package/src/lib/components/reports/activity-card.d.ts +57 -0
  290. package/src/lib/components/reports/activity-card.js +281 -0
  291. package/src/lib/components/reports/activity-card.js.map +1 -0
  292. package/src/lib/components/reports/closed-deals-card.d.ts +20 -0
  293. package/src/lib/components/reports/closed-deals-card.js +194 -0
  294. package/src/lib/components/reports/closed-deals-card.js.map +1 -0
  295. package/src/lib/components/reports/contacts-mix-card.d.ts +27 -0
  296. package/src/lib/components/reports/contacts-mix-card.js +161 -0
  297. package/src/lib/components/reports/contacts-mix-card.js.map +1 -0
  298. package/src/lib/components/reports/contacts-trend-card.d.ts +31 -0
  299. package/src/lib/components/reports/contacts-trend-card.js +144 -0
  300. package/src/lib/components/reports/contacts-trend-card.js.map +1 -0
  301. package/src/lib/components/reports/forecast-card.d.ts +22 -0
  302. package/src/lib/components/reports/forecast-card.js +251 -0
  303. package/src/lib/components/reports/forecast-card.js.map +1 -0
  304. package/src/lib/components/reports/lead-counts.d.ts +32 -0
  305. package/src/lib/components/reports/lead-counts.js +44 -0
  306. package/src/lib/components/reports/lead-counts.js.map +1 -0
  307. package/src/lib/components/reports/lead-funnel-card.d.ts +38 -0
  308. package/src/lib/components/reports/lead-funnel-card.js +375 -0
  309. package/src/lib/components/reports/lead-funnel-card.js.map +1 -0
  310. package/src/lib/components/reports/pipeline-card.d.ts +19 -0
  311. package/src/lib/components/reports/pipeline-card.js +338 -0
  312. package/src/lib/components/reports/pipeline-card.js.map +1 -0
  313. package/src/lib/components/reports/report-bar-chart.d.ts +39 -0
  314. package/src/lib/components/reports/report-bar-chart.js +133 -0
  315. package/src/lib/components/reports/report-bar-chart.js.map +1 -0
  316. package/src/lib/components/reports/report-breakdown.d.ts +32 -0
  317. package/src/lib/components/reports/report-breakdown.js +105 -0
  318. package/src/lib/components/reports/report-breakdown.js.map +1 -0
  319. package/src/lib/components/reports/report-export.d.ts +37 -0
  320. package/src/lib/components/reports/report-export.js +66 -0
  321. package/src/lib/components/reports/report-export.js.map +1 -0
  322. package/src/lib/components/reports/report-format.d.ts +36 -0
  323. package/src/lib/components/reports/report-format.js +43 -0
  324. package/src/lib/components/reports/report-format.js.map +1 -0
  325. package/src/lib/components/reports/report-scope.d.ts +81 -0
  326. package/src/lib/components/reports/report-scope.js +58 -0
  327. package/src/lib/components/reports/report-scope.js.map +1 -0
  328. package/src/lib/components/reports/report-stat-tile.d.ts +47 -0
  329. package/src/lib/components/reports/report-stat-tile.js +96 -0
  330. package/src/lib/components/reports/report-stat-tile.js.map +1 -0
  331. package/src/lib/components/reports/source-conversion-card.d.ts +28 -0
  332. package/src/lib/components/reports/source-conversion-card.js +205 -0
  333. package/src/lib/components/reports/source-conversion-card.js.map +1 -0
  334. package/src/lib/components/reports/tasks-card.d.ts +26 -0
  335. package/src/lib/components/reports/tasks-card.js +279 -0
  336. package/src/lib/components/reports/tasks-card.js.map +1 -0
  337. package/src/lib/components/reports/use-aggregate-read.d.ts +99 -0
  338. package/src/lib/components/reports/use-aggregate-read.js +195 -0
  339. package/src/lib/components/reports/use-aggregate-read.js.map +1 -0
  340. package/src/lib/components/reports/won-lost-by-owner-card.d.ts +30 -0
  341. package/src/lib/components/reports/won-lost-by-owner-card.js +168 -0
  342. package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -0
  343. package/src/lib/components/reports-section.d.ts +42 -0
  344. package/src/lib/components/reports-section.js +308 -0
  345. package/src/lib/components/reports-section.js.map +1 -0
  346. package/src/lib/components/sending-addresses-card.d.ts +49 -0
  347. package/src/lib/components/sending-addresses-card.js +262 -0
  348. package/src/lib/components/sending-addresses-card.js.map +1 -0
  349. package/src/lib/components/settings-section.d.ts +151 -0
  350. package/src/lib/components/settings-section.js +783 -0
  351. package/src/lib/components/settings-section.js.map +1 -0
  352. package/src/lib/components/task-cells.d.ts +51 -0
  353. package/src/lib/components/task-cells.js +139 -0
  354. package/src/lib/components/task-cells.js.map +1 -0
  355. package/src/lib/components/task-edit-drawer.d.ts +53 -0
  356. package/src/lib/components/task-edit-drawer.js +483 -0
  357. package/src/lib/components/task-edit-drawer.js.map +1 -0
  358. package/src/lib/components/task-import-drawer.d.ts +24 -0
  359. package/src/lib/components/task-import-drawer.js +82 -0
  360. package/src/lib/components/task-import-drawer.js.map +1 -0
  361. package/src/lib/components/task-snooze-menu.d.ts +65 -0
  362. package/src/lib/components/task-snooze-menu.js +207 -0
  363. package/src/lib/components/task-snooze-menu.js.map +1 -0
  364. package/src/lib/components/tasks-bulk-bar.d.ts +25 -0
  365. package/src/lib/components/tasks-bulk-bar.js +348 -0
  366. package/src/lib/components/tasks-bulk-bar.js.map +1 -0
  367. package/src/lib/components/tasks-calendar.d.ts +38 -0
  368. package/src/lib/components/tasks-calendar.js +228 -0
  369. package/src/lib/components/tasks-calendar.js.map +1 -0
  370. package/src/lib/components/tasks-section.d.ts +27 -0
  371. package/src/lib/components/tasks-section.js +579 -0
  372. package/src/lib/components/tasks-section.js.map +1 -0
  373. package/src/lib/components/use-contact-campaign-emails.d.ts +32 -0
  374. package/src/lib/components/use-contact-campaign-emails.js +83 -0
  375. package/src/lib/components/use-contact-campaign-emails.js.map +1 -0
  376. package/src/lib/components/use-crm-api.d.ts +44 -0
  377. package/src/lib/components/use-crm-api.js +84 -0
  378. package/src/lib/components/use-crm-api.js.map +1 -0
  379. package/src/lib/components/use-crm-inbound-address.d.ts +60 -0
  380. package/src/lib/components/use-crm-inbound-address.js +124 -0
  381. package/src/lib/components/use-crm-inbound-address.js.map +1 -0
  382. package/src/lib/components/use-emails-hub-path.d.ts +24 -0
  383. package/src/lib/components/use-emails-hub-path.js +59 -0
  384. package/src/lib/components/use-emails-hub-path.js.map +1 -0
  385. package/src/lib/components/use-member-email-aliases.d.ts +54 -0
  386. package/src/lib/components/use-member-email-aliases.js +186 -0
  387. package/src/lib/components/use-member-email-aliases.js.map +1 -0
  388. package/src/lib/components/use-org-members.d.ts +40 -0
  389. package/src/lib/components/use-org-members.js +100 -0
  390. package/src/lib/components/use-org-members.js.map +1 -0
  391. package/src/lib/constants/api-routes.d.ts +109 -0
  392. package/src/lib/constants/api-routes.js +74 -0
  393. package/src/lib/constants/api-routes.js.map +1 -0
  394. package/src/lib/constants/bundle-common.d.ts +14 -0
  395. package/src/lib/constants/bundle-common.js +14 -0
  396. package/src/lib/constants/bundle-common.js.map +1 -0
  397. package/src/lib/constants/company-filters.d.ts +18 -0
  398. package/src/lib/constants/company-filters.js +51 -0
  399. package/src/lib/constants/company-filters.js.map +1 -0
  400. package/src/lib/constants/contact-filters.d.ts +38 -0
  401. package/src/lib/constants/contact-filters.js +294 -0
  402. package/src/lib/constants/contact-filters.js.map +1 -0
  403. package/src/lib/hooks/use-contact-field-definitions.d.ts +48 -0
  404. package/src/lib/hooks/use-contact-field-definitions.js +72 -0
  405. package/src/lib/hooks/use-contact-field-definitions.js.map +1 -0
  406. package/src/lib/hooks/use-contact-update.d.ts +25 -0
  407. package/src/lib/hooks/use-contact-update.js +80 -0
  408. package/src/lib/hooks/use-contact-update.js.map +1 -0
  409. package/src/lib/hooks/use-crm-activity-logger.d.ts +73 -0
  410. package/src/lib/hooks/use-crm-activity-logger.js +101 -0
  411. package/src/lib/hooks/use-crm-activity-logger.js.map +1 -0
  412. package/src/lib/hooks/use-crm-bulk-apply.d.ts +52 -0
  413. package/src/lib/hooks/use-crm-bulk-apply.js +112 -0
  414. package/src/lib/hooks/use-crm-bulk-apply.js.map +1 -0
  415. package/src/lib/hooks/use-crm-default-view.d.ts +24 -0
  416. package/src/lib/hooks/use-crm-default-view.js +64 -0
  417. package/src/lib/hooks/use-crm-default-view.js.map +1 -0
  418. package/src/lib/hooks/use-crm-email-templates.d.ts +59 -0
  419. package/src/lib/hooks/use-crm-email-templates.js +103 -0
  420. package/src/lib/hooks/use-crm-email-templates.js.map +1 -0
  421. package/src/lib/hooks/use-crm-hub-path.d.ts +29 -0
  422. package/src/lib/hooks/use-crm-hub-path.js +41 -0
  423. package/src/lib/hooks/use-crm-hub-path.js.map +1 -0
  424. package/src/lib/hooks/use-crm-org-mount.d.ts +97 -0
  425. package/src/lib/hooks/use-crm-org-mount.js +215 -0
  426. package/src/lib/hooks/use-crm-org-mount.js.map +1 -0
  427. package/src/lib/hooks/use-crm-record-names.d.ts +52 -0
  428. package/src/lib/hooks/use-crm-record-names.js +126 -0
  429. package/src/lib/hooks/use-crm-record-names.js.map +1 -0
  430. package/src/lib/hooks/use-crm-records-quota.d.ts +68 -0
  431. package/src/lib/hooks/use-crm-records-quota.js +118 -0
  432. package/src/lib/hooks/use-crm-records-quota.js.map +1 -0
  433. package/src/lib/hooks/use-crm-saved-view.d.ts +96 -0
  434. package/src/lib/hooks/use-crm-saved-view.js +329 -0
  435. package/src/lib/hooks/use-crm-saved-view.js.map +1 -0
  436. package/src/lib/hooks/use-crm-scope.d.ts +117 -0
  437. package/src/lib/hooks/use-crm-scope.js +147 -0
  438. package/src/lib/hooks/use-crm-scope.js.map +1 -0
  439. package/src/lib/hooks/use-crm-tasks.d.ts +85 -0
  440. package/src/lib/hooks/use-crm-tasks.js +153 -0
  441. package/src/lib/hooks/use-crm-tasks.js.map +1 -0
  442. package/src/lib/hooks/use-crm-view-grid.d.ts +55 -0
  443. package/src/lib/hooks/use-crm-view-grid.js +170 -0
  444. package/src/lib/hooks/use-crm-view-grid.js.map +1 -0
  445. package/src/lib/hooks/use-crm-views.d.ts +42 -0
  446. package/src/lib/hooks/use-crm-views.js +84 -0
  447. package/src/lib/hooks/use-crm-views.js.map +1 -0
  448. package/src/lib/hooks/use-deal-stage-api.d.ts +69 -0
  449. package/src/lib/hooks/use-deal-stage-api.js +78 -0
  450. package/src/lib/hooks/use-deal-stage-api.js.map +1 -0
  451. package/src/lib/hooks/use-deals.d.ts +47 -0
  452. package/src/lib/hooks/use-deals.js +105 -0
  453. package/src/lib/hooks/use-deals.js.map +1 -0
  454. package/src/lib/hooks/use-org-leads.d.ts +53 -0
  455. package/src/lib/hooks/use-org-leads.js +114 -0
  456. package/src/lib/hooks/use-org-leads.js.map +1 -0
  457. package/src/lib/hooks/use-org-member-directory.d.ts +36 -0
  458. package/src/lib/hooks/use-org-member-directory.js +116 -0
  459. package/src/lib/hooks/use-org-member-directory.js.map +1 -0
  460. package/src/lib/hooks/use-org-member-options.d.ts +38 -0
  461. package/src/lib/hooks/use-org-member-options.js +131 -0
  462. package/src/lib/hooks/use-org-member-options.js.map +1 -0
  463. package/src/lib/hooks/use-pipeline.d.ts +71 -0
  464. package/src/lib/hooks/use-pipeline.js +159 -0
  465. package/src/lib/hooks/use-pipeline.js.map +1 -0
  466. package/src/lib/model/companies-bulk-writes.d.ts +40 -0
  467. package/src/lib/model/companies-bulk-writes.js +103 -0
  468. package/src/lib/model/companies-bulk-writes.js.map +1 -0
  469. package/src/lib/model/companies-csv.d.ts +36 -0
  470. package/src/lib/model/companies-csv.js +38 -0
  471. package/src/lib/model/companies-csv.js.map +1 -0
  472. package/src/lib/model/companies.d.ts +99 -0
  473. package/src/lib/model/companies.js +168 -0
  474. package/src/lib/model/companies.js.map +1 -0
  475. package/src/lib/model/company-delete-route.d.ts +43 -0
  476. package/src/lib/model/company-delete-route.js +31 -0
  477. package/src/lib/model/company-delete-route.js.map +1 -0
  478. package/src/lib/model/company-delete.d.ts +42 -0
  479. package/src/lib/model/company-delete.js +52 -0
  480. package/src/lib/model/company-delete.js.map +1 -0
  481. package/src/lib/model/contact-duplicates.d.ts +58 -0
  482. package/src/lib/model/contact-duplicates.js +67 -0
  483. package/src/lib/model/contact-duplicates.js.map +1 -0
  484. package/src/lib/model/contact-record.d.ts +113 -0
  485. package/src/lib/model/contact-record.js +70 -0
  486. package/src/lib/model/contact-record.js.map +1 -0
  487. package/src/lib/model/contact-update.d.ts +93 -0
  488. package/src/lib/model/contact-update.js +50 -0
  489. package/src/lib/model/contact-update.js.map +1 -0
  490. package/src/lib/model/contacts-bulk-writes.d.ts +173 -0
  491. package/src/lib/model/contacts-bulk-writes.js +196 -0
  492. package/src/lib/model/contacts-bulk-writes.js.map +1 -0
  493. package/src/lib/model/contacts-csv.d.ts +54 -0
  494. package/src/lib/model/contacts-csv.js +67 -0
  495. package/src/lib/model/contacts-csv.js.map +1 -0
  496. package/src/lib/model/contacts-list-seed.d.ts +51 -0
  497. package/src/lib/model/contacts-list-seed.js +63 -0
  498. package/src/lib/model/contacts-list-seed.js.map +1 -0
  499. package/src/lib/model/crm-activity-report.d.ts +27 -0
  500. package/src/lib/model/crm-activity-report.js +56 -0
  501. package/src/lib/model/crm-activity-report.js.map +1 -0
  502. package/src/lib/model/crm-api.d.ts +49 -0
  503. package/src/lib/model/crm-api.js +47 -0
  504. package/src/lib/model/crm-api.js.map +1 -0
  505. package/src/lib/model/crm-bulk-writes.d.ts +147 -0
  506. package/src/lib/model/crm-bulk-writes.js +207 -0
  507. package/src/lib/model/crm-bulk-writes.js.map +1 -0
  508. package/src/lib/model/crm-company-import.d.ts +154 -0
  509. package/src/lib/model/crm-company-import.js +331 -0
  510. package/src/lib/model/crm-company-import.js.map +1 -0
  511. package/src/lib/model/crm-custom-draft.d.ts +62 -0
  512. package/src/lib/model/crm-custom-draft.js +67 -0
  513. package/src/lib/model/crm-custom-draft.js.map +1 -0
  514. package/src/lib/model/crm-deal-import.d.ts +112 -0
  515. package/src/lib/model/crm-deal-import.js +263 -0
  516. package/src/lib/model/crm-deal-import.js.map +1 -0
  517. package/src/lib/model/crm-import.d.ts +244 -0
  518. package/src/lib/model/crm-import.js +466 -0
  519. package/src/lib/model/crm-import.js.map +1 -0
  520. package/src/lib/model/crm-lead-import.d.ts +105 -0
  521. package/src/lib/model/crm-lead-import.js +255 -0
  522. package/src/lib/model/crm-lead-import.js.map +1 -0
  523. package/src/lib/model/crm-record-routes.d.ts +21 -0
  524. package/src/lib/model/crm-record-routes.js +70 -0
  525. package/src/lib/model/crm-record-routes.js.map +1 -0
  526. package/src/lib/model/crm-routes.d.ts +101 -0
  527. package/src/lib/model/crm-routes.js +92 -0
  528. package/src/lib/model/crm-routes.js.map +1 -0
  529. package/src/lib/model/crm-task-import.d.ts +94 -0
  530. package/src/lib/model/crm-task-import.js +275 -0
  531. package/src/lib/model/crm-task-import.js.map +1 -0
  532. package/src/lib/model/crm-view-param.d.ts +43 -0
  533. package/src/lib/model/crm-view-param.js +49 -0
  534. package/src/lib/model/crm-view-param.js.map +1 -0
  535. package/src/lib/model/deal-board-model.d.ts +241 -0
  536. package/src/lib/model/deal-board-model.js +439 -0
  537. package/src/lib/model/deal-board-model.js.map +1 -0
  538. package/src/lib/model/deal-form-model.d.ts +114 -0
  539. package/src/lib/model/deal-form-model.js +218 -0
  540. package/src/lib/model/deal-form-model.js.map +1 -0
  541. package/src/lib/model/deals-csv.d.ts +32 -0
  542. package/src/lib/model/deals-csv.js +37 -0
  543. package/src/lib/model/deals-csv.js.map +1 -0
  544. package/src/lib/model/import-column-shapes.d.ts +36 -0
  545. package/src/lib/model/import-column-shapes.js +76 -0
  546. package/src/lib/model/import-column-shapes.js.map +1 -0
  547. package/src/lib/model/lead-company-suggestion.d.ts +61 -0
  548. package/src/lib/model/lead-company-suggestion.js +65 -0
  549. package/src/lib/model/lead-company-suggestion.js.map +1 -0
  550. package/src/lib/model/lead-filters.d.ts +29 -0
  551. package/src/lib/model/lead-filters.js +39 -0
  552. package/src/lib/model/lead-filters.js.map +1 -0
  553. package/src/lib/model/lead-surfaces.d.ts +58 -0
  554. package/src/lib/model/lead-surfaces.js +67 -0
  555. package/src/lib/model/lead-surfaces.js.map +1 -0
  556. package/src/lib/model/leads-csv.d.ts +40 -0
  557. package/src/lib/model/leads-csv.js +40 -0
  558. package/src/lib/model/leads-csv.js.map +1 -0
  559. package/src/lib/model/next-activity-api.d.ts +31 -0
  560. package/src/lib/model/next-activity-api.js +57 -0
  561. package/src/lib/model/next-activity-api.js.map +1 -0
  562. package/src/lib/model/next-activity.d.ts +64 -0
  563. package/src/lib/model/next-activity.js +47 -0
  564. package/src/lib/model/next-activity.js.map +1 -0
  565. package/src/lib/model/person-key-web.d.ts +43 -0
  566. package/src/lib/model/person-key-web.js +52 -0
  567. package/src/lib/model/person-key-web.js.map +1 -0
  568. package/src/lib/model/record-facts.d.ts +282 -0
  569. package/src/lib/model/record-facts.js +434 -0
  570. package/src/lib/model/record-facts.js.map +1 -0
  571. package/src/lib/model/task-api.d.ts +28 -0
  572. package/src/lib/model/task-api.js +57 -0
  573. package/src/lib/model/task-api.js.map +1 -0
  574. package/src/lib/model/task-calendar.d.ts +108 -0
  575. package/src/lib/model/task-calendar.js +139 -0
  576. package/src/lib/model/task-calendar.js.map +1 -0
  577. package/src/lib/model/task-routes.d.ts +177 -0
  578. package/src/lib/model/task-routes.js +197 -0
  579. package/src/lib/model/task-routes.js.map +1 -0
  580. package/src/lib/model/task-scope.d.ts +45 -0
  581. package/src/lib/model/task-scope.js +51 -0
  582. package/src/lib/model/task-scope.js.map +1 -0
  583. package/src/lib/model/task-views.d.ts +149 -0
  584. package/src/lib/model/task-views.js +286 -0
  585. package/src/lib/model/task-views.js.map +1 -0
  586. package/src/lib/model/tasks-csv.d.ts +28 -0
  587. package/src/lib/model/tasks-csv.js +28 -0
  588. package/src/lib/model/tasks-csv.js.map +1 -0
  589. package/src/lib/plugin.d.ts +32 -0
  590. package/src/lib/plugin.js +208 -0
  591. package/src/lib/plugin.js.map +1 -0
  592. package/src/lib/server/companies-import.d.ts +57 -0
  593. package/src/lib/server/companies-import.js +242 -0
  594. package/src/lib/server/companies-import.js.map +1 -0
  595. package/src/lib/server/company-delete.d.ts +59 -0
  596. package/src/lib/server/company-delete.js +146 -0
  597. package/src/lib/server/company-delete.js.map +1 -0
  598. package/src/lib/server/contact-email-history.d.ts +82 -0
  599. package/src/lib/server/contact-email-history.js +264 -0
  600. package/src/lib/server/contact-email-history.js.map +1 -0
  601. package/src/lib/server/contact-profile.d.ts +41 -0
  602. package/src/lib/server/contact-profile.js +45 -0
  603. package/src/lib/server/contact-profile.js.map +1 -0
  604. package/src/lib/server/contact-update.d.ts +92 -0
  605. package/src/lib/server/contact-update.js +512 -0
  606. package/src/lib/server/contact-update.js.map +1 -0
  607. package/src/lib/server/contacts-import.d.ts +73 -0
  608. package/src/lib/server/contacts-import.js +263 -0
  609. package/src/lib/server/contacts-import.js.map +1 -0
  610. package/src/lib/server/contacts-merge.d.ts +86 -0
  611. package/src/lib/server/contacts-merge.js +206 -0
  612. package/src/lib/server/contacts-merge.js.map +1 -0
  613. package/src/lib/server/deals-import.d.ts +76 -0
  614. package/src/lib/server/deals-import.js +246 -0
  615. package/src/lib/server/deals-import.js.map +1 -0
  616. package/src/lib/server/email-send.d.ts +119 -0
  617. package/src/lib/server/email-send.js +667 -0
  618. package/src/lib/server/email-send.js.map +1 -0
  619. package/src/lib/server/email-template-duplicate.d.ts +19 -0
  620. package/src/lib/server/email-template-duplicate.js +206 -0
  621. package/src/lib/server/email-template-duplicate.js.map +1 -0
  622. package/src/lib/server/erase-person.d.ts +108 -0
  623. package/src/lib/server/erase-person.js +309 -0
  624. package/src/lib/server/erase-person.js.map +1 -0
  625. package/src/lib/server/import-context.d.ts +85 -0
  626. package/src/lib/server/import-context.js +181 -0
  627. package/src/lib/server/import-context.js.map +1 -0
  628. package/src/lib/server/inbound-address.d.ts +61 -0
  629. package/src/lib/server/inbound-address.js +188 -0
  630. package/src/lib/server/inbound-address.js.map +1 -0
  631. package/src/lib/server/lead-convert.d.ts +78 -0
  632. package/src/lib/server/lead-convert.js +250 -0
  633. package/src/lib/server/lead-convert.js.map +1 -0
  634. package/src/lib/server/leads-import.d.ts +84 -0
  635. package/src/lib/server/leads-import.js +261 -0
  636. package/src/lib/server/leads-import.js.map +1 -0
  637. package/src/lib/server/next-activity-routes.d.ts +33 -0
  638. package/src/lib/server/next-activity-routes.js +110 -0
  639. package/src/lib/server/next-activity-routes.js.map +1 -0
  640. package/src/lib/server/org-activity.d.ts +61 -0
  641. package/src/lib/server/org-activity.js +120 -0
  642. package/src/lib/server/org-activity.js.map +1 -0
  643. package/src/lib/server/org-caller.d.ts +110 -0
  644. package/src/lib/server/org-caller.js +154 -0
  645. package/src/lib/server/org-caller.js.map +1 -0
  646. package/src/lib/server/recipe-routes.d.ts +75 -0
  647. package/src/lib/server/recipe-routes.js +442 -0
  648. package/src/lib/server/recipe-routes.js.map +1 -0
  649. package/src/lib/server/record-facts.d.ts +65 -0
  650. package/src/lib/server/record-facts.js +337 -0
  651. package/src/lib/server/record-facts.js.map +1 -0
  652. package/src/lib/server/record-timeline.d.ts +51 -0
  653. package/src/lib/server/record-timeline.js +215 -0
  654. package/src/lib/server/record-timeline.js.map +1 -0
  655. package/src/lib/server/suite-gate.d.ts +42 -0
  656. package/src/lib/server/suite-gate.js +87 -0
  657. package/src/lib/server/suite-gate.js.map +1 -0
  658. package/src/lib/server/task-routes.d.ts +132 -0
  659. package/src/lib/server/task-routes.js +699 -0
  660. package/src/lib/server/task-routes.js.map +1 -0
  661. package/src/lib/server/tasks-import.d.ts +54 -0
  662. package/src/lib/server/tasks-import.js +157 -0
  663. package/src/lib/server/tasks-import.js.map +1 -0
  664. package/src/lib/server-deal-stage.d.ts +88 -0
  665. package/src/lib/server-deal-stage.js +316 -0
  666. package/src/lib/server-deal-stage.js.map +1 -0
  667. package/src/lib/server.d.ts +149 -0
  668. package/src/lib/server.js +615 -0
  669. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * `POST /api/crm/company-delete` — unlink the contacts that name a company,
19
+ * then delete it (AGL-2804).
20
+ *
21
+ * Body: `{ hostId, companyId }` under a site, or `{ orgId, companyId }` at
22
+ * the organization level. Answers `CompanyDeleteResponse`.
23
+ *
24
+ * ## Why a route
25
+ *
26
+ * Firestore does not cascade. A company deleted on its own leaves every
27
+ * contact at it naming a record that no longer exists: their page links to
28
+ * nothing and the `companyIds` mirror the company list queries keeps
29
+ * matching a ghost. So a delete is a detach pass and then a delete, and the
30
+ * detach clears each holder's facet that named the company — which the rules
31
+ * refuse a client, because they cannot tell a company link inside a facet
32
+ * from an owner or a stage beside it.
33
+ *
34
+ * ## Bounded, and honest past the bound
35
+ *
36
+ * One pass unlinks at most `COMPANY_DETACH_LIMIT` contacts, a batch's worth.
37
+ * A company past that is left standing with a pass's worth unlinked, and the
38
+ * answer says more remain, so the next delete continues where this one
39
+ * stopped. A company is never deleted with a link still on a contact.
40
+ *
41
+ * ## Who may call it
42
+ *
43
+ * A CRM writer (`authorizeCrmWriter`) whose reach is the whole organization.
44
+ * The contacts are found by the mirror, and that query cannot carry a scope
45
+ * clause beside its `array-contains`: a member scoped to particular sites
46
+ * could not read them in the browser either, and the route does not unlink
47
+ * people on behalf of someone who may not see them.
48
+ *
49
+ * ## The plan
50
+ *
51
+ * Companies are the CRM's, and the CRM is included from Starter (AGL-2851):
52
+ * a workspace whose plan does not carry it is refused once the writer is
53
+ * known, staff included, before anything is read or unlinked
54
+ * (`suite-gate.ts`).
55
+ */
56
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
57
+ /** What a member scoped to particular sites is told. */
58
+ export declare const COMPANY_DELETE_SCOPED_REFUSAL: string;
59
+ export declare const crmCompanyDeleteHandler: PluginApiHandler;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * `POST /api/crm/company-delete` — unlink the contacts that name a company,
18
+ * then delete it (AGL-2804).
19
+ *
20
+ * Body: `{ hostId, companyId }` under a site, or `{ orgId, companyId }` at
21
+ * the organization level. Answers `CompanyDeleteResponse`.
22
+ *
23
+ * ## Why a route
24
+ *
25
+ * Firestore does not cascade. A company deleted on its own leaves every
26
+ * contact at it naming a record that no longer exists: their page links to
27
+ * nothing and the `companyIds` mirror the company list queries keeps
28
+ * matching a ghost. So a delete is a detach pass and then a delete, and the
29
+ * detach clears each holder's facet that named the company — which the rules
30
+ * refuse a client, because they cannot tell a company link inside a facet
31
+ * from an owner or a stage beside it.
32
+ *
33
+ * ## Bounded, and honest past the bound
34
+ *
35
+ * One pass unlinks at most `COMPANY_DETACH_LIMIT` contacts, a batch's worth.
36
+ * A company past that is left standing with a pass's worth unlinked, and the
37
+ * answer says more remain, so the next delete continues where this one
38
+ * stopped. A company is never deleted with a link still on a contact.
39
+ *
40
+ * ## Who may call it
41
+ *
42
+ * A CRM writer (`authorizeCrmWriter`) whose reach is the whole organization.
43
+ * The contacts are found by the mirror, and that query cannot carry a scope
44
+ * clause beside its `array-contains`: a member scoped to particular sites
45
+ * could not read them in the browser either, and the route does not unlink
46
+ * people on behalf of someone who may not see them.
47
+ *
48
+ * ## The plan
49
+ *
50
+ * Companies are the CRM's, and the CRM is included from Starter (AGL-2851):
51
+ * a workspace whose plan does not carry it is refused once the writer is
52
+ * known, staff included, before anything is read or unlinked
53
+ * (`suite-gate.ts`).
54
+ */ import { CONTACT_COMPANY_IDS_FIELD, contactFacetPath, contactGroupsNamingCompany, CRM_COLLECTIONS, isOrgWideMember } from "@aglyn/aglyn/server";
55
+ import { firebaseAdmin } from "@aglyn/tenant-data-admin";
56
+ import { FieldValue } from "firebase-admin/firestore";
57
+ import { COMPANY_DETACH_LIMIT } from "../model/company-delete-route.js";
58
+ import { typed } from "./contact-profile.js";
59
+ import { readCrmRouteScope } from "./org-caller.js";
60
+ import { authorizeCrmWriter, canReach } from "./task-routes.js";
61
+ /** What a member scoped to particular sites is told. */ export const COMPANY_DELETE_SCOPED_REFUSAL = 'Your access is limited to specific sites, so the contacts at this company ' + 'could not be read to unlink them. Ask an organization administrator to delete it.';
62
+ /**
63
+ * A contact's update when a company it names is deleted: the id out of the
64
+ * mirror, and out of every holder's facet that named it. Another holder's
65
+ * link to a different company is not read or written.
66
+ */ function companyDetachFields(contact, companyId) {
67
+ const update = {
68
+ [CONTACT_COMPANY_IDS_FIELD]: FieldValue.arrayRemove(companyId),
69
+ updatedAt: FieldValue.serverTimestamp()
70
+ };
71
+ for (const groupId of contactGroupsNamingCompany(contact, companyId)){
72
+ update[contactFacetPath(groupId, 'companyId')] = FieldValue.delete();
73
+ }
74
+ return update;
75
+ }
76
+ export const crmCompanyDeleteHandler = async (req, res)=>{
77
+ var _req_body;
78
+ if (req.method !== 'POST') {
79
+ res.setHeader('Allow', 'POST');
80
+ res.status(405).json({
81
+ error: 'Method not allowed'
82
+ });
83
+ return;
84
+ }
85
+ const body = (_req_body = req.body) != null ? _req_body : {};
86
+ const scope = readCrmRouteScope(body);
87
+ const companyId = typed(body['companyId'], 200);
88
+ if (!scope || !companyId || companyId.includes('/')) {
89
+ res.status(400).json({
90
+ error: 'Missing hostId or companyId'
91
+ });
92
+ return;
93
+ }
94
+ try {
95
+ const writer = await authorizeCrmWriter(req, scope, {
96
+ suiteAct: 'Deleting a company'
97
+ });
98
+ if (writer.ok === false) {
99
+ res.status(writer.status).json(writer.body);
100
+ return;
101
+ }
102
+ if (!(writer.staff || writer.level === 'org' || isOrgWideMember(writer.member))) {
103
+ res.status(403).json({
104
+ error: COMPANY_DELETE_SCOPED_REFUSAL
105
+ });
106
+ return;
107
+ }
108
+ const firestore = firebaseAdmin.app().firestore();
109
+ const orgRef = firestore.collection('orgs').doc(writer.orgId);
110
+ const companyRef = orgRef.collection(CRM_COLLECTIONS.companies).doc(companyId);
111
+ const company = await companyRef.get();
112
+ if (!company.exists || !canReach(writer, company.get('visibleTo'))) {
113
+ res.status(404).json({
114
+ error: 'That company no longer exists.'
115
+ });
116
+ return;
117
+ }
118
+ // One past the bound, so "more remain" is a fact from the probe row
119
+ // rather than a guess from a full page.
120
+ const probe = await orgRef.collection('contacts').where(CONTACT_COMPANY_IDS_FIELD, 'array-contains', companyId).limit(COMPANY_DETACH_LIMIT + 1).get();
121
+ const linked = probe.docs.slice(0, COMPANY_DETACH_LIMIT);
122
+ const moreRemain = probe.docs.length > COMPANY_DETACH_LIMIT;
123
+ if (linked.length) {
124
+ const batch = firestore.batch();
125
+ for (const snapshot of linked){
126
+ batch.update(snapshot.ref, companyDetachFields(snapshot.data(), companyId));
127
+ }
128
+ await batch.commit();
129
+ }
130
+ if (!moreRemain) await companyRef.delete();
131
+ const answer = {
132
+ ok: true,
133
+ deleted: !moreRemain,
134
+ detached: linked.length,
135
+ moreRemain
136
+ };
137
+ res.status(200).json(answer);
138
+ } catch (error) {
139
+ console.error('[crm] company-delete failed', companyId, error);
140
+ res.status(500).json({
141
+ error: 'The company could not be deleted.'
142
+ });
143
+ }
144
+ };
145
+
146
+ //# sourceMappingURL=company-delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/company-delete.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `POST /api/crm/company-delete` — unlink the contacts that name a company,\n * then delete it (AGL-2804).\n *\n * Body: `{ hostId, companyId }` under a site, or `{ orgId, companyId }` at\n * the organization level. Answers `CompanyDeleteResponse`.\n *\n * ## Why a route\n *\n * Firestore does not cascade. A company deleted on its own leaves every\n * contact at it naming a record that no longer exists: their page links to\n * nothing and the `companyIds` mirror the company list queries keeps\n * matching a ghost. So a delete is a detach pass and then a delete, and the\n * detach clears each holder's facet that named the company — which the rules\n * refuse a client, because they cannot tell a company link inside a facet\n * from an owner or a stage beside it.\n *\n * ## Bounded, and honest past the bound\n *\n * One pass unlinks at most `COMPANY_DETACH_LIMIT` contacts, a batch's worth.\n * A company past that is left standing with a pass's worth unlinked, and the\n * answer says more remain, so the next delete continues where this one\n * stopped. A company is never deleted with a link still on a contact.\n *\n * ## Who may call it\n *\n * A CRM writer (`authorizeCrmWriter`) whose reach is the whole organization.\n * The contacts are found by the mirror, and that query cannot carry a scope\n * clause beside its `array-contains`: a member scoped to particular sites\n * could not read them in the browser either, and the route does not unlink\n * people on behalf of someone who may not see them.\n *\n * ## The plan\n *\n * Companies are the CRM's, and the CRM is included from Starter (AGL-2851):\n * a workspace whose plan does not carry it is refused once the writer is\n * known, staff included, before anything is read or unlinked\n * (`suite-gate.ts`).\n */\n\nimport {\n CONTACT_COMPANY_IDS_FIELD,\n contactFacetPath,\n contactGroupsNamingCompany,\n CRM_COLLECTIONS,\n isOrgWideMember,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n COMPANY_DETACH_LIMIT,\n type CompanyDeleteResponse,\n} from '../model/company-delete-route'\nimport { typed } from './contact-profile'\nimport { readCrmRouteScope } from './org-caller'\nimport { authorizeCrmWriter, canReach } from './task-routes'\n\n/** What a member scoped to particular sites is told. */\nexport const COMPANY_DELETE_SCOPED_REFUSAL =\n 'Your access is limited to specific sites, so the contacts at this company ' +\n 'could not be read to unlink them. Ask an organization administrator to delete it.'\n\n/**\n * A contact's update when a company it names is deleted: the id out of the\n * mirror, and out of every holder's facet that named it. Another holder's\n * link to a different company is not read or written.\n */\nfunction companyDetachFields(\n contact: Record<string, unknown>,\n companyId: string,\n): Record<string, unknown> {\n const update: Record<string, unknown> = {\n [CONTACT_COMPANY_IDS_FIELD]: FieldValue.arrayRemove(companyId),\n updatedAt: FieldValue.serverTimestamp(),\n }\n for (const groupId of contactGroupsNamingCompany(contact, companyId)) {\n update[contactFacetPath(groupId, 'companyId')] = FieldValue.delete()\n }\n return update\n}\n\nexport const crmCompanyDeleteHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const body = (req.body ?? {}) as Record<string, unknown>\n const scope = readCrmRouteScope(body)\n const companyId = typed(body['companyId'], 200)\n if (!scope || !companyId || companyId.includes('/')) {\n res.status(400).json({ error: 'Missing hostId or companyId' })\n return\n }\n\n try {\n const writer = await authorizeCrmWriter(req, scope, { suiteAct: 'Deleting a company' })\n if (writer.ok === false) {\n res.status(writer.status).json(writer.body)\n return\n }\n if (!(writer.staff || writer.level === 'org' || isOrgWideMember(writer.member))) {\n res.status(403).json({ error: COMPANY_DELETE_SCOPED_REFUSAL })\n return\n }\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(writer.orgId)\n const companyRef = orgRef.collection(CRM_COLLECTIONS.companies).doc(companyId)\n const company = await companyRef.get()\n if (!company.exists || !canReach(writer, company.get('visibleTo'))) {\n res.status(404).json({ error: 'That company no longer exists.' })\n return\n }\n\n // One past the bound, so \"more remain\" is a fact from the probe row\n // rather than a guess from a full page.\n const probe = await orgRef\n .collection('contacts')\n .where(CONTACT_COMPANY_IDS_FIELD, 'array-contains', companyId)\n .limit(COMPANY_DETACH_LIMIT + 1)\n .get()\n const linked = probe.docs.slice(0, COMPANY_DETACH_LIMIT)\n const moreRemain = probe.docs.length > COMPANY_DETACH_LIMIT\n if (linked.length) {\n const batch = firestore.batch()\n for (const snapshot of linked) {\n batch.update(\n snapshot.ref,\n companyDetachFields(snapshot.data() as Record<string, unknown>, companyId),\n )\n }\n await batch.commit()\n }\n if (!moreRemain) await companyRef.delete()\n\n const answer: CompanyDeleteResponse = {\n ok: true,\n deleted: !moreRemain,\n detached: linked.length,\n moreRemain,\n }\n res.status(200).json(answer)\n } catch (error) {\n console.error('[crm] company-delete failed', companyId, error)\n res.status(500).json({ error: 'The company could not be deleted.' })\n }\n}\n"],"names":["CONTACT_COMPANY_IDS_FIELD","contactFacetPath","contactGroupsNamingCompany","CRM_COLLECTIONS","isOrgWideMember","firebaseAdmin","FieldValue","COMPANY_DETACH_LIMIT","typed","readCrmRouteScope","authorizeCrmWriter","canReach","COMPANY_DELETE_SCOPED_REFUSAL","companyDetachFields","contact","companyId","update","arrayRemove","updatedAt","serverTimestamp","groupId","delete","crmCompanyDeleteHandler","req","res","method","setHeader","status","json","error","body","scope","includes","writer","suiteAct","ok","staff","level","member","firestore","app","orgRef","collection","doc","orgId","companyRef","companies","company","get","exists","probe","where","limit","linked","docs","slice","moreRemain","length","batch","snapshot","ref","data","commit","answer","deleted","detached","console"],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * `POST /api/crm/contact-email-history` (AGL-2616): the campaign mail one
19
+ * contact was sent, as the timeline draws it.
20
+ *
21
+ * ## Why a server route
22
+ *
23
+ * The per-recipient delivery log — `emailDeliveries/{key}/messages` — is
24
+ * closed to every client by the rules, and rightly: it is keyed by an
25
+ * address hash across every org on the platform, and a row carries the
26
+ * recipient's address, the provider and the links they followed. What a
27
+ * contact's timeline needs is one slice of it — this person, this group's
28
+ * campaigns — projected to the fields a timeline shows. That projection is
29
+ * this route, and the authorization in front of it is the contacts read
30
+ * rule restated for the Admin SDK, which evaluates no rules of its own.
31
+ *
32
+ * ## What one open of a contact page costs
33
+ *
34
+ * Bounded by the PERSON, never by the campaign ledger: one keyed query down
35
+ * the person's own message subcollection, newest first and capped at
36
+ * {@link EMAIL_DELIVERY_READ_LIMIT}, then one `getAll` for the names of the
37
+ * distinct emails those rows name. Nothing here walks a campaign's
38
+ * recipients — that direction is the campaign report's, and is a
39
+ * collection-group query this route never issues.
40
+ *
41
+ * ## Only this group's campaigns
42
+ *
43
+ * The log holds every message ever sent to the address, from every site on
44
+ * the platform. The rows handed back are those a campaign on one of the
45
+ * reading group's sites sent — the `hostId` tag `campaign-send` stamps,
46
+ * filtered against the consent group's `hostIds` — so a sibling business
47
+ * sharing the same contact row never sees the mail another business sent
48
+ * the person, and transactional mail (a receipt, an invite, which carry no
49
+ * site) is not on a CRM timeline at all. The filter runs over the newest
50
+ * fifty messages, so a person mailed heavily by several orgs can have this
51
+ * group's older campaigns fall past the window; the response says what the
52
+ * window was.
53
+ */
54
+ import { type ContactCampaignEmail, type PluginApiHandler } from '@aglyn/aglyn/server';
55
+ import { type EmailDeliveryRecord } from '@aglyn/tenant-data-admin/server/email-delivery-log';
56
+ /** The route key, as `registerCrmConsoleApi` registers it. */
57
+ export declare const CONTACT_EMAIL_HISTORY_ROUTE = "crm/contact-email-history";
58
+ /** What the route answers with. */
59
+ export interface ContactEmailHistoryResponse {
60
+ ok: true;
61
+ /** Newest first, by the instant each was sent. */
62
+ emails: ContactCampaignEmail[];
63
+ /**
64
+ * True when the log could not be read, so an empty list means "unknown"
65
+ * rather than "nothing was sent" — the two lead a reader to opposite
66
+ * conclusions about the relationship.
67
+ */
68
+ lookupFailed: boolean;
69
+ /** How many of the person's newest messages the campaign filter ran over. */
70
+ limit: number;
71
+ }
72
+ /**
73
+ * One delivery-log row as the timeline reads it.
74
+ *
75
+ * Exported for the spec, and pure: it is the projection that decides what
76
+ * leaves the server — the address, the provider, the followed links and the
77
+ * bounce detail stay behind. `sentAtMs` prefers the provider's own `sent`
78
+ * instant and falls back to the first event seen, which the log guarantees
79
+ * on every row, so an entry can always be placed.
80
+ */
81
+ export declare function contactCampaignEmailFromDelivery(row: EmailDeliveryRecord, campaignName: string | null): ContactCampaignEmail | null;
82
+ export declare const contactEmailHistoryHandler: PluginApiHandler;
@@ -0,0 +1,264 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * `POST /api/crm/contact-email-history` (AGL-2616): the campaign mail one
19
+ * contact was sent, as the timeline draws it.
20
+ *
21
+ * ## Why a server route
22
+ *
23
+ * The per-recipient delivery log — `emailDeliveries/{key}/messages` — is
24
+ * closed to every client by the rules, and rightly: it is keyed by an
25
+ * address hash across every org on the platform, and a row carries the
26
+ * recipient's address, the provider and the links they followed. What a
27
+ * contact's timeline needs is one slice of it — this person, this group's
28
+ * campaigns — projected to the fields a timeline shows. That projection is
29
+ * this route, and the authorization in front of it is the contacts read
30
+ * rule restated for the Admin SDK, which evaluates no rules of its own.
31
+ *
32
+ * ## What one open of a contact page costs
33
+ *
34
+ * Bounded by the PERSON, never by the campaign ledger: one keyed query down
35
+ * the person's own message subcollection, newest first and capped at
36
+ * {@link EMAIL_DELIVERY_READ_LIMIT}, then one `getAll` for the names of the
37
+ * distinct emails those rows name. Nothing here walks a campaign's
38
+ * recipients — that direction is the campaign report's, and is a
39
+ * collection-group query this route never issues.
40
+ *
41
+ * ## Only this group's campaigns
42
+ *
43
+ * The log holds every message ever sent to the address, from every site on
44
+ * the platform. The rows handed back are those a campaign on one of the
45
+ * reading group's sites sent — the `hostId` tag `campaign-send` stamps,
46
+ * filtered against the consent group's `hostIds` — so a sibling business
47
+ * sharing the same contact row never sees the mail another business sent
48
+ * the person, and transactional mail (a receipt, an invite, which carry no
49
+ * site) is not on a CRM timeline at all. The filter runs over the newest
50
+ * fifty messages, so a person mailed heavily by several orgs can have this
51
+ * group's older campaigns fall past the window; the response says what the
52
+ * window was.
53
+ */ import { memberCanSee } from "@aglyn/aglyn/server";
54
+ import { consentGroupForSite, firebaseAdmin, getOrgForHost, memberHasOrgPermission, orgDataCollectionForHost, resolveOrgMembership } from "@aglyn/tenant-data-admin";
55
+ // The leaf, for the reason the webhook imports it from the leaf: a spec that
56
+ // mocks the admin barrel wholesale must not silently replace the reader.
57
+ import { EMAIL_DELIVERY_READ_LIMIT, readEmailDeliveryHistory } from "@aglyn/tenant-data-admin/server/email-delivery-log";
58
+ import { isRefusedIdToken } from "@aglyn/tenant-data-admin/server/id-token-refusal";
59
+ import { crmSuiteRefusal } from "./suite-gate.js";
60
+ /** The route key, as `registerCrmConsoleApi` registers it. */ export const CONTACT_EMAIL_HISTORY_ROUTE = 'crm/contact-email-history';
61
+ const refuse = (status, error)=>({
62
+ ok: false,
63
+ status,
64
+ error
65
+ });
66
+ /**
67
+ * Who is asking, and whether they may read this site's CRM at all — the
68
+ * rules' `canReadScopedPeople` restated, minus the per-document half that
69
+ * the handler asks against the contact once it has it.
70
+ *
71
+ * `data.manage` is the key, because it is the key the contacts read rule
72
+ * gates on: an org viewer holding no grant may open no contact in the
73
+ * console and may not read one here. Staff pass on the claim alone, as the
74
+ * rules admit them.
75
+ */ async function authorizeCrmReader(req, hostId) {
76
+ var _req_headers_authorization, _ref;
77
+ const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
78
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
79
+ if (!idToken) return refuse(401, 'Unauthenticated');
80
+ let decoded;
81
+ try {
82
+ decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
83
+ } catch (error) {
84
+ // A refused credential is the caller's 401; a failure to check one is
85
+ // ours and keeps a 5xx (AGL-2852).
86
+ if (isRefusedIdToken(error)) return refuse(401, 'Unauthenticated');
87
+ console.error('[crm] contact-email-history could not verify the reader', error);
88
+ return refuse(500, 'The sign-in could not be checked. Try again.');
89
+ }
90
+ const staff = decoded.staff === true;
91
+ const resolved = await getOrgForHost(hostId).catch(()=>null);
92
+ if (!resolved) {
93
+ return refuse(404, 'This site has no organization, so it has no CRM.');
94
+ }
95
+ const { orgId, org } = resolved;
96
+ const membership = await resolveOrgMembership(decoded.uid, orgId).catch(()=>null);
97
+ const member = (_ref = membership == null ? void 0 : membership.member) != null ? _ref : null;
98
+ if (!staff) {
99
+ const suspended = (member == null ? void 0 : member.orgSuspended) === true;
100
+ if (!member || suspended) {
101
+ return refuse(403, 'Your organization role does not allow reading the CRM.');
102
+ }
103
+ if (!await memberHasOrgPermission(orgId, member, 'data.manage')) {
104
+ return refuse(403, 'Your organization role does not allow reading the CRM.');
105
+ }
106
+ }
107
+ return {
108
+ ok: true,
109
+ uid: decoded.uid,
110
+ staff,
111
+ orgId,
112
+ org: org != null ? org : {},
113
+ member
114
+ };
115
+ }
116
+ /** A finite, positive instant, or nothing. */ const instant = (value)=>{
117
+ const parsed = Number(value);
118
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
119
+ };
120
+ /**
121
+ * One delivery-log row as the timeline reads it.
122
+ *
123
+ * Exported for the spec, and pure: it is the projection that decides what
124
+ * leaves the server — the address, the provider, the followed links and the
125
+ * bounce detail stay behind. `sentAtMs` prefers the provider's own `sent`
126
+ * instant and falls back to the first event seen, which the log guarantees
127
+ * on every row, so an entry can always be placed.
128
+ */ export function contactCampaignEmailFromDelivery(row, campaignName) {
129
+ var _row_timestamps, _instant, _row_subject, _row_openCount, _row_clickCount;
130
+ if (!row.campaignId || !row.hostId) return null;
131
+ const stamps = (_row_timestamps = row.timestamps) != null ? _row_timestamps : {};
132
+ const sentAtMs = (_instant = instant(stamps.sent)) != null ? _instant : instant(row.firstSeenAtMs);
133
+ if (!sentAtMs) return null;
134
+ const delivered = instant(stamps.delivered);
135
+ const opened = instant(stamps.opened);
136
+ const clicked = instant(stamps.clicked);
137
+ const bounced = instant(stamps.bounced);
138
+ const complained = instant(stamps.complained);
139
+ return _extends({
140
+ messageId: row.messageId,
141
+ hostId: row.hostId,
142
+ campaignId: row.campaignId,
143
+ campaignName,
144
+ subject: (_row_subject = row.subject) != null ? _row_subject : null,
145
+ sentAtMs
146
+ }, delivered ? {
147
+ deliveredAtMs: delivered
148
+ } : {}, opened ? {
149
+ openedAtMs: opened
150
+ } : {}, clicked ? {
151
+ clickedAtMs: clicked
152
+ } : {}, bounced ? {
153
+ bouncedAtMs: bounced
154
+ } : {}, complained ? {
155
+ complainedAtMs: complained
156
+ } : {}, {
157
+ openCount: Number((_row_openCount = row.openCount) != null ? _row_openCount : 0),
158
+ clickCount: Number((_row_clickCount = row.clickCount) != null ? _row_clickCount : 0)
159
+ });
160
+ }
161
+ /**
162
+ * The names of the emails these rows name, in one `getAll`.
163
+ *
164
+ * Keyed reads rather than a query: the ids are known, a `getAll` is one round
165
+ * trip however many there are, and it needs no index. An email the team has
166
+ * since deleted reads as `null`, which the timeline draws as the subject the
167
+ * person received with no report to link to.
168
+ */ async function readCampaignNames(rows) {
169
+ const names = new Map();
170
+ const firestore = firebaseAdmin.app().firestore();
171
+ const refs = [];
172
+ for (const row of rows){
173
+ const key = `${row.hostId}/${row.campaignId}`;
174
+ if (names.has(key)) continue;
175
+ names.set(key, null);
176
+ refs.push(firestore.collection('hosts').doc(String(row.hostId)).collection('campaigns').doc(String(row.campaignId)));
177
+ }
178
+ if (!refs.length) return names;
179
+ const snapshots = await firestore.getAll(...refs);
180
+ for (const snapshot of snapshots){
181
+ var _ref, _snapshot_get, _snapshot_get1;
182
+ var _snapshot_ref_parent_parent;
183
+ if (!snapshot.exists) continue;
184
+ const hostId = (_ref = (_snapshot_ref_parent_parent = snapshot.ref.parent.parent) == null ? void 0 : _snapshot_ref_parent_parent.id) != null ? _ref : '';
185
+ const name = String((_snapshot_get = snapshot.get('displayName')) != null ? _snapshot_get : '').trim() || String((_snapshot_get1 = snapshot.get('subject')) != null ? _snapshot_get1 : '').trim();
186
+ names.set(`${hostId}/${snapshot.id}`, name || null);
187
+ }
188
+ return names;
189
+ }
190
+ export const contactEmailHistoryHandler = async (req, res)=>{
191
+ var _ref, _ref1;
192
+ var _req_body, _req_body1;
193
+ if (req.method !== 'POST') {
194
+ res.setHeader('Allow', 'POST');
195
+ res.status(405).json({
196
+ error: 'Method not allowed'
197
+ });
198
+ return;
199
+ }
200
+ const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '').trim();
201
+ const contactId = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.contactId) != null ? _ref1 : '').trim();
202
+ if (!hostId || !contactId) {
203
+ res.status(400).json({
204
+ error: 'Missing hostId or contactId'
205
+ });
206
+ return;
207
+ }
208
+ const reader = await authorizeCrmReader(req, hostId);
209
+ if (reader.ok === false) {
210
+ res.status(reader.status).json({
211
+ error: reader.error
212
+ });
213
+ return;
214
+ }
215
+ // The plan after the reader, before any contact or log is read (AGL-2851).
216
+ const suite = crmSuiteRefusal(reader.org, "A contact's campaign email history");
217
+ if (suite) {
218
+ res.status(suite.status).json(suite.body);
219
+ return;
220
+ }
221
+ try {
222
+ var _snapshot_get;
223
+ /*
224
+ * The contact by id, then the per-document half of the read rule: the
225
+ * Admin SDK evaluates no rules, so a scoped member who can guess an id
226
+ * must be refused here exactly as their listener would refuse the row.
227
+ * "Not visible" and "not there" are one answer on purpose — a 403 would
228
+ * confirm the id exists.
229
+ */ const contactsRef = await orgDataCollectionForHost(hostId, 'contacts');
230
+ const snapshot = await contactsRef.doc(contactId).get();
231
+ const visibleTo = snapshot.exists ? snapshot.get('visibleTo') : undefined;
232
+ if (!snapshot.exists || !(reader.staff || memberCanSee(reader.member, visibleTo))) {
233
+ res.status(404).json({
234
+ error: 'Unknown contact'
235
+ });
236
+ return;
237
+ }
238
+ const email = String((_snapshot_get = snapshot.get('email')) != null ? _snapshot_get : '');
239
+ const group = await consentGroupForSite(hostId, reader.org);
240
+ const history = await readEmailDeliveryHistory(email, {
241
+ limit: EMAIL_DELIVERY_READ_LIMIT
242
+ });
243
+ const rows = history.rows.filter((row)=>Boolean(row.campaignId) && Boolean(row.hostId) && group.hostIds.includes(String(row.hostId)));
244
+ const names = await readCampaignNames(rows);
245
+ const emails = rows.map((row)=>{
246
+ var _names_get;
247
+ return contactCampaignEmailFromDelivery(row, (_names_get = names.get(`${row.hostId}/${row.campaignId}`)) != null ? _names_get : null);
248
+ }).filter((entry)=>entry !== null).sort((a, b)=>b.sentAtMs - a.sentAtMs);
249
+ const body = {
250
+ ok: true,
251
+ emails,
252
+ lookupFailed: history.lookupFailed,
253
+ limit: EMAIL_DELIVERY_READ_LIMIT
254
+ };
255
+ res.status(200).json(body);
256
+ } catch (error) {
257
+ console.error('[crm] contact-email-history failed', hostId, contactId, error);
258
+ res.status(500).json({
259
+ error: 'The campaign history could not be read'
260
+ });
261
+ }
262
+ };
263
+
264
+ //# sourceMappingURL=contact-email-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/contact-email-history.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `POST /api/crm/contact-email-history` (AGL-2616): the campaign mail one\n * contact was sent, as the timeline draws it.\n *\n * ## Why a server route\n *\n * The per-recipient delivery log — `emailDeliveries/{key}/messages` — is\n * closed to every client by the rules, and rightly: it is keyed by an\n * address hash across every org on the platform, and a row carries the\n * recipient's address, the provider and the links they followed. What a\n * contact's timeline needs is one slice of it — this person, this group's\n * campaigns — projected to the fields a timeline shows. That projection is\n * this route, and the authorization in front of it is the contacts read\n * rule restated for the Admin SDK, which evaluates no rules of its own.\n *\n * ## What one open of a contact page costs\n *\n * Bounded by the PERSON, never by the campaign ledger: one keyed query down\n * the person's own message subcollection, newest first and capped at\n * {@link EMAIL_DELIVERY_READ_LIMIT}, then one `getAll` for the names of the\n * distinct emails those rows name. Nothing here walks a campaign's\n * recipients — that direction is the campaign report's, and is a\n * collection-group query this route never issues.\n *\n * ## Only this group's campaigns\n *\n * The log holds every message ever sent to the address, from every site on\n * the platform. The rows handed back are those a campaign on one of the\n * reading group's sites sent — the `hostId` tag `campaign-send` stamps,\n * filtered against the consent group's `hostIds` — so a sibling business\n * sharing the same contact row never sees the mail another business sent\n * the person, and transactional mail (a receipt, an invite, which carry no\n * site) is not on a CRM timeline at all. The filter runs over the newest\n * fifty messages, so a person mailed heavily by several orgs can have this\n * group's older campaigns fall past the window; the response says what the\n * window was.\n */\n\nimport {\n type AglynOrgMember,\n type ContactCampaignEmail,\n memberCanSee,\n type PluginApiHandler,\n type PluginApiRequest,\n} from '@aglyn/aglyn/server'\nimport {\n consentGroupForSite,\n firebaseAdmin,\n getOrgForHost,\n memberHasOrgPermission,\n orgDataCollectionForHost,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\n// The leaf, for the reason the webhook imports it from the leaf: a spec that\n// mocks the admin barrel wholesale must not silently replace the reader.\nimport {\n EMAIL_DELIVERY_READ_LIMIT,\n type EmailDeliveryRecord,\n readEmailDeliveryHistory,\n} from '@aglyn/tenant-data-admin/server/email-delivery-log'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { crmSuiteRefusal } from './suite-gate'\n\n/** The route key, as `registerCrmConsoleApi` registers it. */\nexport const CONTACT_EMAIL_HISTORY_ROUTE = 'crm/contact-email-history'\n\n/** What the route answers with. */\nexport interface ContactEmailHistoryResponse {\n ok: true\n /** Newest first, by the instant each was sent. */\n emails: ContactCampaignEmail[]\n /**\n * True when the log could not be read, so an empty list means \"unknown\"\n * rather than \"nothing was sent\" — the two lead a reader to opposite\n * conclusions about the relationship.\n */\n lookupFailed: boolean\n /** How many of the person's newest messages the campaign filter ran over. */\n limit: number\n}\n\ntype Refusal = { ok: false; status: number; error: string }\n\ninterface Reader {\n ok: true\n uid: string\n staff: boolean\n orgId: string\n org: Record<string, unknown>\n member: Partial<AglynOrgMember> | null\n}\n\nconst refuse = (status: number, error: string): Refusal => ({\n ok: false,\n status,\n error,\n})\n\n/**\n * Who is asking, and whether they may read this site's CRM at all — the\n * rules' `canReadScopedPeople` restated, minus the per-document half that\n * the handler asks against the contact once it has it.\n *\n * `data.manage` is the key, because it is the key the contacts read rule\n * gates on: an org viewer holding no grant may open no contact in the\n * console and may not read one here. Staff pass on the claim alone, as the\n * rules admit them.\n */\nasync function authorizeCrmReader(\n req: PluginApiRequest,\n hostId: string,\n): Promise<Reader | Refusal> {\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return refuse(401, 'Unauthenticated')\n let decoded: { uid: string; staff?: unknown }\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // A refused credential is the caller's 401; a failure to check one is\n // ours and keeps a 5xx (AGL-2852).\n if (isRefusedIdToken(error)) return refuse(401, 'Unauthenticated')\n console.error('[crm] contact-email-history could not verify the reader', error)\n return refuse(500, 'The sign-in could not be checked. Try again.')\n }\n const staff = decoded.staff === true\n const resolved = await getOrgForHost(hostId).catch(() => null)\n if (!resolved) {\n return refuse(404, 'This site has no organization, so it has no CRM.')\n }\n const { orgId, org } = resolved\n const membership = await resolveOrgMembership(decoded.uid, orgId).catch(\n () => null,\n )\n const member = (membership?.member ?? null) as Partial<AglynOrgMember> | null\n if (!staff) {\n const suspended =\n (member as { orgSuspended?: boolean } | null)?.orgSuspended === true\n if (!member || suspended) {\n return refuse(403, 'Your organization role does not allow reading the CRM.')\n }\n if (!(await memberHasOrgPermission(orgId, member, 'data.manage'))) {\n return refuse(403, 'Your organization role does not allow reading the CRM.')\n }\n }\n return {\n ok: true,\n uid: decoded.uid,\n staff,\n orgId,\n org: (org ?? {}) as Record<string, unknown>,\n member,\n }\n}\n\n/** A finite, positive instant, or nothing. */\nconst instant = (value: unknown): number | undefined => {\n const parsed = Number(value)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined\n}\n\n/**\n * One delivery-log row as the timeline reads it.\n *\n * Exported for the spec, and pure: it is the projection that decides what\n * leaves the server — the address, the provider, the followed links and the\n * bounce detail stay behind. `sentAtMs` prefers the provider's own `sent`\n * instant and falls back to the first event seen, which the log guarantees\n * on every row, so an entry can always be placed.\n */\nexport function contactCampaignEmailFromDelivery(\n row: EmailDeliveryRecord,\n campaignName: string | null,\n): ContactCampaignEmail | null {\n if (!row.campaignId || !row.hostId) return null\n const stamps = row.timestamps ?? {}\n const sentAtMs = instant(stamps.sent) ?? instant(row.firstSeenAtMs)\n if (!sentAtMs) return null\n const delivered = instant(stamps.delivered)\n const opened = instant(stamps.opened)\n const clicked = instant(stamps.clicked)\n const bounced = instant(stamps.bounced)\n const complained = instant(stamps.complained)\n return {\n messageId: row.messageId,\n hostId: row.hostId,\n campaignId: row.campaignId,\n campaignName,\n subject: row.subject ?? null,\n sentAtMs,\n ...(delivered ? { deliveredAtMs: delivered } : {}),\n ...(opened ? { openedAtMs: opened } : {}),\n ...(clicked ? { clickedAtMs: clicked } : {}),\n ...(bounced ? { bouncedAtMs: bounced } : {}),\n ...(complained ? { complainedAtMs: complained } : {}),\n openCount: Number(row.openCount ?? 0),\n clickCount: Number(row.clickCount ?? 0),\n }\n}\n\n/**\n * The names of the emails these rows name, in one `getAll`.\n *\n * Keyed reads rather than a query: the ids are known, a `getAll` is one round\n * trip however many there are, and it needs no index. An email the team has\n * since deleted reads as `null`, which the timeline draws as the subject the\n * person received with no report to link to.\n */\nasync function readCampaignNames(\n rows: readonly EmailDeliveryRecord[],\n): Promise<Map<string, string | null>> {\n const names = new Map<string, string | null>()\n const firestore = firebaseAdmin.app().firestore()\n const refs: FirebaseFirestore.DocumentReference[] = []\n for (const row of rows) {\n const key = `${row.hostId}/${row.campaignId}`\n if (names.has(key)) continue\n names.set(key, null)\n refs.push(\n firestore\n .collection('hosts')\n .doc(String(row.hostId))\n .collection('campaigns')\n .doc(String(row.campaignId)),\n )\n }\n if (!refs.length) return names\n const snapshots = await firestore.getAll(...refs)\n for (const snapshot of snapshots) {\n if (!snapshot.exists) continue\n const hostId = snapshot.ref.parent.parent?.id ?? ''\n const name =\n String(snapshot.get('displayName') ?? '').trim() ||\n String(snapshot.get('subject') ?? '').trim()\n names.set(`${hostId}/${snapshot.id}`, name || null)\n }\n return names\n}\n\nexport const contactEmailHistoryHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ error: 'Method not allowed' })\n return\n }\n const hostId = String(req.body?.hostId ?? '').trim()\n const contactId = String(req.body?.contactId ?? '').trim()\n if (!hostId || !contactId) {\n res.status(400).json({ error: 'Missing hostId or contactId' })\n return\n }\n const reader = await authorizeCrmReader(req, hostId)\n if (reader.ok === false) {\n res.status(reader.status).json({ error: reader.error })\n return\n }\n // The plan after the reader, before any contact or log is read (AGL-2851).\n const suite = crmSuiteRefusal(reader.org, \"A contact's campaign email history\")\n if (suite) {\n res.status(suite.status).json(suite.body)\n return\n }\n try {\n /*\n * The contact by id, then the per-document half of the read rule: the\n * Admin SDK evaluates no rules, so a scoped member who can guess an id\n * must be refused here exactly as their listener would refuse the row.\n * \"Not visible\" and \"not there\" are one answer on purpose — a 403 would\n * confirm the id exists.\n */\n const contactsRef = await orgDataCollectionForHost(hostId, 'contacts')\n const snapshot = await contactsRef.doc(contactId).get()\n const visibleTo = snapshot.exists\n ? (snapshot.get('visibleTo') as string[] | undefined)\n : undefined\n if (!snapshot.exists || !(reader.staff || memberCanSee(reader.member, visibleTo))) {\n res.status(404).json({ error: 'Unknown contact' })\n return\n }\n const email = String(snapshot.get('email') ?? '')\n const group = await consentGroupForSite(hostId, reader.org)\n const history = await readEmailDeliveryHistory(email, {\n limit: EMAIL_DELIVERY_READ_LIMIT,\n })\n const rows = history.rows.filter(\n (row) =>\n Boolean(row.campaignId) &&\n Boolean(row.hostId) &&\n group.hostIds.includes(String(row.hostId)),\n )\n const names = await readCampaignNames(rows)\n const emails = rows\n .map((row) =>\n contactCampaignEmailFromDelivery(\n row,\n names.get(`${row.hostId}/${row.campaignId}`) ?? null,\n ),\n )\n .filter((entry): entry is ContactCampaignEmail => entry !== null)\n .sort((a, b) => b.sentAtMs - a.sentAtMs)\n const body: ContactEmailHistoryResponse = {\n ok: true,\n emails,\n lookupFailed: history.lookupFailed,\n limit: EMAIL_DELIVERY_READ_LIMIT,\n }\n res.status(200).json(body)\n } catch (error) {\n console.error('[crm] contact-email-history failed', hostId, contactId, error)\n res.status(500).json({ error: 'The campaign history could not be read' })\n }\n}\n"],"names":["memberCanSee","consentGroupForSite","firebaseAdmin","getOrgForHost","memberHasOrgPermission","orgDataCollectionForHost","resolveOrgMembership","EMAIL_DELIVERY_READ_LIMIT","readEmailDeliveryHistory","isRefusedIdToken","crmSuiteRefusal","CONTACT_EMAIL_HISTORY_ROUTE","refuse","status","error","ok","authorizeCrmReader","req","hostId","authorization","String","headers","idToken","startsWith","slice","length","undefined","decoded","app","auth","verifyIdToken","console","staff","resolved","catch","orgId","org","membership","uid","member","suspended","orgSuspended","instant","value","parsed","Number","isFinite","contactCampaignEmailFromDelivery","row","campaignName","campaignId","stamps","timestamps","sentAtMs","sent","firstSeenAtMs","delivered","opened","clicked","bounced","complained","messageId","subject","deliveredAtMs","openedAtMs","clickedAtMs","bouncedAtMs","complainedAtMs","openCount","clickCount","readCampaignNames","rows","names","Map","firestore","refs","key","has","set","push","collection","doc","snapshots","getAll","snapshot","exists","ref","parent","id","name","get","trim","contactEmailHistoryHandler","res","method","setHeader","json","body","contactId","reader","suite","contactsRef","visibleTo","email","group","history","limit","filter","Boolean","hostIds","includes","emails","map","entry","sort","a","b","lookupFailed"],"mappings":"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"}