@aglyn/plugins-crm 1.0.0-beta.221 → 1.0.0-beta.223

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 (541) hide show
  1. package/README.md +2 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/activity-list.js +12 -5
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/companies-bulk-bar.d.ts +1 -4
  6. package/src/lib/components/companies-bulk-bar.js +7 -20
  7. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  8. package/src/lib/components/companies-section.d.ts +3 -3
  9. package/src/lib/components/companies-section.js +149 -46
  10. package/src/lib/components/companies-section.js.map +1 -1
  11. package/src/lib/components/company-edit-drawer.js +281 -20
  12. package/src/lib/components/company-edit-drawer.js.map +1 -1
  13. package/src/lib/components/company-properties-card.js +99 -8
  14. package/src/lib/components/company-properties-card.js.map +1 -1
  15. package/src/lib/components/contact-detail-page.js +3 -1
  16. package/src/lib/components/contact-detail-page.js.map +1 -1
  17. package/src/lib/components/contact-merge-dialog.js +33 -10
  18. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  19. package/src/lib/components/contact-properties-card.d.ts +0 -69
  20. package/src/lib/components/contact-properties-card.js +350 -60
  21. package/src/lib/components/contact-properties-card.js.map +1 -1
  22. package/src/lib/components/contact-reports-to-field.d.ts +46 -0
  23. package/src/lib/components/contact-reports-to-field.js +67 -0
  24. package/src/lib/components/contact-reports-to-field.js.map +1 -0
  25. package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
  26. package/src/lib/components/contacts-bulk-bar.js +7 -19
  27. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  28. package/src/lib/components/contacts-section.js +46 -33
  29. package/src/lib/components/contacts-section.js.map +1 -1
  30. package/src/lib/components/crm-call-actions.d.ts +10 -0
  31. package/src/lib/components/crm-call-actions.js +26 -9
  32. package/src/lib/components/crm-call-actions.js.map +1 -1
  33. package/src/lib/components/crm-next-activity-column.js +1 -1
  34. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  35. package/src/lib/components/crm-record-picker.d.ts +15 -0
  36. package/src/lib/components/crm-record-picker.js +16 -10
  37. package/src/lib/components/crm-record-picker.js.map +1 -1
  38. package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
  39. package/src/lib/components/crm-transfer-buttons.js +167 -0
  40. package/src/lib/components/crm-transfer-buttons.js.map +1 -0
  41. package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
  42. package/src/lib/components/deal-contact-roles-card.js +368 -0
  43. package/src/lib/components/deal-contact-roles-card.js.map +1 -0
  44. package/src/lib/components/deal-detail-page.d.ts +3 -2
  45. package/src/lib/components/deal-detail-page.js +25 -6
  46. package/src/lib/components/deal-detail-page.js.map +1 -1
  47. package/src/lib/components/deal-edit-drawer.d.ts +10 -0
  48. package/src/lib/components/deal-edit-drawer.js +219 -10
  49. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  50. package/src/lib/components/deal-properties-card.d.ts +7 -0
  51. package/src/lib/components/deal-properties-card.js +34 -3
  52. package/src/lib/components/deal-properties-card.js.map +1 -1
  53. package/src/lib/components/deal-stage-card.d.ts +4 -0
  54. package/src/lib/components/deal-stage-card.js +10 -2
  55. package/src/lib/components/deal-stage-card.js.map +1 -1
  56. package/src/lib/components/deals-bulk-bar.d.ts +0 -3
  57. package/src/lib/components/deals-bulk-bar.js +7 -20
  58. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  59. package/src/lib/components/deals-section.d.ts +3 -3
  60. package/src/lib/components/deals-section.js +123 -63
  61. package/src/lib/components/deals-section.js.map +1 -1
  62. package/src/lib/components/fields-section.js +31 -17
  63. package/src/lib/components/fields-section.js.map +1 -1
  64. package/src/lib/components/lead-convert-dialog.d.ts +4 -0
  65. package/src/lib/components/lead-convert-dialog.js +23 -1
  66. package/src/lib/components/lead-convert-dialog.js.map +1 -1
  67. package/src/lib/components/lead-detail-page.js +8 -2
  68. package/src/lib/components/lead-detail-page.js.map +1 -1
  69. package/src/lib/components/lead-profile-fields.d.ts +71 -0
  70. package/src/lib/components/lead-profile-fields.js +386 -0
  71. package/src/lib/components/lead-profile-fields.js.map +1 -0
  72. package/src/lib/components/lead-properties-card.d.ts +2 -1
  73. package/src/lib/components/lead-properties-card.js +85 -176
  74. package/src/lib/components/lead-properties-card.js.map +1 -1
  75. package/src/lib/components/lead-source-select.d.ts +4 -23
  76. package/src/lib/components/lead-source-select.js +9 -73
  77. package/src/lib/components/lead-source-select.js.map +1 -1
  78. package/src/lib/components/lead-status-chip.d.ts +9 -3
  79. package/src/lib/components/lead-status-chip.js +8 -4
  80. package/src/lib/components/lead-status-chip.js.map +1 -1
  81. package/src/lib/components/lead-status-options.d.ts +20 -0
  82. package/src/lib/components/lead-status-options.js +51 -0
  83. package/src/lib/components/lead-status-options.js.map +1 -0
  84. package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
  85. package/src/lib/components/lead-unqualify-dialog.js +50 -18
  86. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  87. package/src/lib/components/leads-bulk-bar.d.ts +0 -3
  88. package/src/lib/components/leads-bulk-bar.js +29 -29
  89. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  90. package/src/lib/components/leads-section.js +120 -68
  91. package/src/lib/components/leads-section.js.map +1 -1
  92. package/src/lib/components/linked-deals-card.d.ts +4 -0
  93. package/src/lib/components/linked-deals-card.js +47 -3
  94. package/src/lib/components/linked-deals-card.js.map +1 -1
  95. package/src/lib/components/log-activity-dialog.js +43 -3
  96. package/src/lib/components/log-activity-dialog.js.map +1 -1
  97. package/src/lib/components/new-contact-drawer.d.ts +8 -0
  98. package/src/lib/components/new-contact-drawer.js +108 -6
  99. package/src/lib/components/new-contact-drawer.js.map +1 -1
  100. package/src/lib/components/new-lead-drawer.d.ts +9 -2
  101. package/src/lib/components/new-lead-drawer.js +88 -121
  102. package/src/lib/components/new-lead-drawer.js.map +1 -1
  103. package/src/lib/components/picklist-select.d.ts +36 -0
  104. package/src/lib/components/picklist-select.js +114 -0
  105. package/src/lib/components/picklist-select.js.map +1 -0
  106. package/src/lib/components/picklist-values-card.d.ts +34 -0
  107. package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
  108. package/src/lib/components/picklist-values-card.js.map +1 -0
  109. package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
  110. package/src/lib/components/pipeline-stages-dialog.js +26 -4
  111. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  112. package/src/lib/components/pipelines-dialog.js +15 -6
  113. package/src/lib/components/pipelines-dialog.js.map +1 -1
  114. package/src/lib/components/recent-activity-feed.js +20 -5
  115. package/src/lib/components/recent-activity-feed.js.map +1 -1
  116. package/src/lib/components/recipes-card.js +4 -2
  117. package/src/lib/components/recipes-card.js.map +1 -1
  118. package/src/lib/components/record-tasks-card.js +15 -8
  119. package/src/lib/components/record-tasks-card.js.map +1 -1
  120. package/src/lib/components/reports/forecast-card.d.ts +6 -0
  121. package/src/lib/components/reports/forecast-card.js +101 -3
  122. package/src/lib/components/reports/forecast-card.js.map +1 -1
  123. package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
  124. package/src/lib/components/reports/lead-funnel-card.js +9 -5
  125. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  126. package/src/lib/components/reports/lead-source-card.d.ts +23 -0
  127. package/src/lib/components/reports/lead-source-card.js +318 -0
  128. package/src/lib/components/reports/lead-source-card.js.map +1 -0
  129. package/src/lib/components/reports/pipeline-card.js +2 -1
  130. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  131. package/src/lib/components/reports/report-export.js +1 -1
  132. package/src/lib/components/reports/report-export.js.map +1 -1
  133. package/src/lib/components/reports-section.js +8 -0
  134. package/src/lib/components/reports-section.js.map +1 -1
  135. package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
  136. package/src/lib/components/sharing-rule-drawer.js +108 -19
  137. package/src/lib/components/sharing-rule-drawer.js.map +1 -1
  138. package/src/lib/components/sharing-rules-card.js +11 -3
  139. package/src/lib/components/sharing-rules-card.js.map +1 -1
  140. package/src/lib/components/task-cells.d.ts +9 -1
  141. package/src/lib/components/task-cells.js +9 -4
  142. package/src/lib/components/task-cells.js.map +1 -1
  143. package/src/lib/components/task-edit-drawer.js +111 -43
  144. package/src/lib/components/task-edit-drawer.js.map +1 -1
  145. package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
  146. package/src/lib/components/tasks-bulk-bar.js +7 -20
  147. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
  148. package/src/lib/components/tasks-calendar.d.ts +3 -0
  149. package/src/lib/components/tasks-calendar.js +8 -1
  150. package/src/lib/components/tasks-calendar.js.map +1 -1
  151. package/src/lib/components/tasks-section.d.ts +3 -3
  152. package/src/lib/components/tasks-section.js +58 -43
  153. package/src/lib/components/tasks-section.js.map +1 -1
  154. package/src/lib/constants/api-routes.d.ts +2 -9
  155. package/src/lib/constants/api-routes.js +1 -7
  156. package/src/lib/constants/api-routes.js.map +1 -1
  157. package/src/lib/constants/bundle-common.d.ts +6 -0
  158. package/src/lib/constants/bundle-common.js +11 -0
  159. package/src/lib/constants/bundle-common.js.map +1 -1
  160. package/src/lib/constants/company-filters.d.ts +14 -0
  161. package/src/lib/constants/company-filters.js +51 -1
  162. package/src/lib/constants/company-filters.js.map +1 -1
  163. package/src/lib/constants/contact-filters.d.ts +2 -0
  164. package/src/lib/constants/contact-filters.js +18 -1
  165. package/src/lib/constants/contact-filters.js.map +1 -1
  166. package/src/lib/constants/deal-filters.d.ts +22 -1
  167. package/src/lib/constants/deal-filters.js +67 -4
  168. package/src/lib/constants/deal-filters.js.map +1 -1
  169. package/src/lib/declarations.console-server.d.ts +3 -0
  170. package/src/lib/declarations.console-server.js +35 -0
  171. package/src/lib/declarations.console-server.js.map +1 -1
  172. package/src/lib/declarations.d.ts +26 -0
  173. package/src/lib/declarations.js +35 -0
  174. package/src/lib/declarations.js.map +1 -0
  175. package/src/lib/declarations.server.d.ts +11 -0
  176. package/src/lib/declarations.server.js +97 -1
  177. package/src/lib/declarations.server.js.map +1 -1
  178. package/src/lib/hooks/use-company-picklists.d.ts +15 -0
  179. package/src/lib/hooks/use-company-picklists.js +55 -0
  180. package/src/lib/hooks/use-company-picklists.js.map +1 -0
  181. package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
  182. package/src/lib/hooks/use-crm-picklist.js +53 -0
  183. package/src/lib/hooks/use-crm-picklist.js.map +1 -0
  184. package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
  185. package/src/lib/hooks/use-crm-record-names.js +45 -19
  186. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  187. package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
  188. package/src/lib/hooks/use-crm-task-picklists.js +45 -0
  189. package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
  190. package/src/lib/hooks/use-deals.d.ts +14 -0
  191. package/src/lib/hooks/use-deals.js +29 -0
  192. package/src/lib/hooks/use-deals.js.map +1 -1
  193. package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
  194. package/src/lib/hooks/use-lead-picklists.js +46 -0
  195. package/src/lib/hooks/use-lead-picklists.js.map +1 -0
  196. package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
  197. package/src/lib/hooks/use-lead-source-picklist.js +12 -22
  198. package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
  199. package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
  200. package/src/lib/hooks/use-lead-status-picklist.js +40 -0
  201. package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
  202. package/src/lib/model/companies.d.ts +32 -3
  203. package/src/lib/model/companies.js +133 -16
  204. package/src/lib/model/companies.js.map +1 -1
  205. package/src/lib/model/contact-merge.d.ts +27 -3
  206. package/src/lib/model/contact-merge.js +81 -7
  207. package/src/lib/model/contact-merge.js.map +1 -1
  208. package/src/lib/model/contact-record.d.ts +17 -0
  209. package/src/lib/model/contact-record.js +17 -2
  210. package/src/lib/model/contact-record.js.map +1 -1
  211. package/src/lib/model/contact-update.d.ts +31 -2
  212. package/src/lib/model/contact-update.js +14 -0
  213. package/src/lib/model/contact-update.js.map +1 -1
  214. package/src/lib/model/crm-list-query.d.ts +5 -0
  215. package/src/lib/model/crm-list-query.js +8 -0
  216. package/src/lib/model/crm-list-query.js.map +1 -1
  217. package/src/lib/model/crm-next-activity.d.ts +107 -0
  218. package/src/lib/model/crm-next-activity.js +140 -0
  219. package/src/lib/model/crm-next-activity.js.map +1 -0
  220. package/src/lib/model/crm-recipes.d.ts +40 -0
  221. package/src/lib/model/crm-recipes.js +259 -0
  222. package/src/lib/model/crm-recipes.js.map +1 -0
  223. package/src/lib/model/crm-record-counts.d.ts +36 -0
  224. package/src/lib/model/crm-record-counts.js +54 -0
  225. package/src/lib/model/crm-record-counts.js.map +1 -0
  226. package/src/lib/model/crm-record-lists.d.ts +23 -0
  227. package/src/lib/model/crm-record-lists.js +161 -0
  228. package/src/lib/model/crm-record-lists.js.map +1 -0
  229. package/src/lib/model/crm-reports.d.ts +73 -3
  230. package/src/lib/model/crm-reports.js +120 -1
  231. package/src/lib/model/crm-reports.js.map +1 -1
  232. package/src/lib/model/crm-sharing.d.ts +21 -3
  233. package/src/lib/model/crm-sharing.js +27 -6
  234. package/src/lib/model/crm-sharing.js.map +1 -1
  235. package/src/lib/model/crm-task-reminders.d.ts +152 -0
  236. package/src/lib/model/crm-task-reminders.js +161 -0
  237. package/src/lib/model/crm-task-reminders.js.map +1 -0
  238. package/src/lib/model/deal-board-model.d.ts +8 -2
  239. package/src/lib/model/deal-board-model.js +15 -2
  240. package/src/lib/model/deal-board-model.js.map +1 -1
  241. package/src/lib/model/deal-contact-roles.d.ts +45 -0
  242. package/src/lib/model/deal-contact-roles.js +73 -0
  243. package/src/lib/model/deal-contact-roles.js.map +1 -0
  244. package/src/lib/model/deal-form-model.d.ts +56 -0
  245. package/src/lib/model/deal-form-model.js +79 -7
  246. package/src/lib/model/deal-form-model.js.map +1 -1
  247. package/src/lib/model/download-text-file.d.ts +23 -0
  248. package/src/lib/model/download-text-file.js +34 -0
  249. package/src/lib/model/download-text-file.js.map +1 -0
  250. package/src/lib/model/lead-filters.d.ts +36 -2
  251. package/src/lib/model/lead-filters.js +123 -2
  252. package/src/lib/model/lead-filters.js.map +1 -1
  253. package/src/lib/model/lead-source-direction.d.ts +38 -0
  254. package/src/lib/model/lead-source-direction.js +52 -0
  255. package/src/lib/model/lead-source-direction.js.map +1 -0
  256. package/src/lib/model/next-activity.d.ts +1 -1
  257. package/src/lib/model/next-activity.js.map +1 -1
  258. package/src/lib/model/picklist-values.d.ts +119 -0
  259. package/src/lib/model/picklist-values.js +256 -0
  260. package/src/lib/model/picklist-values.js.map +1 -0
  261. package/src/lib/model/record-facts-disclosed.d.ts +81 -0
  262. package/src/lib/model/record-facts-disclosed.js +281 -0
  263. package/src/lib/model/record-facts-disclosed.js.map +1 -0
  264. package/src/lib/model/record-facts.d.ts +215 -34
  265. package/src/lib/model/record-facts.js +307 -128
  266. package/src/lib/model/record-facts.js.map +1 -1
  267. package/src/lib/model/task-picklists.d.ts +52 -0
  268. package/src/lib/model/task-picklists.js +83 -0
  269. package/src/lib/model/task-picklists.js.map +1 -0
  270. package/src/lib/model/task-routes.d.ts +13 -0
  271. package/src/lib/model/task-routes.js +23 -4
  272. package/src/lib/model/task-routes.js.map +1 -1
  273. package/src/lib/plan-entitlements.js +6 -4
  274. package/src/lib/plan-entitlements.js.map +1 -1
  275. package/src/lib/plugin.js +17 -0
  276. package/src/lib/plugin.js.map +1 -1
  277. package/src/lib/server/api-v1/contacts.js +220 -14
  278. package/src/lib/server/api-v1/contacts.js.map +1 -1
  279. package/src/lib/server/api-v1/crm-activities.js +14 -1
  280. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  281. package/src/lib/server/api-v1/crm-companies.js +123 -14
  282. package/src/lib/server/api-v1/crm-companies.js.map +1 -1
  283. package/src/lib/server/api-v1/crm-deals.js +291 -26
  284. package/src/lib/server/api-v1/crm-deals.js.map +1 -1
  285. package/src/lib/server/api-v1/crm-leads.js +144 -38
  286. package/src/lib/server/api-v1/crm-leads.js.map +1 -1
  287. package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
  288. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  289. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  290. package/src/lib/server/api-v1/crm-tasks.js +78 -34
  291. package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
  292. package/src/lib/server/api-v1/openapi.d.ts +0 -11
  293. package/src/lib/server/api-v1/openapi.js +171 -16
  294. package/src/lib/server/api-v1/openapi.js.map +1 -1
  295. package/src/lib/server/assign-contact-owner.d.ts +188 -0
  296. package/src/lib/server/assign-contact-owner.js +446 -0
  297. package/src/lib/server/assign-contact-owner.js.map +1 -0
  298. package/src/lib/server/associate-company-by-domain.d.ts +70 -0
  299. package/src/lib/server/associate-company-by-domain.js +153 -0
  300. package/src/lib/server/associate-company-by-domain.js.map +1 -0
  301. package/src/lib/server/automation-steps.d.ts +140 -0
  302. package/src/lib/server/automation-steps.js +669 -0
  303. package/src/lib/server/automation-steps.js.map +1 -0
  304. package/src/lib/server/capture-contact.d.ts +17 -0
  305. package/src/lib/server/capture-contact.js +95 -26
  306. package/src/lib/server/capture-contact.js.map +1 -1
  307. package/src/lib/server/capture-host-contact.d.ts +104 -0
  308. package/src/lib/server/capture-host-contact.js +151 -0
  309. package/src/lib/server/capture-host-contact.js.map +1 -0
  310. package/src/lib/server/company-children.d.ts +25 -0
  311. package/src/lib/server/company-children.js +42 -0
  312. package/src/lib/server/company-children.js.map +1 -0
  313. package/src/lib/server/company-delete.d.ts +18 -0
  314. package/src/lib/server/company-delete.js +44 -21
  315. package/src/lib/server/company-delete.js.map +1 -1
  316. package/src/lib/server/contact-email-history.js +21 -19
  317. package/src/lib/server/contact-email-history.js.map +1 -1
  318. package/src/lib/server/contact-remove.js +27 -2
  319. package/src/lib/server/contact-remove.js.map +1 -1
  320. package/src/lib/server/contact-reports-to.d.ts +61 -0
  321. package/src/lib/server/contact-reports-to.js +114 -0
  322. package/src/lib/server/contact-reports-to.js.map +1 -0
  323. package/src/lib/server/contact-update.d.ts +42 -1
  324. package/src/lib/server/contact-update.js +204 -23
  325. package/src/lib/server/contact-update.js.map +1 -1
  326. package/src/lib/server/convert-host-lead.d.ts +46 -1
  327. package/src/lib/server/convert-host-lead.js +173 -15
  328. package/src/lib/server/convert-host-lead.js.map +1 -1
  329. package/src/lib/server/convert-open-lead.js +12 -5
  330. package/src/lib/server/convert-open-lead.js.map +1 -1
  331. package/src/lib/server/crm-email-activity.d.ts +118 -0
  332. package/src/lib/server/crm-email-activity.js +148 -0
  333. package/src/lib/server/crm-email-activity.js.map +1 -0
  334. package/src/lib/server/crm-inbound-email.js +1 -1
  335. package/src/lib/server/crm-inbound-email.js.map +1 -1
  336. package/src/lib/server/crm-next-activity.d.ts +74 -0
  337. package/src/lib/server/crm-next-activity.js +194 -0
  338. package/src/lib/server/crm-next-activity.js.map +1 -0
  339. package/src/lib/server/crm-record-indexes.d.ts +45 -0
  340. package/src/lib/server/crm-record-indexes.js +78 -0
  341. package/src/lib/server/crm-record-indexes.js.map +1 -0
  342. package/src/lib/server/crm-sharing.d.ts +9 -1
  343. package/src/lib/server/crm-sharing.js +23 -3
  344. package/src/lib/server/crm-sharing.js.map +1 -1
  345. package/src/lib/server/deal-contact-roles.d.ts +27 -0
  346. package/src/lib/server/deal-contact-roles.js +89 -0
  347. package/src/lib/server/deal-contact-roles.js.map +1 -0
  348. package/src/lib/server/email-send.js +2 -1
  349. package/src/lib/server/email-send.js.map +1 -1
  350. package/src/lib/server/lead-convert.d.ts +5 -0
  351. package/src/lib/server/lead-convert.js +19 -3
  352. package/src/lib/server/lead-convert.js.map +1 -1
  353. package/src/lib/server/lead-create.d.ts +17 -2
  354. package/src/lib/server/lead-create.js +46 -14
  355. package/src/lib/server/lead-create.js.map +1 -1
  356. package/src/lib/server/lead-nurturing.d.ts +6 -0
  357. package/src/lib/server/lead-nurturing.js +57 -20
  358. package/src/lib/server/lead-nurturing.js.map +1 -1
  359. package/src/lib/server/lead-picklists.d.ts +35 -0
  360. package/src/lib/server/lead-picklists.js +52 -0
  361. package/src/lib/server/lead-picklists.js.map +1 -0
  362. package/src/lib/server/lead-source-picklist.d.ts +1 -13
  363. package/src/lib/server/lead-source-picklist.js +6 -33
  364. package/src/lib/server/lead-source-picklist.js.map +1 -1
  365. package/src/lib/server/merge-contacts.js +23 -5
  366. package/src/lib/server/merge-contacts.js.map +1 -1
  367. package/src/lib/server/next-activity-routes.js +2 -1
  368. package/src/lib/server/next-activity-routes.js.map +1 -1
  369. package/src/lib/server/person-eraser.d.ts +53 -0
  370. package/src/lib/server/person-eraser.js +149 -0
  371. package/src/lib/server/person-eraser.js.map +1 -0
  372. package/src/lib/server/person-records.d.ts +69 -0
  373. package/src/lib/server/person-records.js +317 -0
  374. package/src/lib/server/person-records.js.map +1 -0
  375. package/src/lib/server/person-refund.d.ts +97 -0
  376. package/src/lib/server/person-refund.js +221 -0
  377. package/src/lib/server/person-refund.js.map +1 -0
  378. package/src/lib/server/picklist-values.d.ts +21 -0
  379. package/src/lib/server/picklist-values.js +308 -0
  380. package/src/lib/server/picklist-values.js.map +1 -0
  381. package/src/lib/server/read-picklist.d.ts +44 -0
  382. package/src/lib/server/read-picklist.js +65 -0
  383. package/src/lib/server/read-picklist.js.map +1 -0
  384. package/src/lib/server/recipe-routes.js +22 -8
  385. package/src/lib/server/recipe-routes.js.map +1 -1
  386. package/src/lib/server/record-email-state.js +7 -1
  387. package/src/lib/server/record-email-state.js.map +1 -1
  388. package/src/lib/server/record-facts.d.ts +3 -0
  389. package/src/lib/server/record-facts.js +233 -31
  390. package/src/lib/server/record-facts.js.map +1 -1
  391. package/src/lib/server/record-origin.d.ts +27 -0
  392. package/src/lib/server/record-origin.js +104 -0
  393. package/src/lib/server/record-origin.js.map +1 -0
  394. package/src/lib/server/record-timeline.js +40 -3
  395. package/src/lib/server/record-timeline.js.map +1 -1
  396. package/src/lib/server/suite-gate.js +4 -3
  397. package/src/lib/server/suite-gate.js.map +1 -1
  398. package/src/lib/server/task-reminders-route.js +2 -1
  399. package/src/lib/server/task-reminders-route.js.map +1 -1
  400. package/src/lib/server/task-routes.js +33 -17
  401. package/src/lib/server/task-routes.js.map +1 -1
  402. package/src/lib/server-deal-stage.d.ts +5 -1
  403. package/src/lib/server-deal-stage.js +10 -5
  404. package/src/lib/server-deal-stage.js.map +1 -1
  405. package/src/lib/server.d.ts +8 -3
  406. package/src/lib/server.js +47 -43
  407. package/src/lib/server.js.map +1 -1
  408. package/src/lib/testing/memory-firestore.d.ts +55 -0
  409. package/src/lib/testing/memory-firestore.js +335 -0
  410. package/src/lib/testing/memory-firestore.js.map +1 -0
  411. package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
  412. package/src/lib/testing/stand-in-email-send-index.js +49 -0
  413. package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
  414. package/src/lib/transfer/activities.d.ts +30 -0
  415. package/src/lib/transfer/activities.js +132 -0
  416. package/src/lib/transfer/activities.js.map +1 -0
  417. package/src/lib/transfer/aliases.d.ts +22 -0
  418. package/src/lib/transfer/aliases.js +1342 -0
  419. package/src/lib/transfer/aliases.js.map +1 -0
  420. package/src/lib/transfer/common.d.ts +222 -0
  421. package/src/lib/transfer/common.js +832 -0
  422. package/src/lib/transfer/common.js.map +1 -0
  423. package/src/lib/transfer/companies.d.ts +43 -0
  424. package/src/lib/transfer/companies.js +549 -0
  425. package/src/lib/transfer/companies.js.map +1 -0
  426. package/src/lib/transfer/contacts.d.ts +31 -0
  427. package/src/lib/transfer/contacts.js +727 -0
  428. package/src/lib/transfer/contacts.js.map +1 -0
  429. package/src/lib/transfer/deals.d.ts +66 -0
  430. package/src/lib/transfer/deals.js +698 -0
  431. package/src/lib/transfer/deals.js.map +1 -0
  432. package/src/lib/transfer/email-templates-package.d.ts +40 -0
  433. package/src/lib/transfer/email-templates-package.js +76 -0
  434. package/src/lib/transfer/email-templates-package.js.map +1 -0
  435. package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
  436. package/src/lib/transfer/email-templates-package.server.js +176 -0
  437. package/src/lib/transfer/email-templates-package.server.js.map +1 -0
  438. package/src/lib/transfer/fields.d.ts +102 -0
  439. package/src/lib/transfer/fields.js +1755 -0
  440. package/src/lib/transfer/fields.js.map +1 -0
  441. package/src/lib/transfer/leads.d.ts +30 -0
  442. package/src/lib/transfer/leads.js +582 -0
  443. package/src/lib/transfer/leads.js.map +1 -0
  444. package/src/lib/transfer/pipelines.d.ts +21 -0
  445. package/src/lib/transfer/pipelines.js +172 -0
  446. package/src/lib/transfer/pipelines.js.map +1 -0
  447. package/src/lib/transfer/register.d.ts +47 -0
  448. package/src/lib/transfer/register.js +277 -0
  449. package/src/lib/transfer/register.js.map +1 -0
  450. package/src/lib/transfer/tasks.d.ts +30 -0
  451. package/src/lib/transfer/tasks.js +418 -0
  452. package/src/lib/transfer/tasks.js.map +1 -0
  453. package/src/lib/transfer/ui.d.ts +20 -0
  454. package/src/lib/transfer/ui.js +42 -0
  455. package/src/lib/transfer/ui.js.map +1 -0
  456. package/src/lib/components/company-import-drawer.d.ts +0 -42
  457. package/src/lib/components/company-import-drawer.js +0 -99
  458. package/src/lib/components/company-import-drawer.js.map +0 -1
  459. package/src/lib/components/contact-import-drawer.d.ts +0 -46
  460. package/src/lib/components/contact-import-drawer.js +0 -149
  461. package/src/lib/components/contact-import-drawer.js.map +0 -1
  462. package/src/lib/components/crm-export-all-button.d.ts +0 -39
  463. package/src/lib/components/crm-export-all-button.js +0 -98
  464. package/src/lib/components/crm-export-all-button.js.map +0 -1
  465. package/src/lib/components/csv-import-drawer.d.ts +0 -139
  466. package/src/lib/components/csv-import-drawer.js +0 -757
  467. package/src/lib/components/csv-import-drawer.js.map +0 -1
  468. package/src/lib/components/deal-import-drawer.d.ts +0 -24
  469. package/src/lib/components/deal-import-drawer.js +0 -83
  470. package/src/lib/components/deal-import-drawer.js.map +0 -1
  471. package/src/lib/components/lead-import-drawer.d.ts +0 -27
  472. package/src/lib/components/lead-import-drawer.js +0 -118
  473. package/src/lib/components/lead-import-drawer.js.map +0 -1
  474. package/src/lib/components/lead-source-values-card.d.ts +0 -27
  475. package/src/lib/components/lead-source-values-card.js.map +0 -1
  476. package/src/lib/components/task-import-drawer.d.ts +0 -24
  477. package/src/lib/components/task-import-drawer.js +0 -82
  478. package/src/lib/components/task-import-drawer.js.map +0 -1
  479. package/src/lib/model/companies-csv.d.ts +0 -36
  480. package/src/lib/model/companies-csv.js +0 -38
  481. package/src/lib/model/companies-csv.js.map +0 -1
  482. package/src/lib/model/contacts-csv.d.ts +0 -54
  483. package/src/lib/model/contacts-csv.js +0 -67
  484. package/src/lib/model/contacts-csv.js.map +0 -1
  485. package/src/lib/model/crm-company-import.d.ts +0 -154
  486. package/src/lib/model/crm-company-import.js +0 -331
  487. package/src/lib/model/crm-company-import.js.map +0 -1
  488. package/src/lib/model/crm-csv.d.ts +0 -239
  489. package/src/lib/model/crm-csv.js +0 -467
  490. package/src/lib/model/crm-csv.js.map +0 -1
  491. package/src/lib/model/crm-deal-import.d.ts +0 -112
  492. package/src/lib/model/crm-deal-import.js +0 -263
  493. package/src/lib/model/crm-deal-import.js.map +0 -1
  494. package/src/lib/model/crm-import.d.ts +0 -250
  495. package/src/lib/model/crm-import.js +0 -467
  496. package/src/lib/model/crm-import.js.map +0 -1
  497. package/src/lib/model/crm-lead-import.d.ts +0 -138
  498. package/src/lib/model/crm-lead-import.js +0 -446
  499. package/src/lib/model/crm-lead-import.js.map +0 -1
  500. package/src/lib/model/crm-task-import.d.ts +0 -94
  501. package/src/lib/model/crm-task-import.js +0 -275
  502. package/src/lib/model/crm-task-import.js.map +0 -1
  503. package/src/lib/model/deals-csv.d.ts +0 -32
  504. package/src/lib/model/deals-csv.js +0 -36
  505. package/src/lib/model/deals-csv.js.map +0 -1
  506. package/src/lib/model/import-column-shapes.d.ts +0 -36
  507. package/src/lib/model/import-column-shapes.js +0 -76
  508. package/src/lib/model/import-column-shapes.js.map +0 -1
  509. package/src/lib/model/lead-source-values.d.ts +0 -82
  510. package/src/lib/model/lead-source-values.js +0 -149
  511. package/src/lib/model/lead-source-values.js.map +0 -1
  512. package/src/lib/model/leads-csv.d.ts +0 -40
  513. package/src/lib/model/leads-csv.js +0 -40
  514. package/src/lib/model/leads-csv.js.map +0 -1
  515. package/src/lib/model/tasks-csv.d.ts +0 -28
  516. package/src/lib/model/tasks-csv.js +0 -28
  517. package/src/lib/model/tasks-csv.js.map +0 -1
  518. package/src/lib/server/companies-import.d.ts +0 -57
  519. package/src/lib/server/companies-import.js +0 -242
  520. package/src/lib/server/companies-import.js.map +0 -1
  521. package/src/lib/server/contacts-import.d.ts +0 -109
  522. package/src/lib/server/contacts-import.js +0 -393
  523. package/src/lib/server/contacts-import.js.map +0 -1
  524. package/src/lib/server/deals-import.d.ts +0 -76
  525. package/src/lib/server/deals-import.js +0 -248
  526. package/src/lib/server/deals-import.js.map +0 -1
  527. package/src/lib/server/export-route.d.ts +0 -60
  528. package/src/lib/server/export-route.js +0 -447
  529. package/src/lib/server/export-route.js.map +0 -1
  530. package/src/lib/server/import-context.d.ts +0 -87
  531. package/src/lib/server/import-context.js +0 -182
  532. package/src/lib/server/import-context.js.map +0 -1
  533. package/src/lib/server/lead-source-values.d.ts +0 -50
  534. package/src/lib/server/lead-source-values.js +0 -191
  535. package/src/lib/server/lead-source-values.js.map +0 -1
  536. package/src/lib/server/leads-import.d.ts +0 -84
  537. package/src/lib/server/leads-import.js +0 -367
  538. package/src/lib/server/leads-import.js.map +0 -1
  539. package/src/lib/server/tasks-import.d.ts +0 -54
  540. package/src/lib/server/tasks-import.js +0 -160
  541. package/src/lib/server/tasks-import.js.map +0 -1
@@ -19,12 +19,15 @@ import { registerPluginConsentGroupParticipant } from "@aglyn/aglyn/plugin-manag
19
19
  import { registerPluginContactCaptureWriter } from "@aglyn/aglyn/plugin-manager/plugin-contact-capture";
20
20
  import { registerPluginLeadConversionListener } from "@aglyn/aglyn/plugin-manager/plugin-lead-conversion";
21
21
  import { registerPluginPersonMatcher } from "@aglyn/aglyn/plugin-manager/plugin-person-matches";
22
+ import { registerPluginPersonRecords } from "@aglyn/aglyn/plugin-manager/plugin-person-records";
22
23
  import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
23
24
  import { registerPluginRecordEmailStateWriter } from "@aglyn/aglyn/plugin-manager/plugin-record-email-state";
25
+ import { registerPluginRecordOriginWriter } from "@aglyn/aglyn/plugin-manager/plugin-record-origin";
24
26
  import { registerPluginRecordTimelineWriter } from "@aglyn/aglyn/plugin-manager/plugin-record-timeline";
25
27
  import { registerPluginRecordWrittenListener } from "@aglyn/aglyn/plugin-manager/plugin-record-written";
28
+ import { registerServerStepExecutor } from "@aglyn/aglyn/plugin-manager/plugin-server-steps";
26
29
  import { registerPluginUsageMeter } from "@aglyn/aglyn/plugin-manager/plugin-usage-meters";
27
- import { BUNDLE_ID, CRM_RECORDS_METER_ID } from "./constants/bundle-common.js";
30
+ import { BUNDLE_ID, CRM_RECORDS_METER_ID, CRM_STEP_TYPES } from "./constants/bundle-common.js";
28
31
  import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
29
32
  /**
30
33
  * The CRM as the plugin that keeps people.
@@ -40,6 +43,36 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
40
43
  return await captureContactForCrm(request);
41
44
  }
42
45
  };
46
+ /**
47
+ * The CRM as the plugin other plugins ask for people (AGL-3080), deferred
48
+ * like the capture: `server/person-records.ts` and the Admin SDK it brings
49
+ * load with the first question, not when the process starts.
50
+ */ export const crmPersonRecordsService = {
51
+ async find (request) {
52
+ const { crmPersonRecords } = await import("./server/person-records.js");
53
+ return crmPersonRecords.find(request);
54
+ },
55
+ async read (request) {
56
+ const { crmPersonRecords } = await import("./server/person-records.js");
57
+ return crmPersonRecords.read(request);
58
+ },
59
+ async fileUnder (request) {
60
+ const { crmPersonRecords } = await import("./server/person-records.js");
61
+ return crmPersonRecords.fileUnder(request);
62
+ },
63
+ async recordRefund (request) {
64
+ const { crmPersonRecords } = await import("./server/person-records.js");
65
+ return crmPersonRecords.recordRefund(request);
66
+ },
67
+ async peopleInView (request) {
68
+ const { crmPersonRecords } = await import("./server/person-records.js");
69
+ return crmPersonRecords.peopleInView(request);
70
+ },
71
+ async wroteIn (request) {
72
+ const { crmPersonRecords } = await import("./server/person-records.js");
73
+ return crmPersonRecords.wroteIn(request);
74
+ }
75
+ };
43
76
  /**
44
77
  * The `pipeline` record index, its reads loaded with the first one — see
45
78
  * `server/pipeline-record-index.ts` for what it answers.
@@ -53,6 +86,20 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
53
86
  return pipelineRecordIndex.get(request);
54
87
  }
55
88
  };
89
+ /** One of `server/crm-record-indexes.ts`'s indexes, its reads loaded with the first one. */ function lazyCrmRecordIndex(kind) {
90
+ return {
91
+ async list (request) {
92
+ const { crmRecordIndexes } = await import("./server/crm-record-indexes.js");
93
+ return crmRecordIndexes()[kind].list(request);
94
+ },
95
+ async get (request) {
96
+ const { crmRecordIndexes } = await import("./server/crm-record-indexes.js");
97
+ return crmRecordIndexes()[kind].get(request);
98
+ }
99
+ };
100
+ }
101
+ /** The companies a person works for, as another plugin reads them (AGL-3080). */ export const crmCompanyRecordIndex = lazyCrmRecordIndex('company');
102
+ /** The email templates and snippets reps write, as another plugin reads them (AGL-3080). */ export const crmMessageTemplateRecordIndex = lazyCrmRecordIndex('messageTemplate');
56
103
  /**
57
104
  * The CRM's writer on the record-timeline seam (AGL-2981), deferred: the
58
105
  * writer and the Admin SDK it brings load with the first entry filed.
@@ -68,6 +115,17 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
68
115
  async recordEmailDelivery (request) {
69
116
  const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } = await import("./server/record-timeline.js");
70
117
  return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).recordEmailDelivery(request);
118
+ },
119
+ // A delivery event for a message the CRM tagged (AGL-2615, AGL-3080).
120
+ async recordTaggedDelivery (request) {
121
+ const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } = await import("./server/record-timeline.js");
122
+ return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).recordTaggedDelivery(request);
123
+ },
124
+ // An automation's email to the person its event is about, filed on that
125
+ // person's timeline with its delivery state (AGL-2615, AGL-3080).
126
+ async prepareEmail (request) {
127
+ const { prepareCrmRecordEmail } = await import("./server/automation-steps.js");
128
+ return prepareCrmRecordEmail(request);
71
129
  }
72
130
  };
73
131
  /**
@@ -96,6 +154,13 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
96
154
  registerPluginContactCaptureWriter(crmContactCaptureWriter, {
97
155
  pluginId: BUNDLE_ID
98
156
  });
157
+ // The other half of keeping people (AGL-3080): another plugin that holds
158
+ // an address, or a record the CRM handed it, asks here for the person —
159
+ // a flow email's consent read, a refund, an automation filing somebody
160
+ // under a campaign — and never opens the CRM's collections itself.
161
+ registerPluginPersonRecords(crmPersonRecordsService, {
162
+ pluginId: BUNDLE_ID
163
+ });
99
164
  // The CRM's own share of a lead conversion (AGL-3254): the lead's
100
165
  // campaigns go onto the contact's facet. Through the seam every door
101
166
  // that converts a lead reaches, and deferred like the capture: the
@@ -146,6 +211,11 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
146
211
  async reached (request) {
147
212
  const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } = await import("./server/record-email-state.js");
148
213
  return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).reached(request);
214
+ },
215
+ // A lead that wrote back, moved to Working (AGL-3080).
216
+ async replied (request) {
217
+ const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } = await import("./server/record-email-state.js");
218
+ return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).replied(request);
149
219
  }
150
220
  }, {
151
221
  pluginId: BUNDLE_ID
@@ -158,6 +228,18 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
158
228
  registerPluginRecordTimelineWriter(crmRecordTimelineWriter, {
159
229
  pluginId: BUNDLE_ID
160
230
  });
231
+ // Where a person came from (AGL-3519): a door the CRM does not run — the
232
+ // platform's account sign-up, a sequence enrolling someone — names its
233
+ // origin, and the CRM stamps the built-in Lead source on a record holding
234
+ // none. Deferred like the rest.
235
+ registerPluginRecordOriginWriter({
236
+ async stamp (request) {
237
+ const { crmRecordOriginWriter } = await import("./server/record-origin.js");
238
+ return crmRecordOriginWriter.stamp(request);
239
+ }
240
+ }, {
241
+ pluginId: BUNDLE_ID
242
+ });
161
243
  // Sharing rules (AGL-3336), re-evaluated after every server write of a
162
244
  // lead, a contact, a company or a deal: the core tells its record-written
163
245
  // listeners from the list-field restamp every such writer ends with, in
@@ -176,6 +258,20 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
176
258
  registerPluginRecordIndex('pipeline', crmPipelineRecordIndex, {
177
259
  pluginId: BUNDLE_ID
178
260
  });
261
+ // The company a person works for, and the templates reps write, as a sales
262
+ // sequence reads them (AGL-3080). Deferred like the rest.
263
+ registerPluginRecordIndex('company', crmCompanyRecordIndex, {
264
+ pluginId: BUNDLE_ID
265
+ });
266
+ registerPluginRecordIndex('messageTemplate', crmMessageTemplateRecordIndex, {
267
+ pluginId: BUNDLE_ID
268
+ });
269
+ // The automation steps that write the CRM (AGL-2605, AGL-3080), run for the
270
+ // workflows engine through the server-step seam and declared under
271
+ // `serverSteps`. Deferred like the rest: the writes load with the first step.
272
+ registerServerStepExecutor(CRM_STEP_TYPES, async (request)=>(await import("./server/automation-steps.js")).runCrmAutomationStep(request), {
273
+ pluginId: BUNDLE_ID
274
+ });
179
275
  registerPluginConsentGroupParticipant({
180
276
  async preview (request) {
181
277
  const { consentGroupParticipant } = await import("./server/consent-group-participant.js");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.server.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// The contract from its own module, not the plugin-manager barrel: boot needs\n// the registry and nothing else, and the barrel reaches the client contexts.\nimport { registerPluginConsentGroupParticipant } from '@aglyn/aglyn/plugin-manager/plugin-consent-group-change'\nimport {\n registerPluginContactCaptureWriter,\n type PluginContactCaptureWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport { registerPluginLeadConversionListener } from '@aglyn/aglyn/plugin-manager/plugin-lead-conversion'\nimport { registerPluginPersonMatcher } from '@aglyn/aglyn/plugin-manager/plugin-person-matches'\nimport {\n registerPluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { registerPluginRecordEmailStateWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport {\n registerPluginRecordTimelineWriter,\n type PluginRecordTimelineWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport { registerPluginRecordWrittenListener } from '@aglyn/aglyn/plugin-manager/plugin-record-written'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { BUNDLE_ID, CRM_RECORDS_METER_ID } from './constants/bundle-common'\nimport { summarizeConsentGroupChange } from './model/consent-group-summary'\n\n/**\n * The CRM as the plugin that keeps people.\n *\n * Light by construction, the way the workflows listener is: the capture\n * itself is imported when the first one arrives, not when the process\n * starts, so a process that never captures anybody pays for this object and\n * nothing else. That matters because this registers in EVERY server process,\n * including ones that will never touch the CRM.\n */\nexport const crmContactCaptureWriter: PluginContactCaptureWriter = {\n async capture(request) {\n const { captureContactForCrm } = await import('./server/capture-contact')\n return await captureContactForCrm(request)\n },\n}\n\n/**\n * The `pipeline` record index, its reads loaded with the first one — see\n * `server/pipeline-record-index.ts` for what it answers.\n */\nexport const crmPipelineRecordIndex: PluginRecordIndex = {\n async list(request) {\n const { pipelineRecordIndex } = await import('./server/pipeline-record-index')\n return pipelineRecordIndex.list(request)\n },\n async get(request) {\n const { pipelineRecordIndex } = await import('./server/pipeline-record-index')\n return pipelineRecordIndex.get(request)\n },\n}\n\n/**\n * The CRM's writer on the record-timeline seam (AGL-2981), deferred: the\n * writer and the Admin SDK it brings load with the first entry filed.\n */\nexport const crmRecordTimelineWriter: PluginRecordTimelineWriter = {\n async logActivity(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).logActivity(request)\n },\n async createTask(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).createTask(request)\n },\n async recordEmailDelivery(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).recordEmailDelivery!(request)\n },\n}\n\n/**\n * The plugin's SERVER declarations: what the server must know at boot, before\n * any door of the CRM has been called.\n *\n * ## Why a declaration and not a `tenantApi` registration\n *\n * The doors that meet a person mostly never load this plugin. A form\n * submission is a core route; an order and a booking are captured inside\n * STRIPE WEBHOOKS. None of them has a reason to load the CRM, and a registry\n * filled by a surface that did not load answers `null` — which\n * `capturePluginContact` defines as \"this workspace has no record system\", a\n * sentence that would be false and silent. Orders would stop creating\n * contacts with nothing red anywhere, which is the AGL-3025 shape.\n *\n * Running from both apps' generated server-declarations manifest at boot is\n * what makes the answer true in every process. It is also called from the\n * plugin's own API register functions, so a process whose boot did not run it\n * still registers the writer the first time a CRM door loads. Registering\n * twice replaces in place.\n *\n * ⚠️ The registry holds ONE writer: a workspace keeps one set of people. A\n * second plugin's is refused naming both, and the incumbent keeps serving.\n */\nexport function registerCrmServerDeclarations(): void {\n registerPluginContactCaptureWriter(crmContactCaptureWriter, {\n pluginId: BUNDLE_ID,\n })\n // The CRM's own share of a lead conversion (AGL-3254): the lead's\n // campaigns go onto the contact's facet. Through the seam every door\n // that converts a lead reaches, and deferred like the capture: the\n // module that writes is loaded when the first conversion arrives, and\n // it brings the Admin SDK with it — this file defers the plugin's OWN\n // module only. A library imported statically across the plugin cannot\n // also be lazy-loaded here: `enforce-module-boundaries` refuses every\n // static import of a library the project lazy-loads anywhere.\n registerPluginLeadConversionListener(\n async (request) => {\n const { carryLeadCampaignsOnConversion } =\n await import('./server/lead-campaign-carry')\n return carryLeadCampaignsOnConversion(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // \"Did this workspace already know this person?\" (AGL-3289), asked by the\n // staff console's acquisition card. Deferred like the rest: the reads load\n // when a staff member first opens a card, not when the process starts.\n registerPluginPersonMatcher(\n async (request) => {\n const { matchCrmPeople } = await import('./server/person-matches')\n return matchCrmPeople(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // The CRM's share of a consent group change (AGL-3320): a contact's\n // per-site refusal carried across a separation, and the records keyed by a\n // group re-homed after the flip. Registered whether or not a workspace has\n // the CRM switched on, because the contacts — and the refusals on them —\n // outlive the switch. Deferred like the rest; the summary is plain words\n // and answers synchronously.\n // The record email-state writer (AGL-3245), here as well as in the console\n // API so the tenant's doors reach it too: a form submission's capture is\n // checked for deliverability after its response (AGL-3328), and the \"Would\n // bounce\" that check finds is written through this seam. Deferred like the\n // rest; the console's own registration replaces this one with the same\n // writer, loaded eagerly.\n registerPluginRecordEmailStateWriter(\n {\n async stamp(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).stamp(request)\n },\n // A send's first opens and clicks, stamped on the contacts they name\n // (AGL-2616) — the campaign webhook's courtesy, deferred like the rest.\n async engaged(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).engaged!(request)\n },\n // A delivered campaign's leads, moved to Nurturing (AGL-3446).\n async reached(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).reached!(request)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n // The record timeline writer (AGL-2981), here as well as in the API\n // surfaces so a door in either app reaches it: a booking confirmed on the\n // tenant, or by the payment webhook, files its meeting and follow-up\n // through it (AGL-2660). Deferred like the rest; an API registration\n // replaces this one with the same writer, loaded eagerly.\n registerPluginRecordTimelineWriter(crmRecordTimelineWriter, { pluginId: BUNDLE_ID })\n // Sharing rules (AGL-3336), re-evaluated after every server write of a\n // lead, a contact, a company or a deal: the core tells its record-written\n // listeners from the list-field restamp every such writer ends with, in\n // both apps — a form's capture on the tenant, an edit in the console.\n // Deferred like the rest: an org with no rules pays one cached read.\n registerPluginRecordWrittenListener(\n async (event) => {\n const { crmSharingRecordWritten } = await import('./server/crm-sharing')\n await crmSharingRecordWritten(event)\n },\n { pluginId: BUNDLE_ID, key: 'sharing' },\n )\n // The organization's pipelines and their stages, as another plugin reads\n // them (AGL-3080): an automation the AI drafts names the stage a deal\n // moves to. Deferred like the rest: the reads load with the first one.\n registerPluginRecordIndex('pipeline', crmPipelineRecordIndex, { pluginId: BUNDLE_ID })\n registerPluginConsentGroupParticipant(\n {\n async preview(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.preview(request)\n },\n async run(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.run(request)\n },\n summarize: summarizeConsentGroupChange,\n },\n { pluginId: BUNDLE_ID },\n )\n // The records band in the monthly usage sweep, a core cron that never\n // loads a CRM door; declared in `usageAxes` so the sweep refuses to bill a\n // month without it. Deferred like the rest: the counts load with the\n // first sweep.\n registerPluginUsageMeter({\n pluginId: BUNDLE_ID,\n id: CRM_RECORDS_METER_ID,\n measure: async (context) => {\n const { measureCrmRecords } = await import('./server/crm-records-meter')\n return measureCrmRecords(context)\n },\n })\n}\n\n"],"names":["registerPluginConsentGroupParticipant","registerPluginContactCaptureWriter","registerPluginLeadConversionListener","registerPluginPersonMatcher","registerPluginRecordIndex","registerPluginRecordEmailStateWriter","registerPluginRecordTimelineWriter","registerPluginRecordWrittenListener","registerPluginUsageMeter","BUNDLE_ID","CRM_RECORDS_METER_ID","summarizeConsentGroupChange","crmContactCaptureWriter","capture","request","captureContactForCrm","crmPipelineRecordIndex","list","pipelineRecordIndex","get","crmRecordTimelineWriter","logActivity","createCrmRecordTimelineWriter","defaultCrmRecordTimelineDeps","createTask","recordEmailDelivery","registerCrmServerDeclarations","pluginId","carryLeadCampaignsOnConversion","matchCrmPeople","stamp","createCrmRecordEmailStateWriter","defaultCrmRecordEmailStateDeps","engaged","reached","event","crmSharingRecordWritten","key","preview","consentGroupParticipant","run","summarize","id","measure","context","measureCrmRecords"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.server.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// The contract from its own module, not the plugin-manager barrel: boot needs\n// the registry and nothing else, and the barrel reaches the client contexts.\nimport { registerPluginConsentGroupParticipant } from '@aglyn/aglyn/plugin-manager/plugin-consent-group-change'\nimport {\n registerPluginContactCaptureWriter,\n type PluginContactCaptureWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport { registerPluginLeadConversionListener } from '@aglyn/aglyn/plugin-manager/plugin-lead-conversion'\nimport { registerPluginPersonMatcher } from '@aglyn/aglyn/plugin-manager/plugin-person-matches'\nimport {\n registerPluginPersonRecords,\n type PluginPersonRecords,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-records'\nimport {\n registerPluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { registerPluginRecordEmailStateWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { registerPluginRecordOriginWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-origin'\nimport {\n registerPluginRecordTimelineWriter,\n type PluginRecordTimelineWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport { registerPluginRecordWrittenListener } from '@aglyn/aglyn/plugin-manager/plugin-record-written'\nimport { registerServerStepExecutor } from '@aglyn/aglyn/plugin-manager/plugin-server-steps'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { BUNDLE_ID, CRM_RECORDS_METER_ID, CRM_STEP_TYPES } from './constants/bundle-common'\nimport { summarizeConsentGroupChange } from './model/consent-group-summary'\n\n/**\n * The CRM as the plugin that keeps people.\n *\n * Light by construction, the way the workflows listener is: the capture\n * itself is imported when the first one arrives, not when the process\n * starts, so a process that never captures anybody pays for this object and\n * nothing else. That matters because this registers in EVERY server process,\n * including ones that will never touch the CRM.\n */\nexport const crmContactCaptureWriter: PluginContactCaptureWriter = {\n async capture(request) {\n const { captureContactForCrm } = await import('./server/capture-contact')\n return await captureContactForCrm(request)\n },\n}\n\n/**\n * The CRM as the plugin other plugins ask for people (AGL-3080), deferred\n * like the capture: `server/person-records.ts` and the Admin SDK it brings\n * load with the first question, not when the process starts.\n */\nexport const crmPersonRecordsService: PluginPersonRecords = {\n async find(request) {\n const { crmPersonRecords } = await import('./server/person-records')\n return crmPersonRecords.find(request)\n },\n async read(request) {\n const { crmPersonRecords } = await import('./server/person-records')\n return crmPersonRecords.read(request)\n },\n async fileUnder(request) {\n const { crmPersonRecords } = await import('./server/person-records')\n return crmPersonRecords.fileUnder(request)\n },\n async recordRefund(request) {\n const { crmPersonRecords } = await import('./server/person-records')\n return crmPersonRecords.recordRefund(request)\n },\n async peopleInView(request) {\n const { crmPersonRecords } = await import('./server/person-records')\n return crmPersonRecords.peopleInView(request)\n },\n async wroteIn(request) {\n const { crmPersonRecords } = await import('./server/person-records')\n return crmPersonRecords.wroteIn(request)\n },\n}\n\n/**\n * The `pipeline` record index, its reads loaded with the first one — see\n * `server/pipeline-record-index.ts` for what it answers.\n */\nexport const crmPipelineRecordIndex: PluginRecordIndex = {\n async list(request) {\n const { pipelineRecordIndex } = await import('./server/pipeline-record-index')\n return pipelineRecordIndex.list(request)\n },\n async get(request) {\n const { pipelineRecordIndex } = await import('./server/pipeline-record-index')\n return pipelineRecordIndex.get(request)\n },\n}\n\n/** One of `server/crm-record-indexes.ts`'s indexes, its reads loaded with the first one. */\nfunction lazyCrmRecordIndex(kind: 'company' | 'messageTemplate'): PluginRecordIndex {\n return {\n async list(request) {\n const { crmRecordIndexes } = await import('./server/crm-record-indexes')\n return crmRecordIndexes()[kind].list(request)\n },\n async get(request) {\n const { crmRecordIndexes } = await import('./server/crm-record-indexes')\n return crmRecordIndexes()[kind].get(request)\n },\n }\n}\n\n/** The companies a person works for, as another plugin reads them (AGL-3080). */\nexport const crmCompanyRecordIndex = lazyCrmRecordIndex('company')\n\n/** The email templates and snippets reps write, as another plugin reads them (AGL-3080). */\nexport const crmMessageTemplateRecordIndex = lazyCrmRecordIndex('messageTemplate')\n\n/**\n * The CRM's writer on the record-timeline seam (AGL-2981), deferred: the\n * writer and the Admin SDK it brings load with the first entry filed.\n */\nexport const crmRecordTimelineWriter: PluginRecordTimelineWriter = {\n async logActivity(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).logActivity(request)\n },\n async createTask(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).createTask(request)\n },\n async recordEmailDelivery(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).recordEmailDelivery!(request)\n },\n // A delivery event for a message the CRM tagged (AGL-2615, AGL-3080).\n async recordTaggedDelivery(request) {\n const { createCrmRecordTimelineWriter, defaultCrmRecordTimelineDeps } =\n await import('./server/record-timeline')\n return createCrmRecordTimelineWriter(defaultCrmRecordTimelineDeps()).recordTaggedDelivery!(request)\n },\n // An automation's email to the person its event is about, filed on that\n // person's timeline with its delivery state (AGL-2615, AGL-3080).\n async prepareEmail(request) {\n const { prepareCrmRecordEmail } = await import('./server/automation-steps')\n return prepareCrmRecordEmail(request)\n },\n}\n\n/**\n * The plugin's SERVER declarations: what the server must know at boot, before\n * any door of the CRM has been called.\n *\n * ## Why a declaration and not a `tenantApi` registration\n *\n * The doors that meet a person mostly never load this plugin. A form\n * submission is a core route; an order and a booking are captured inside\n * STRIPE WEBHOOKS. None of them has a reason to load the CRM, and a registry\n * filled by a surface that did not load answers `null` — which\n * `capturePluginContact` defines as \"this workspace has no record system\", a\n * sentence that would be false and silent. Orders would stop creating\n * contacts with nothing red anywhere, which is the AGL-3025 shape.\n *\n * Running from both apps' generated server-declarations manifest at boot is\n * what makes the answer true in every process. It is also called from the\n * plugin's own API register functions, so a process whose boot did not run it\n * still registers the writer the first time a CRM door loads. Registering\n * twice replaces in place.\n *\n * ⚠️ The registry holds ONE writer: a workspace keeps one set of people. A\n * second plugin's is refused naming both, and the incumbent keeps serving.\n */\nexport function registerCrmServerDeclarations(): void {\n registerPluginContactCaptureWriter(crmContactCaptureWriter, {\n pluginId: BUNDLE_ID,\n })\n // The other half of keeping people (AGL-3080): another plugin that holds\n // an address, or a record the CRM handed it, asks here for the person —\n // a flow email's consent read, a refund, an automation filing somebody\n // under a campaign — and never opens the CRM's collections itself.\n registerPluginPersonRecords(crmPersonRecordsService, { pluginId: BUNDLE_ID })\n // The CRM's own share of a lead conversion (AGL-3254): the lead's\n // campaigns go onto the contact's facet. Through the seam every door\n // that converts a lead reaches, and deferred like the capture: the\n // module that writes is loaded when the first conversion arrives, and\n // it brings the Admin SDK with it — this file defers the plugin's OWN\n // module only. A library imported statically across the plugin cannot\n // also be lazy-loaded here: `enforce-module-boundaries` refuses every\n // static import of a library the project lazy-loads anywhere.\n registerPluginLeadConversionListener(\n async (request) => {\n const { carryLeadCampaignsOnConversion } =\n await import('./server/lead-campaign-carry')\n return carryLeadCampaignsOnConversion(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // \"Did this workspace already know this person?\" (AGL-3289), asked by the\n // staff console's acquisition card. Deferred like the rest: the reads load\n // when a staff member first opens a card, not when the process starts.\n registerPluginPersonMatcher(\n async (request) => {\n const { matchCrmPeople } = await import('./server/person-matches')\n return matchCrmPeople(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // The CRM's share of a consent group change (AGL-3320): a contact's\n // per-site refusal carried across a separation, and the records keyed by a\n // group re-homed after the flip. Registered whether or not a workspace has\n // the CRM switched on, because the contacts — and the refusals on them —\n // outlive the switch. Deferred like the rest; the summary is plain words\n // and answers synchronously.\n // The record email-state writer (AGL-3245), here as well as in the console\n // API so the tenant's doors reach it too: a form submission's capture is\n // checked for deliverability after its response (AGL-3328), and the \"Would\n // bounce\" that check finds is written through this seam. Deferred like the\n // rest; the console's own registration replaces this one with the same\n // writer, loaded eagerly.\n registerPluginRecordEmailStateWriter(\n {\n async stamp(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).stamp(request)\n },\n // A send's first opens and clicks, stamped on the contacts they name\n // (AGL-2616) — the campaign webhook's courtesy, deferred like the rest.\n async engaged(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).engaged!(request)\n },\n // A delivered campaign's leads, moved to Nurturing (AGL-3446).\n async reached(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).reached!(request)\n },\n // A lead that wrote back, moved to Working (AGL-3080).\n async replied(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).replied!(request)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n // The record timeline writer (AGL-2981), here as well as in the API\n // surfaces so a door in either app reaches it: a booking confirmed on the\n // tenant, or by the payment webhook, files its meeting and follow-up\n // through it (AGL-2660). Deferred like the rest; an API registration\n // replaces this one with the same writer, loaded eagerly.\n registerPluginRecordTimelineWriter(crmRecordTimelineWriter, { pluginId: BUNDLE_ID })\n // Where a person came from (AGL-3519): a door the CRM does not run — the\n // platform's account sign-up, a sequence enrolling someone — names its\n // origin, and the CRM stamps the built-in Lead source on a record holding\n // none. Deferred like the rest.\n registerPluginRecordOriginWriter(\n {\n async stamp(request) {\n const { crmRecordOriginWriter } = await import('./server/record-origin')\n return crmRecordOriginWriter.stamp(request)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n // Sharing rules (AGL-3336), re-evaluated after every server write of a\n // lead, a contact, a company or a deal: the core tells its record-written\n // listeners from the list-field restamp every such writer ends with, in\n // both apps — a form's capture on the tenant, an edit in the console.\n // Deferred like the rest: an org with no rules pays one cached read.\n registerPluginRecordWrittenListener(\n async (event) => {\n const { crmSharingRecordWritten } = await import('./server/crm-sharing')\n await crmSharingRecordWritten(event)\n },\n { pluginId: BUNDLE_ID, key: 'sharing' },\n )\n // The organization's pipelines and their stages, as another plugin reads\n // them (AGL-3080): an automation the AI drafts names the stage a deal\n // moves to. Deferred like the rest: the reads load with the first one.\n registerPluginRecordIndex('pipeline', crmPipelineRecordIndex, { pluginId: BUNDLE_ID })\n // The company a person works for, and the templates reps write, as a sales\n // sequence reads them (AGL-3080). Deferred like the rest.\n registerPluginRecordIndex('company', crmCompanyRecordIndex, { pluginId: BUNDLE_ID })\n registerPluginRecordIndex('messageTemplate', crmMessageTemplateRecordIndex, { pluginId: BUNDLE_ID })\n // The automation steps that write the CRM (AGL-2605, AGL-3080), run for the\n // workflows engine through the server-step seam and declared under\n // `serverSteps`. Deferred like the rest: the writes load with the first step.\n registerServerStepExecutor(\n CRM_STEP_TYPES,\n async (request) => (await import('./server/automation-steps')).runCrmAutomationStep(request),\n { pluginId: BUNDLE_ID },\n )\n registerPluginConsentGroupParticipant(\n {\n async preview(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.preview(request)\n },\n async run(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.run(request)\n },\n summarize: summarizeConsentGroupChange,\n },\n { pluginId: BUNDLE_ID },\n )\n // The records band in the monthly usage sweep, a core cron that never\n // loads a CRM door; declared in `usageAxes` so the sweep refuses to bill a\n // month without it. Deferred like the rest: the counts load with the\n // first sweep.\n registerPluginUsageMeter({\n pluginId: BUNDLE_ID,\n id: CRM_RECORDS_METER_ID,\n measure: async (context) => {\n const { measureCrmRecords } = await import('./server/crm-records-meter')\n return measureCrmRecords(context)\n },\n })\n}\n\n"],"names":["registerPluginConsentGroupParticipant","registerPluginContactCaptureWriter","registerPluginLeadConversionListener","registerPluginPersonMatcher","registerPluginPersonRecords","registerPluginRecordIndex","registerPluginRecordEmailStateWriter","registerPluginRecordOriginWriter","registerPluginRecordTimelineWriter","registerPluginRecordWrittenListener","registerServerStepExecutor","registerPluginUsageMeter","BUNDLE_ID","CRM_RECORDS_METER_ID","CRM_STEP_TYPES","summarizeConsentGroupChange","crmContactCaptureWriter","capture","request","captureContactForCrm","crmPersonRecordsService","find","crmPersonRecords","read","fileUnder","recordRefund","peopleInView","wroteIn","crmPipelineRecordIndex","list","pipelineRecordIndex","get","lazyCrmRecordIndex","kind","crmRecordIndexes","crmCompanyRecordIndex","crmMessageTemplateRecordIndex","crmRecordTimelineWriter","logActivity","createCrmRecordTimelineWriter","defaultCrmRecordTimelineDeps","createTask","recordEmailDelivery","recordTaggedDelivery","prepareEmail","prepareCrmRecordEmail","registerCrmServerDeclarations","pluginId","carryLeadCampaignsOnConversion","matchCrmPeople","stamp","createCrmRecordEmailStateWriter","defaultCrmRecordEmailStateDeps","engaged","reached","replied","crmRecordOriginWriter","event","crmSharingRecordWritten","key","runCrmAutomationStep","preview","consentGroupParticipant","run","summarize","id","measure","context","measureCrmRecords"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { CrmPicklist, CrmPicklistId } from '@aglyn/aglyn';
2
+ export interface CompanyPicklistsResult {
3
+ /** Each list a company field holds a value of, by picklist id. */
4
+ lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>;
5
+ /** Every list has answered from the server. */
6
+ ready: boolean;
7
+ }
8
+ /**
9
+ * The org's lists behind a company's picklist fields (AGL-3514): Type,
10
+ * Industry, Rating and Ownership, and the lead source list Account Source
11
+ * holds a value of. One document listen each, shared with every other
12
+ * mount asking for the same list.
13
+ */
14
+ export declare function useCompanyPicklists(orgId: string | null | undefined): CompanyPicklistsResult;
15
+ export default useCompanyPicklists;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { useMemo } from "react";
18
+ import { useCrmPicklist } from "./use-crm-picklist.js";
19
+ /**
20
+ * The org's lists behind a company's picklist fields (AGL-3514): Type,
21
+ * Industry, Rating and Ownership, and the lead source list Account Source
22
+ * holds a value of. One document listen each, shared with every other
23
+ * mount asking for the same list.
24
+ */ export function useCompanyPicklists(orgId) {
25
+ const accountType = useCrmPicklist('accountType', orgId);
26
+ const industry = useCrmPicklist('industry', orgId);
27
+ const rating = useCrmPicklist('rating', orgId);
28
+ const ownership = useCrmPicklist('ownership', orgId);
29
+ const leadSource = useCrmPicklist('leadSource', orgId);
30
+ return useMemo(()=>({
31
+ lists: {
32
+ accountType: accountType.picklist,
33
+ industry: industry.picklist,
34
+ rating: rating.picklist,
35
+ ownership: ownership.picklist,
36
+ leadSource: leadSource.picklist
37
+ },
38
+ ready: [
39
+ accountType,
40
+ industry,
41
+ rating,
42
+ ownership,
43
+ leadSource
44
+ ].every((list)=>list.ready)
45
+ }), [
46
+ accountType,
47
+ industry,
48
+ rating,
49
+ ownership,
50
+ leadSource
51
+ ]);
52
+ }
53
+ export default useCompanyPicklists;
54
+
55
+ //# sourceMappingURL=use-company-picklists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-company-picklists.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 type { CrmPicklist, CrmPicklistId } from '@aglyn/aglyn'\nimport { useMemo } from 'react'\nimport { useCrmPicklist } from './use-crm-picklist'\n\nexport interface CompanyPicklistsResult {\n /** Each list a company field holds a value of, by picklist id. */\n lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>\n /** Every list has answered from the server. */\n ready: boolean\n}\n\n/**\n * The org's lists behind a company's picklist fields (AGL-3514): Type,\n * Industry, Rating and Ownership, and the lead source list Account Source\n * holds a value of. One document listen each, shared with every other\n * mount asking for the same list.\n */\nexport function useCompanyPicklists(orgId: string | null | undefined): CompanyPicklistsResult {\n const accountType = useCrmPicklist('accountType', orgId)\n const industry = useCrmPicklist('industry', orgId)\n const rating = useCrmPicklist('rating', orgId)\n const ownership = useCrmPicklist('ownership', orgId)\n const leadSource = useCrmPicklist('leadSource', orgId)\n return useMemo(\n () => ({\n lists: {\n accountType: accountType.picklist,\n industry: industry.picklist,\n rating: rating.picklist,\n ownership: ownership.picklist,\n leadSource: leadSource.picklist,\n },\n ready: [accountType, industry, rating, ownership, leadSource].every((list) => list.ready),\n }),\n [accountType, industry, rating, ownership, leadSource],\n )\n}\n\nexport default useCompanyPicklists\n"],"names":["useMemo","useCrmPicklist","useCompanyPicklists","orgId","accountType","industry","rating","ownership","leadSource","lists","picklist","ready","every","list"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAGA,SAASA,OAAO,QAAQ,QAAO;AAC/B,SAASC,cAAc,QAAQ,wBAAoB;AASnD;;;;;CAKC,GACD,OAAO,SAASC,oBAAoBC,KAAgC;IAClE,MAAMC,cAAcH,eAAe,eAAeE;IAClD,MAAME,WAAWJ,eAAe,YAAYE;IAC5C,MAAMG,SAASL,eAAe,UAAUE;IACxC,MAAMI,YAAYN,eAAe,aAAaE;IAC9C,MAAMK,aAAaP,eAAe,cAAcE;IAChD,OAAOH,QACL,IAAO,CAAA;YACLS,OAAO;gBACLL,aAAaA,YAAYM,QAAQ;gBACjCL,UAAUA,SAASK,QAAQ;gBAC3BJ,QAAQA,OAAOI,QAAQ;gBACvBH,WAAWA,UAAUG,QAAQ;gBAC7BF,YAAYA,WAAWE,QAAQ;YACjC;YACAC,OAAO;gBAACP;gBAAaC;gBAAUC;gBAAQC;gBAAWC;aAAW,CAACI,KAAK,CAAC,CAACC,OAASA,KAAKF,KAAK;QAC1F,CAAA,GACA;QAACP;QAAaC;QAAUC;QAAQC;QAAWC;KAAW;AAE1D;AAEA,eAAeN,oBAAmB"}
@@ -0,0 +1,25 @@
1
+ import { type CrmPicklist, type CrmPicklistDefinition, type CrmPicklistId } from '@aglyn/aglyn';
2
+ export interface CrmPicklistResult {
3
+ /** The field the list belongs to — its standard values, groups and targets. */
4
+ definition: CrmPicklistDefinition;
5
+ /** The list every picker offers — the org's own values with every standard value. */
6
+ picklist: CrmPicklist;
7
+ /** The org has written its own list; false while it reads the standard values alone. */
8
+ stored: boolean;
9
+ /** The server has answered, so the list is the org's and not a placeholder. */
10
+ ready: boolean;
11
+ /** The document has not been confirmed by the server — `writeGuardedBySeed`'s input. */
12
+ fromCache: boolean;
13
+ }
14
+ /**
15
+ * An org's values for one of the CRM's standard picklists (AGL-3510), for
16
+ * every surface that offers or manages them: a record's select, a create
17
+ * drawer, a list's filter and the Fields page.
18
+ *
19
+ * One document listen, shared by the Firestore SDK across every mount that
20
+ * asks for the same org and picklist, so a page with the list and a drawer
21
+ * open pays for it once. `orgId` null issues no read and answers the
22
+ * standard values, unready.
23
+ */
24
+ export declare function useCrmPicklist(id: CrmPicklistId, orgId: string | null | undefined): CrmPicklistResult;
25
+ export default useCrmPicklist;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { CRM_COLLECTIONS, crmPicklistDefinition, effectiveCrmPicklist, normalizeCrmPicklist } from "@aglyn/aglyn";
18
+ import { useFirestore, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
19
+ import { doc } from "firebase/firestore";
20
+ import { useMemo } from "react";
21
+ /**
22
+ * An org's values for one of the CRM's standard picklists (AGL-3510), for
23
+ * every surface that offers or manages them: a record's select, a create
24
+ * drawer, a list's filter and the Fields page.
25
+ *
26
+ * One document listen, shared by the Firestore SDK across every mount that
27
+ * asks for the same org and picklist, so a page with the list and a drawer
28
+ * open pays for it once. `orgId` null issues no read and answers the
29
+ * standard values, unready.
30
+ */ export function useCrmPicklist(id, orgId) {
31
+ const firestore = useFirestore();
32
+ const { data, status, fromCache } = useFirestoreDoc(()=>orgId ? doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.picklists, id) : null, [
33
+ firestore,
34
+ orgId,
35
+ id
36
+ ]);
37
+ return useMemo(()=>({
38
+ definition: crmPicklistDefinition(id),
39
+ picklist: effectiveCrmPicklist(id, data),
40
+ stored: normalizeCrmPicklist(data) !== null,
41
+ ready: Boolean(orgId) && status !== 'loading',
42
+ fromCache
43
+ }), [
44
+ id,
45
+ data,
46
+ orgId,
47
+ status,
48
+ fromCache
49
+ ]);
50
+ }
51
+ export default useCrmPicklist;
52
+
53
+ //# sourceMappingURL=use-crm-picklist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-crm-picklist.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 {\n CRM_COLLECTIONS,\n type CrmPicklist,\n type CrmPicklistDefinition,\n type CrmPicklistId,\n crmPicklistDefinition,\n effectiveCrmPicklist,\n normalizeCrmPicklist,\n} from '@aglyn/aglyn'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { doc } from 'firebase/firestore'\nimport { useMemo } from 'react'\n\nexport interface CrmPicklistResult {\n /** The field the list belongs to — its standard values, groups and targets. */\n definition: CrmPicklistDefinition\n /** The list every picker offers — the org's own values with every standard value. */\n picklist: CrmPicklist\n /** The org has written its own list; false while it reads the standard values alone. */\n stored: boolean\n /** The server has answered, so the list is the org's and not a placeholder. */\n ready: boolean\n /** The document has not been confirmed by the server — `writeGuardedBySeed`'s input. */\n fromCache: boolean\n}\n\n/**\n * An org's values for one of the CRM's standard picklists (AGL-3510), for\n * every surface that offers or manages them: a record's select, a create\n * drawer, a list's filter and the Fields page.\n *\n * One document listen, shared by the Firestore SDK across every mount that\n * asks for the same org and picklist, so a page with the list and a drawer\n * open pays for it once. `orgId` null issues no read and answers the\n * standard values, unready.\n */\nexport function useCrmPicklist(\n id: CrmPicklistId,\n orgId: string | null | undefined,\n): CrmPicklistResult {\n const firestore = useFirestore()\n const { data, status, fromCache } = useFirestoreDoc<Record<string, unknown>>(\n () => (orgId ? doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.picklists, id) : null),\n [firestore, orgId, id],\n )\n return useMemo(\n () => ({\n definition: crmPicklistDefinition(id) as CrmPicklistDefinition,\n picklist: effectiveCrmPicklist(id, data),\n stored: normalizeCrmPicklist(data) !== null,\n ready: Boolean(orgId) && status !== 'loading',\n fromCache,\n }),\n [id, data, orgId, status, fromCache],\n )\n}\n\nexport default useCrmPicklist\n"],"names":["CRM_COLLECTIONS","crmPicklistDefinition","effectiveCrmPicklist","normalizeCrmPicklist","useFirestore","useFirestoreDoc","doc","useMemo","useCrmPicklist","id","orgId","firestore","data","status","fromCache","picklists","definition","picklist","stored","ready","Boolean"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SACEA,eAAe,EAIfC,qBAAqB,EACrBC,oBAAoB,EACpBC,oBAAoB,QACf,eAAc;AACrB,SAASC,YAAY,EAAEC,eAAe,QAAQ,iCAAgC;AAC9E,SAASC,GAAG,QAAQ,qBAAoB;AACxC,SAASC,OAAO,QAAQ,QAAO;AAe/B;;;;;;;;;CASC,GACD,OAAO,SAASC,eACdC,EAAiB,EACjBC,KAAgC;IAEhC,MAAMC,YAAYP;IAClB,MAAM,EAAEQ,IAAI,EAAEC,MAAM,EAAEC,SAAS,EAAE,GAAGT,gBAClC,IAAOK,QAAQJ,IAAIK,WAAW,QAAQD,OAAOV,gBAAgBe,SAAS,EAAEN,MAAM,MAC9E;QAACE;QAAWD;QAAOD;KAAG;IAExB,OAAOF,QACL,IAAO,CAAA;YACLS,YAAYf,sBAAsBQ;YAClCQ,UAAUf,qBAAqBO,IAAIG;YACnCM,QAAQf,qBAAqBS,UAAU;YACvCO,OAAOC,QAAQV,UAAUG,WAAW;YACpCC;QACF,CAAA,GACA;QAACL;QAAIG;QAAMF;QAAOG;QAAQC;KAAU;AAExC;AAEA,eAAeN,eAAc"}
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import { type Firestore } from 'firebase/firestore';
17
18
  export type CrmRecordKind = 'contact' | 'company' | 'deal';
18
19
  /** The collection each kind of record lives in, under `orgs/{orgId}/`. */
19
20
  export declare const CRM_RECORD_COLLECTIONS: Record<CrmRecordKind, string>;
@@ -28,6 +29,21 @@ export declare const CRM_RECORD_COLLECTIONS: Record<CrmRecordKind, string>;
28
29
  export declare function crmRecordName(kind: CrmRecordKind, data: Record<string, unknown> | undefined, groupId: string): string;
29
30
  /** Test seam: forget everything, so a spec's fixtures are what get read. */
30
31
  export declare function resetCrmRecordNameCache(): void;
32
+ /**
33
+ * The names of `records`, read now — for a one-off like a file export,
34
+ * which needs every name at once rather than a listener's re-render
35
+ * (AGL-3521). Shares {@link useCrmRecordNames}' cache, and answers the same
36
+ * lookup: `undefined` for a record that could not be named.
37
+ */
38
+ export declare function readCrmRecordNames(firestore: Firestore, options: {
39
+ orgId: string;
40
+ groupId: string | null;
41
+ org?: Record<string, unknown> | null;
42
+ records: ReadonlyArray<{
43
+ kind: CrmRecordKind;
44
+ id: string;
45
+ }>;
46
+ }): Promise<(kind: CrmRecordKind, id: string) => string | undefined>;
31
47
  /**
32
48
  * The names of the records a list of tasks points at, resolved once each.
33
49
  *
@@ -62,6 +62,45 @@ const cacheKey = (orgId, kind, id)=>`${orgId}/${CRM_RECORD_COLLECTIONS[kind]}/${
62
62
  nameCache.clear();
63
63
  pending.clear();
64
64
  }
65
+ /**
66
+ * The reads that fill the cache for `records` not already in it — each
67
+ * record read once however many callers ask at the same time.
68
+ */ function startNameReads(firestore, orgId, groupId, org, records) {
69
+ const reads = [];
70
+ for (const { kind, id } of records){
71
+ if (!id) continue;
72
+ const key = cacheKey(orgId, kind, id);
73
+ if (nameCache.has(key)) continue;
74
+ let read = pending.get(key);
75
+ if (!read) {
76
+ read = getDoc(doc(firestore, 'orgs', orgId, CRM_RECORD_COLLECTIONS[kind], id)).then((snapshot)=>{
77
+ const data = snapshot.exists() ? snapshot.data() : undefined;
78
+ nameCache.set(key, data ? crmRecordName(kind, data, groupId != null ? groupId : contactPrimaryGroup(data, org).groupId) : null);
79
+ }).catch(()=>{
80
+ // A refused or failed read is not cached: the cell keeps showing
81
+ // the id, and the next mount tries again.
82
+ }).finally(()=>{
83
+ pending.delete(key);
84
+ });
85
+ pending.set(key, read);
86
+ }
87
+ reads.push(read);
88
+ }
89
+ return reads;
90
+ }
91
+ /**
92
+ * The names of `records`, read now — for a one-off like a file export,
93
+ * which needs every name at once rather than a listener's re-render
94
+ * (AGL-3521). Shares {@link useCrmRecordNames}' cache, and answers the same
95
+ * lookup: `undefined` for a record that could not be named.
96
+ */ export async function readCrmRecordNames(firestore, options) {
97
+ const { orgId, groupId, org, records } = options;
98
+ await Promise.all(startNameReads(firestore, orgId, groupId, org, records));
99
+ return (kind, id)=>{
100
+ var _nameCache_get;
101
+ return (_nameCache_get = nameCache.get(cacheKey(orgId, kind, id))) != null ? _nameCache_get : undefined;
102
+ };
103
+ }
65
104
  /**
66
105
  * The names of the records a list of tasks points at, resolved once each.
67
106
  *
@@ -81,26 +120,13 @@ const cacheKey = (orgId, kind, id)=>`${orgId}/${CRM_RECORD_COLLECTIONS[kind]}/${
81
120
  useEffect(()=>{
82
121
  if (!orgId || !wanted) return;
83
122
  let active = true;
84
- const reads = [];
85
- for (const entry of wanted.split('\n')){
123
+ const reads = startNameReads(firestore, orgId, groupId, org, wanted.split('\n').map((entry)=>{
86
124
  const [kind, id] = entry.split(':');
87
- const key = cacheKey(orgId, kind, id);
88
- if (nameCache.has(key)) continue;
89
- let read = pending.get(key);
90
- if (!read) {
91
- read = getDoc(doc(firestore, 'orgs', orgId, CRM_RECORD_COLLECTIONS[kind], id)).then((snapshot)=>{
92
- const data = snapshot.exists() ? snapshot.data() : undefined;
93
- nameCache.set(key, data ? crmRecordName(kind, data, groupId != null ? groupId : contactPrimaryGroup(data, org).groupId) : null);
94
- }).catch(()=>{
95
- // A refused or failed read is not cached: the cell keeps showing
96
- // the id, and the next mount tries again.
97
- }).finally(()=>{
98
- pending.delete(key);
99
- });
100
- pending.set(key, read);
101
- }
102
- reads.push(read);
103
- }
125
+ return {
126
+ kind,
127
+ id
128
+ };
129
+ }));
104
130
  if (!reads.length) return;
105
131
  void Promise.all(reads).then(()=>{
106
132
  if (active) setResolved((count)=>count + 1);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-crm-record-names.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 { contactDisplayName, CRM_COLLECTIONS } from '@aglyn/aglyn'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { contactPrimaryGroup } from '../model/contact-record'\n\nexport type CrmRecordKind = 'contact' | 'company' | 'deal'\n\n/** The collection each kind of record lives in, under `orgs/{orgId}/`. */\nexport const CRM_RECORD_COLLECTIONS: Record<CrmRecordKind, string> = {\n contact: 'contacts',\n company: CRM_COLLECTIONS.companies,\n deal: CRM_COLLECTIONS.deals,\n}\n\n/**\n * What a record is CALLED, from its document.\n *\n * A contact's name is read through the viewing group's facet, the way the\n * people list reads it, so one business renaming a person does not change\n * what an unrelated business's task list shows; the email stands in for a\n * contact nobody has named. A company has a name and a deal a title.\n */\nexport function crmRecordName(\n kind: CrmRecordKind,\n data: Record<string, unknown> | undefined,\n groupId: string,\n): string {\n if (!data) return ''\n switch (kind) {\n case 'contact':\n return contactDisplayName(data, groupId) || String(data['email'] ?? '')\n case 'company':\n return String(data['name'] ?? '')\n case 'deal':\n return String(data['title'] ?? '')\n }\n}\n\n/**\n * Names already read, keyed by `orgId/collection/id`, shared by every\n * mount for the life of the page.\n *\n * A task list names the same handful of records over and over — five tasks\n * about one deal are five rows and one name — and a person moves between the\n * tasks section, a contact's page and the dashboard without the records\n * changing underneath them. `null` records a document that does not exist,\n * so a task pointing at a deleted record costs one read rather than one per\n * render. A rename is picked up on the next page load, which is the right\n * staleness for a label beside a link to the record itself.\n */\nconst nameCache = new Map<string, string | null>()\nconst pending = new Map<string, Promise<void>>()\n\nconst cacheKey = (orgId: string, kind: CrmRecordKind, id: string) =>\n `${orgId}/${CRM_RECORD_COLLECTIONS[kind]}/${id}`\n\n/** Test seam: forget everything, so a spec's fixtures are what get read. */\nexport function resetCrmRecordNameCache(): void {\n nameCache.clear()\n pending.clear()\n}\n\n/**\n * The names of the records a list of tasks points at, resolved once each.\n *\n * Returns a lookup rather than a map so the caller does not have to know the\n * cache's key shape. An unresolved record answers `undefined` — the cell\n * renders the link with the id until the name lands, which is one render\n * later on a warm cache and one round trip on a cold one.\n */\nexport function useCrmRecordNames(options: {\n orgId: string | null | undefined\n /**\n * The viewing group a contact is named through, or `null` at the\n * organization level (AGL-2630), where each contact is named through its\n * own primary holder, resolved from {@link org}.\n */\n groupId: string | null\n org?: Record<string, unknown> | null\n records: ReadonlyArray<{ kind: CrmRecordKind; id: string }>\n}): (kind: CrmRecordKind, id: string) => string | undefined {\n const { orgId, groupId, org, records } = options\n const firestore = useFirestore()\n // A counter rather than a copy of the cache: the cache is the source of\n // truth and this only exists to re-render when it grows.\n const [, setResolved] = useState(0)\n\n // The pairs as one string, so the effect runs when the SET changes and not\n // when the caller builds a new array of the same pairs on every render.\n const wanted = records\n .map((record) => `${record.kind}:${record.id}`)\n .filter((entry, index, all) => all.indexOf(entry) === index)\n .sort()\n .join('\\n')\n\n useEffect(() => {\n if (!orgId || !wanted) return\n let active = true\n const reads: Promise<void>[] = []\n for (const entry of wanted.split('\\n')) {\n const [kind, id] = entry.split(':') as [CrmRecordKind, string]\n const key = cacheKey(orgId, kind, id)\n if (nameCache.has(key)) continue\n let read = pending.get(key)\n if (!read) {\n read = getDoc(\n doc(firestore, 'orgs', orgId, CRM_RECORD_COLLECTIONS[kind], id),\n )\n .then((snapshot) => {\n const data = snapshot.exists()\n ? (snapshot.data() as Record<string, unknown>)\n : undefined\n nameCache.set(\n key,\n data\n ? crmRecordName(\n kind,\n data,\n groupId ?? contactPrimaryGroup(data, org).groupId,\n )\n : null,\n )\n })\n .catch(() => {\n // A refused or failed read is not cached: the cell keeps showing\n // the id, and the next mount tries again.\n })\n .finally(() => {\n pending.delete(key)\n })\n pending.set(key, read)\n }\n reads.push(read)\n }\n if (!reads.length) return\n void Promise.all(reads).then(() => {\n if (active) setResolved((count) => count + 1)\n })\n return () => {\n active = false\n }\n }, [firestore, orgId, groupId, wanted])\n\n return useCallback(\n (kind: CrmRecordKind, id: string) => {\n if (!orgId) return undefined\n const hit = nameCache.get(cacheKey(orgId, kind, id))\n return hit === null ? undefined : hit\n },\n [orgId],\n )\n}\n"],"names":["contactDisplayName","CRM_COLLECTIONS","useFirestore","doc","getDoc","useCallback","useEffect","useState","contactPrimaryGroup","CRM_RECORD_COLLECTIONS","contact","company","companies","deal","deals","crmRecordName","kind","data","groupId","String","nameCache","Map","pending","cacheKey","orgId","id","resetCrmRecordNameCache","clear","useCrmRecordNames","options","org","records","firestore","setResolved","wanted","map","record","filter","entry","index","all","indexOf","sort","join","active","reads","split","key","has","read","get","then","snapshot","exists","undefined","set","catch","finally","delete","push","length","Promise","count","hit"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-crm-record-names.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 { contactDisplayName, CRM_COLLECTIONS } from '@aglyn/aglyn'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { doc, type Firestore, getDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { contactPrimaryGroup } from '../model/contact-record'\n\nexport type CrmRecordKind = 'contact' | 'company' | 'deal'\n\n/** The collection each kind of record lives in, under `orgs/{orgId}/`. */\nexport const CRM_RECORD_COLLECTIONS: Record<CrmRecordKind, string> = {\n contact: 'contacts',\n company: CRM_COLLECTIONS.companies,\n deal: CRM_COLLECTIONS.deals,\n}\n\n/**\n * What a record is CALLED, from its document.\n *\n * A contact's name is read through the viewing group's facet, the way the\n * people list reads it, so one business renaming a person does not change\n * what an unrelated business's task list shows; the email stands in for a\n * contact nobody has named. A company has a name and a deal a title.\n */\nexport function crmRecordName(\n kind: CrmRecordKind,\n data: Record<string, unknown> | undefined,\n groupId: string,\n): string {\n if (!data) return ''\n switch (kind) {\n case 'contact':\n return contactDisplayName(data, groupId) || String(data['email'] ?? '')\n case 'company':\n return String(data['name'] ?? '')\n case 'deal':\n return String(data['title'] ?? '')\n }\n}\n\n/**\n * Names already read, keyed by `orgId/collection/id`, shared by every\n * mount for the life of the page.\n *\n * A task list names the same handful of records over and over — five tasks\n * about one deal are five rows and one name — and a person moves between the\n * tasks section, a contact's page and the dashboard without the records\n * changing underneath them. `null` records a document that does not exist,\n * so a task pointing at a deleted record costs one read rather than one per\n * render. A rename is picked up on the next page load, which is the right\n * staleness for a label beside a link to the record itself.\n */\nconst nameCache = new Map<string, string | null>()\nconst pending = new Map<string, Promise<void>>()\n\nconst cacheKey = (orgId: string, kind: CrmRecordKind, id: string) =>\n `${orgId}/${CRM_RECORD_COLLECTIONS[kind]}/${id}`\n\n/** Test seam: forget everything, so a spec's fixtures are what get read. */\nexport function resetCrmRecordNameCache(): void {\n nameCache.clear()\n pending.clear()\n}\n\n/**\n * The reads that fill the cache for `records` not already in it — each\n * record read once however many callers ask at the same time.\n */\nfunction startNameReads(\n firestore: Firestore,\n orgId: string,\n groupId: string | null,\n org: Record<string, unknown> | null | undefined,\n records: ReadonlyArray<{ kind: CrmRecordKind; id: string }>,\n): Promise<void>[] {\n const reads: Promise<void>[] = []\n for (const { kind, id } of records) {\n if (!id) continue\n const key = cacheKey(orgId, kind, id)\n if (nameCache.has(key)) continue\n let read = pending.get(key)\n if (!read) {\n read = getDoc(doc(firestore, 'orgs', orgId, CRM_RECORD_COLLECTIONS[kind], id))\n .then((snapshot) => {\n const data = snapshot.exists() ? (snapshot.data() as Record<string, unknown>) : undefined\n nameCache.set(\n key,\n data ? crmRecordName(kind, data, groupId ?? contactPrimaryGroup(data, org).groupId) : null,\n )\n })\n .catch(() => {\n // A refused or failed read is not cached: the cell keeps showing\n // the id, and the next mount tries again.\n })\n .finally(() => {\n pending.delete(key)\n })\n pending.set(key, read)\n }\n reads.push(read)\n }\n return reads\n}\n\n/**\n * The names of `records`, read now — for a one-off like a file export,\n * which needs every name at once rather than a listener's re-render\n * (AGL-3521). Shares {@link useCrmRecordNames}' cache, and answers the same\n * lookup: `undefined` for a record that could not be named.\n */\nexport async function readCrmRecordNames(\n firestore: Firestore,\n options: {\n orgId: string\n groupId: string | null\n org?: Record<string, unknown> | null\n records: ReadonlyArray<{ kind: CrmRecordKind; id: string }>\n },\n): Promise<(kind: CrmRecordKind, id: string) => string | undefined> {\n const { orgId, groupId, org, records } = options\n await Promise.all(startNameReads(firestore, orgId, groupId, org, records))\n return (kind, id) => nameCache.get(cacheKey(orgId, kind, id)) ?? undefined\n}\n\n/**\n * The names of the records a list of tasks points at, resolved once each.\n *\n * Returns a lookup rather than a map so the caller does not have to know the\n * cache's key shape. An unresolved record answers `undefined` — the cell\n * renders the link with the id until the name lands, which is one render\n * later on a warm cache and one round trip on a cold one.\n */\nexport function useCrmRecordNames(options: {\n orgId: string | null | undefined\n /**\n * The viewing group a contact is named through, or `null` at the\n * organization level (AGL-2630), where each contact is named through its\n * own primary holder, resolved from {@link org}.\n */\n groupId: string | null\n org?: Record<string, unknown> | null\n records: ReadonlyArray<{ kind: CrmRecordKind; id: string }>\n}): (kind: CrmRecordKind, id: string) => string | undefined {\n const { orgId, groupId, org, records } = options\n const firestore = useFirestore()\n // A counter rather than a copy of the cache: the cache is the source of\n // truth and this only exists to re-render when it grows.\n const [, setResolved] = useState(0)\n\n // The pairs as one string, so the effect runs when the SET changes and not\n // when the caller builds a new array of the same pairs on every render.\n const wanted = records\n .map((record) => `${record.kind}:${record.id}`)\n .filter((entry, index, all) => all.indexOf(entry) === index)\n .sort()\n .join('\\n')\n\n useEffect(() => {\n if (!orgId || !wanted) return\n let active = true\n const reads = startNameReads(\n firestore,\n orgId,\n groupId,\n org,\n wanted.split('\\n').map((entry) => {\n const [kind, id] = entry.split(':') as [CrmRecordKind, string]\n return { kind, id }\n }),\n )\n if (!reads.length) return\n void Promise.all(reads).then(() => {\n if (active) setResolved((count) => count + 1)\n })\n return () => {\n active = false\n }\n }, [firestore, orgId, groupId, wanted])\n\n return useCallback(\n (kind: CrmRecordKind, id: string) => {\n if (!orgId) return undefined\n const hit = nameCache.get(cacheKey(orgId, kind, id))\n return hit === null ? undefined : hit\n },\n [orgId],\n )\n}\n"],"names":["contactDisplayName","CRM_COLLECTIONS","useFirestore","doc","getDoc","useCallback","useEffect","useState","contactPrimaryGroup","CRM_RECORD_COLLECTIONS","contact","company","companies","deal","deals","crmRecordName","kind","data","groupId","String","nameCache","Map","pending","cacheKey","orgId","id","resetCrmRecordNameCache","clear","startNameReads","firestore","org","records","reads","key","has","read","get","then","snapshot","exists","undefined","set","catch","finally","delete","push","readCrmRecordNames","options","Promise","all","useCrmRecordNames","setResolved","wanted","map","record","filter","entry","index","indexOf","sort","join","active","split","length","count","hit"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type CrmTaskPicklists } from '@aglyn/aglyn';
2
+ /**
3
+ * An org's Status, Priority and Type lists for tasks (AGL-3517), for every
4
+ * surface that shows or writes a task's labels. Three document listens,
5
+ * shared by the Firestore SDK with every other mount asking for the same
6
+ * lists; `orgId` null reads nothing and answers the standard values.
7
+ */
8
+ export declare function useCrmTaskPicklists(orgId: string | null | undefined): CrmTaskPicklists & {
9
+ ready: boolean;
10
+ };
11
+ export default useCrmTaskPicklists;