@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
@@ -0,0 +1,317 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { containerMembershipField, contactContainerFieldPath, normalizeContainerIds } from "@aglyn/aglyn/app-utils/container-membership";
17
+ import { normalizeContactEmail } from "@aglyn/aglyn/app-utils/contacts";
18
+ import { CRM_COLLECTIONS, crmLeadStatus, crmViewIsListed, isCrmLeadOpen, normalizeCrmViewFilters } from "@aglyn/aglyn/app-utils/crm";
19
+ import { dynamicListDimensionsForCrmView } from "@aglyn/aglyn/app-utils/dynamic-list-rule";
20
+ import { personKey } from "@aglyn/aglyn/app-utils/person-key";
21
+ import { scopeTokensForHost, seenOnlyThroughGrant, visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
22
+ import { consentGroupForSite, firebaseAdmin, resolveOrgIdForHost } from "@aglyn/tenant-data-admin";
23
+ // The leaves, not the barrel: this plugin's specs substitute the barrel
24
+ // wholesale, and the lookup and the restamp must reach the real logic.
25
+ import { findContactByEmail } from "@aglyn/tenant-data-admin/server/contact-email-index";
26
+ import { restampCrmListFieldsAt } from "@aglyn/tenant-data-admin/server/crm-records";
27
+ import { collectDynamicListCandidates } from "@aglyn/tenant-data-admin/server/dynamic-list-materialize";
28
+ import { FieldValue } from "firebase-admin/firestore";
29
+ import { recordPersonRefund } from "./person-refund.js";
30
+ /** The person kinds, as `plugin-record-routes` addresses them. */ export const CRM_PERSON_KINDS = {
31
+ contact: 'contact',
32
+ lead: 'lead'
33
+ };
34
+ /** The collection each person kind is stored in, under the organization. */ const STORED_IN = {
35
+ [CRM_PERSON_KINDS.contact]: 'contacts',
36
+ [CRM_PERSON_KINDS.lead]: 'leads'
37
+ };
38
+ /** `getAll` takes this many references per call. */ const GET_ALL_CHUNK = 300;
39
+ /**
40
+ * The most leads a saved Leads view is taken from: the site's most recently
41
+ * seen, the Leads list's own window (AGL-3234).
42
+ */ export const CRM_LEADS_VIEW_WINDOW = 200;
43
+ export function defaultCrmPersonRecordsDeps() {
44
+ return {
45
+ firestore: ()=>firebaseAdmin.app().firestore(),
46
+ orgIdForHost: async (hostId)=>{
47
+ var _ref;
48
+ return (_ref = await resolveOrgIdForHost(hostId)) != null ? _ref : null;
49
+ },
50
+ groupIdForHost: async (hostId)=>(await consentGroupForSite(hostId)).groupId,
51
+ contactViewEmails: async ({ hostId, viewId })=>{
52
+ const scan = await collectDynamicListCandidates({
53
+ hostId,
54
+ rule: {
55
+ sources: [
56
+ 'contacts'
57
+ ],
58
+ viewId
59
+ }
60
+ });
61
+ return {
62
+ emails: scan.candidates.map((candidate)=>candidate.email),
63
+ complete: scan.complete
64
+ };
65
+ }
66
+ };
67
+ }
68
+ function personOf(kind, snapshot) {
69
+ var _snapshot_data;
70
+ const data = (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {};
71
+ return {
72
+ kind,
73
+ id: snapshot.id,
74
+ email: normalizeContactEmail(data['email']),
75
+ data
76
+ };
77
+ }
78
+ /**
79
+ * The organization a request names, or the one its site belongs to. A site
80
+ * with no organization THROWS, as the org-data lookup every caller used to
81
+ * make threw: each caller has already decided which way a failed read falls.
82
+ */ async function orgOf(deps, request) {
83
+ var _request_orgId, _request_hostId;
84
+ const named = String((_request_orgId = request.orgId) != null ? _request_orgId : '').trim();
85
+ if (named) return named;
86
+ const hostId = String((_request_hostId = request.hostId) != null ? _request_hostId : '').trim();
87
+ const resolved = hostId ? await deps.orgIdForHost(hostId) : null;
88
+ if (!resolved) throw new Error(`[crm] no organization to find a person in for host ${hostId || '(none)'}`);
89
+ return resolved;
90
+ }
91
+ export function createCrmPersonRecords(deps) {
92
+ return {
93
+ /**
94
+ * The contact first, through the org's address index (AGL-2633) so an
95
+ * address a merge folded into another record still names the person who
96
+ * now holds it; then, for a caller that asked for any kind, the lead the
97
+ * address is keyed as. Narrowed to the site when asked, on the document
98
+ * the address names: a record the site cannot see is `null`.
99
+ */ async find (request) {
100
+ var _request_hostId;
101
+ const email = normalizeContactEmail(request.email);
102
+ if (!email) return null;
103
+ const orgId = await orgOf(deps, request);
104
+ const org = deps.firestore().collection('orgs').doc(orgId);
105
+ const hostId = String((_request_hostId = request.hostId) != null ? _request_hostId : '').trim();
106
+ const narrowTo = request.onlyVisibleToSite && hostId ? hostId : null;
107
+ const contact = await findContactByEmail(org.collection(STORED_IN[CRM_PERSON_KINDS.contact]), email, narrowTo ? {
108
+ hostId: narrowTo
109
+ } : {});
110
+ if (contact) return personOf(CRM_PERSON_KINDS.contact, contact);
111
+ if (!request.anyKind) return null;
112
+ const key = personKey(email);
113
+ if (!key) return null;
114
+ const lead = await org.collection(STORED_IN[CRM_PERSON_KINDS.lead]).doc(key).get();
115
+ if (!lead.exists) return null;
116
+ if (narrowTo && !visibleToHost(lead.get('visibleTo'), narrowTo)) {
117
+ return null;
118
+ }
119
+ return personOf(CRM_PERSON_KINDS.lead, lead);
120
+ },
121
+ /** Every record named, read at once; a kind the CRM does not keep is `null`. */ async read (request) {
122
+ const firestore = deps.firestore();
123
+ const org = firestore.collection('orgs').doc(request.orgId);
124
+ const refs = request.records.map((record)=>{
125
+ var _record_id;
126
+ const collection = STORED_IN[record.kind];
127
+ const id = String((_record_id = record.id) != null ? _record_id : '').trim();
128
+ return collection && id ? org.collection(collection).doc(id) : null;
129
+ });
130
+ const wanted = refs.filter((ref)=>ref !== null);
131
+ const snapshots = [];
132
+ for(let start = 0; start < wanted.length; start += GET_ALL_CHUNK){
133
+ snapshots.push(...await firestore.getAll(...wanted.slice(start, start + GET_ALL_CHUNK)));
134
+ }
135
+ let at = 0;
136
+ return request.records.map((record, index)=>{
137
+ if (!refs[index]) return null;
138
+ const snapshot = snapshots[at++];
139
+ return (snapshot == null ? void 0 : snapshot.exists) ? personOf(record.kind, snapshot) : null;
140
+ });
141
+ },
142
+ /**
143
+ * Files the person under the containers as the site holds them: on a
144
+ * contact, inside the site's consent-group facet — which campaigns a
145
+ * merchant filed somebody under is that merchant's record, and written at
146
+ * the top it would be readable by every site of an agency's account; on a
147
+ * lead, in the lead's own membership field, whose list fields are then
148
+ * restamped so the Leads list's filter finds it (AGL-3321).
149
+ *
150
+ * `update`, never a merge-set: a dotted path in `set` is a literal field
151
+ * name, and the record must exist. A record the site cannot see is not
152
+ * filed.
153
+ */ async fileUnder (request) {
154
+ var _request_record_id;
155
+ const ids = normalizeContainerIds(request.ids);
156
+ const collection = STORED_IN[request.record.kind];
157
+ const id = String((_request_record_id = request.record.id) != null ? _request_record_id : '').trim();
158
+ if (!ids.length || !collection || !id) return {
159
+ filed: false
160
+ };
161
+ const orgId = await orgOf(deps, request);
162
+ const ref = deps.firestore().collection('orgs').doc(orgId).collection(collection).doc(id);
163
+ const snapshot = await ref.get();
164
+ if (!snapshot.exists) return {
165
+ filed: false
166
+ };
167
+ if (!visibleToHost(snapshot.get('visibleTo'), request.hostId)) {
168
+ return {
169
+ filed: false
170
+ };
171
+ }
172
+ if (request.record.kind === CRM_PERSON_KINDS.lead) {
173
+ await ref.update({
174
+ [containerMembershipField(request.containerKind)]: FieldValue.arrayUnion(...ids),
175
+ updatedAt: FieldValue.serverTimestamp()
176
+ });
177
+ await restampCrmListFieldsAt(ref, 'leads');
178
+ return {
179
+ filed: true
180
+ };
181
+ }
182
+ const groupId = await deps.groupIdForHost(request.hostId);
183
+ await ref.update({
184
+ [contactContainerFieldPath(groupId, request.containerKind)]: FieldValue.arrayUnion(...ids),
185
+ updatedAt: FieldValue.serverTimestamp()
186
+ });
187
+ return {
188
+ filed: true
189
+ };
190
+ },
191
+ recordRefund: (request)=>recordPersonRefund(request),
192
+ /**
193
+ * The people a saved view selects for a site (AGL-3234). A colleague's
194
+ * private view is theirs: not listed for this member, so it does not
195
+ * exist for them here either.
196
+ *
197
+ * A Leads view takes the site's most recently seen leads —
198
+ * {@link CRM_LEADS_VIEW_WINDOW}, the Leads list's own window — narrowed by
199
+ * the view's status clause the way the list narrows it: open leads when
200
+ * the view names no status, one status when it does, everything for
201
+ * `all`. Held, not merely seen: a lead another site only SHARED with this
202
+ * one (AGL-3336) is not this site's to take.
203
+ *
204
+ * A Contacts view is read the way the dynamic-list sweep reads one, and
205
+ * each address it selects is resolved to the contact the site may see. A
206
+ * view filtering on something that reading cannot apply is refused whole:
207
+ * dropping the filter would select more people than the view shows.
208
+ */ async peopleInView (request) {
209
+ var _view_data, _data_ownerUid;
210
+ const firestore = deps.firestore();
211
+ const org = firestore.collection('orgs').doc(request.orgId);
212
+ const view = await org.collection(CRM_COLLECTIONS.views).doc(request.viewId).get();
213
+ const data = view.exists ? (_view_data = view.data()) != null ? _view_data : {} : null;
214
+ if (!data || !crmViewIsListed({
215
+ shared: data['shared'] === true,
216
+ ownerUid: String((_data_ownerUid = data['ownerUid']) != null ? _data_ownerUid : '')
217
+ }, request.viewerUid)) {
218
+ return {
219
+ ok: false,
220
+ reason: 'not-found'
221
+ };
222
+ }
223
+ const filters = normalizeCrmViewFilters(data['filters']);
224
+ if (data['section'] === 'leads') {
225
+ var _ref;
226
+ const statusClause = filters.find((clause)=>clause.field === 'status' && clause.op === 'equals');
227
+ const wanted = String((_ref = statusClause == null ? void 0 : statusClause.value) != null ? _ref : 'open');
228
+ // Narrowed to what the site may see before it is ordered (AGL-3275):
229
+ // the collection is org-wide, and an agency's sequence must not be
230
+ // offered another client's people.
231
+ const window = await org.collection(STORED_IN[CRM_PERSON_KINDS.lead]).where('visibleTo', 'array-contains-any', scopeTokensForHost(request.hostId)).orderBy('lastSeenAtMs', 'desc').limit(CRM_LEADS_VIEW_WINDOW + 1).get();
232
+ const matching = window.docs.slice(0, CRM_LEADS_VIEW_WINDOW).filter((doc)=>{
233
+ var _doc_data;
234
+ const lead = (_doc_data = doc.data()) != null ? _doc_data : {};
235
+ if (seenOnlyThroughGrant(lead, request.hostId)) return false;
236
+ if (wanted === 'all') return true;
237
+ if (wanted === 'open') return isCrmLeadOpen(lead) && !lead['convertedContactId'];
238
+ return crmLeadStatus(lead) === wanted;
239
+ });
240
+ return {
241
+ ok: true,
242
+ people: matching.slice(0, request.limit).map((doc)=>({
243
+ kind: CRM_PERSON_KINDS.lead,
244
+ id: doc.id
245
+ })),
246
+ total: matching.length,
247
+ truncated: matching.length > request.limit || window.docs.length > CRM_LEADS_VIEW_WINDOW
248
+ };
249
+ }
250
+ if (data['section'] !== 'contacts') return {
251
+ ok: false,
252
+ reason: 'not-people'
253
+ };
254
+ const { unsupported } = dynamicListDimensionsForCrmView(filters);
255
+ if (unsupported.length) {
256
+ return {
257
+ ok: false,
258
+ reason: 'unsupported',
259
+ unsupported: unsupported.map((clause)=>clause.label || clause.field)
260
+ };
261
+ }
262
+ const { emails, complete } = await deps.contactViewEmails({
263
+ hostId: request.hostId,
264
+ viewId: request.viewId
265
+ });
266
+ const ordered = [
267
+ ...new Set(emails)
268
+ ].sort();
269
+ const contacts = org.collection(STORED_IN[CRM_PERSON_KINDS.contact]);
270
+ const found = await Promise.all(ordered.slice(0, request.limit).map((email)=>findContactByEmail(contacts, email, {
271
+ hostId: request.hostId
272
+ })));
273
+ const ids = [
274
+ ...new Set(found.filter((snapshot)=>snapshot).map((snapshot)=>String(snapshot == null ? void 0 : snapshot.id)))
275
+ ];
276
+ return {
277
+ ok: true,
278
+ people: ids.map((id)=>({
279
+ kind: CRM_PERSON_KINDS.contact,
280
+ id
281
+ })),
282
+ total: ordered.length,
283
+ truncated: ordered.length > request.limit || !complete
284
+ };
285
+ },
286
+ /**
287
+ * Whether each person has ever written in: an inbound email on their
288
+ * timeline, filed on the contact, or on the lead while they were one
289
+ * (AGL-3234). One keyed query each; a failed one is unknown.
290
+ */ async wroteIn (request) {
291
+ const activities = deps.firestore().collection('orgs').doc(request.orgId).collection(CRM_COLLECTIONS.activities);
292
+ return Promise.all(request.records.map(async (record)=>{
293
+ var _record_id;
294
+ const field = record.kind === CRM_PERSON_KINDS.contact ? 'contactId' : record.kind === CRM_PERSON_KINDS.lead ? 'leadId' : null;
295
+ const id = String((_record_id = record.id) != null ? _record_id : '').trim();
296
+ if (!field || !id) return null;
297
+ try {
298
+ const snapshot = await activities.where(field, '==', id).where('direction', '==', 'inbound').limit(1).get();
299
+ return !snapshot.empty;
300
+ } catch (error) {
301
+ console.error('[crm] inbound email lookup failed; reading as unknown', error);
302
+ return null;
303
+ }
304
+ }));
305
+ }
306
+ };
307
+ }
308
+ /** The service as the CRM registers it, its storage the admin app's. */ export const crmPersonRecords = {
309
+ find: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).find(request),
310
+ read: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).read(request),
311
+ fileUnder: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).fileUnder(request),
312
+ recordRefund: (request)=>recordPersonRefund(request),
313
+ peopleInView: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).peopleInView(request),
314
+ wroteIn: (request)=>createCrmPersonRecords(defaultCrmPersonRecordsDeps()).wroteIn(request)
315
+ };
316
+
317
+ //# sourceMappingURL=person-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/person-records.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 {\n containerMembershipField,\n contactContainerFieldPath,\n normalizeContainerIds,\n} from '@aglyn/aglyn/app-utils/container-membership'\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport {\n CRM_COLLECTIONS,\n crmLeadStatus,\n crmViewIsListed,\n isCrmLeadOpen,\n normalizeCrmViewFilters,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { dynamicListDimensionsForCrmView } from '@aglyn/aglyn/app-utils/dynamic-list-rule'\nimport { personKey } from '@aglyn/aglyn/app-utils/person-key'\nimport {\n scopeTokensForHost,\n seenOnlyThroughGrant,\n visibleToHost,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type {\n PluginPersonFileRequest,\n PluginPersonFindRequest,\n PluginPersonReadRequest,\n PluginPersonRecord,\n PluginPersonRecordRef,\n PluginPersonRecords,\n PluginPersonViewPeople,\n PluginPersonViewRequest,\n PluginPersonWroteInRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-records'\nimport {\n consentGroupForSite,\n firebaseAdmin,\n resolveOrgIdForHost,\n} from '@aglyn/tenant-data-admin'\n// The leaves, not the barrel: this plugin's specs substitute the barrel\n// wholesale, and the lookup and the restamp must reach the real logic.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { restampCrmListFieldsAt } from '@aglyn/tenant-data-admin/server/crm-records'\nimport { collectDynamicListCandidates } from '@aglyn/tenant-data-admin/server/dynamic-list-materialize'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { recordPersonRefund } from './person-refund'\n\n/**\n * THE CRM AS THE PLUGIN THAT KEEPS PEOPLE, asked by the plugins that do not\n * (AGL-3080) — the CRM's side of `plugin-person-records`.\n *\n * Two kinds of record are a person here, in the words `plugin-record-routes`\n * addresses them by: a `contact`, the person once they are known, filed at\n * `orgs/{orgId}/contacts/{id}`; and a `lead`, the person before anybody has\n * qualified them, at `orgs/{orgId}/leads/{personKey}` (AGL-3275). One person\n * is one record (AGL-3232), so a lookup by address answers the contact when\n * there is one and the lead only for a caller that asked for any kind.\n *\n * The record's `data` is the stored document. Beside the fields the platform\n * defines (`marketingConsent…`, `emailState`, `visibleTo`), a contact keeps\n * what each consent group knows of the person in `facets.{groupId}` — the\n * sources, stage, owner, company, address and tags — and a lead keeps the\n * same facts at its top level.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The person kinds, as `plugin-record-routes` addresses them. */\nexport const CRM_PERSON_KINDS = { contact: 'contact', lead: 'lead' } as const\n\n/** The collection each person kind is stored in, under the organization. */\nconst STORED_IN: Readonly<Record<string, string>> = {\n [CRM_PERSON_KINDS.contact]: 'contacts',\n [CRM_PERSON_KINDS.lead]: 'leads',\n}\n\n/** `getAll` takes this many references per call. */\nconst GET_ALL_CHUNK = 300\n\n/**\n * The most leads a saved Leads view is taken from: the site's most recently\n * seen, the Leads list's own window (AGL-3234).\n */\nexport const CRM_LEADS_VIEW_WINDOW = 200\n\nexport interface CrmPersonRecordsDeps {\n firestore(): Firestore\n /** The organization a site belongs to, or `null` for one with none. */\n orgIdForHost(hostId: string): Promise<string | null>\n /** The consent group a site files its facet under. */\n groupIdForHost(hostId: string): Promise<string>\n /**\n * The addresses a saved Contacts view selects among one site's contacts,\n * read the way the dynamic-list sweep reads one, and whether the read\n * reached the whole view before its budget.\n */\n contactViewEmails(input: { hostId: string; viewId: string }): Promise<{ emails: string[]; complete: boolean }>\n}\n\nexport function defaultCrmPersonRecordsDeps(): CrmPersonRecordsDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n orgIdForHost: async (hostId) => (await resolveOrgIdForHost(hostId)) ?? null,\n groupIdForHost: async (hostId) => (await consentGroupForSite(hostId)).groupId,\n contactViewEmails: async ({ hostId, viewId }) => {\n const scan = await collectDynamicListCandidates({\n hostId,\n rule: { sources: ['contacts'], viewId },\n })\n return { emails: scan.candidates.map((candidate) => candidate.email), complete: scan.complete }\n },\n }\n}\n\nfunction personOf(kind: string, snapshot: FirebaseFirestore.DocumentSnapshot): PluginPersonRecord {\n const data = (snapshot.data() ?? {}) as Record<string, unknown>\n return { kind, id: snapshot.id, email: normalizeContactEmail(data['email']), data }\n}\n\n/**\n * The organization a request names, or the one its site belongs to. A site\n * with no organization THROWS, as the org-data lookup every caller used to\n * make threw: each caller has already decided which way a failed read falls.\n */\nasync function orgOf(\n deps: CrmPersonRecordsDeps,\n request: { orgId?: string | null; hostId?: string | null },\n): Promise<string> {\n const named = String(request.orgId ?? '').trim()\n if (named) return named\n const hostId = String(request.hostId ?? '').trim()\n const resolved = hostId ? await deps.orgIdForHost(hostId) : null\n if (!resolved) throw new Error(`[crm] no organization to find a person in for host ${hostId || '(none)'}`)\n return resolved\n}\n\nexport function createCrmPersonRecords(deps: CrmPersonRecordsDeps): PluginPersonRecords {\n return {\n /**\n * The contact first, through the org's address index (AGL-2633) so an\n * address a merge folded into another record still names the person who\n * now holds it; then, for a caller that asked for any kind, the lead the\n * address is keyed as. Narrowed to the site when asked, on the document\n * the address names: a record the site cannot see is `null`.\n */\n async find(request: PluginPersonFindRequest) {\n const email = normalizeContactEmail(request.email)\n if (!email) return null\n const orgId = await orgOf(deps, request)\n const org = deps.firestore().collection('orgs').doc(orgId)\n const hostId = String(request.hostId ?? '').trim()\n const narrowTo = request.onlyVisibleToSite && hostId ? hostId : null\n const contact = await findContactByEmail(\n org.collection(STORED_IN[CRM_PERSON_KINDS.contact]),\n email,\n narrowTo ? { hostId: narrowTo } : {},\n )\n if (contact) return personOf(CRM_PERSON_KINDS.contact, contact)\n if (!request.anyKind) return null\n const key = personKey(email)\n if (!key) return null\n const lead = await org.collection(STORED_IN[CRM_PERSON_KINDS.lead]).doc(key).get()\n if (!lead.exists) return null\n if (narrowTo && !visibleToHost(lead.get('visibleTo') as string[] | undefined, narrowTo)) {\n return null\n }\n return personOf(CRM_PERSON_KINDS.lead, lead)\n },\n\n /** Every record named, read at once; a kind the CRM does not keep is `null`. */\n async read(request: PluginPersonReadRequest) {\n const firestore = deps.firestore()\n const org = firestore.collection('orgs').doc(request.orgId)\n const refs = request.records.map((record) => {\n const collection = STORED_IN[record.kind]\n const id = String(record.id ?? '').trim()\n return collection && id ? org.collection(collection).doc(id) : null\n })\n const wanted = refs.filter((ref): ref is FirebaseFirestore.DocumentReference => ref !== null)\n const snapshots: FirebaseFirestore.DocumentSnapshot[] = []\n for (let start = 0; start < wanted.length; start += GET_ALL_CHUNK) {\n snapshots.push(...(await firestore.getAll(...wanted.slice(start, start + GET_ALL_CHUNK))))\n }\n let at = 0\n return request.records.map((record, index) => {\n if (!refs[index]) return null\n const snapshot = snapshots[at++]\n return snapshot?.exists ? personOf(record.kind, snapshot) : null\n })\n },\n\n /**\n * Files the person under the containers as the site holds them: on a\n * contact, inside the site's consent-group facet — which campaigns a\n * merchant filed somebody under is that merchant's record, and written at\n * the top it would be readable by every site of an agency's account; on a\n * lead, in the lead's own membership field, whose list fields are then\n * restamped so the Leads list's filter finds it (AGL-3321).\n *\n * `update`, never a merge-set: a dotted path in `set` is a literal field\n * name, and the record must exist. A record the site cannot see is not\n * filed.\n */\n async fileUnder(request: PluginPersonFileRequest) {\n const ids = normalizeContainerIds(request.ids)\n const collection = STORED_IN[request.record.kind]\n const id = String(request.record.id ?? '').trim()\n if (!ids.length || !collection || !id) return { filed: false }\n const orgId = await orgOf(deps, request)\n const ref = deps.firestore().collection('orgs').doc(orgId).collection(collection).doc(id)\n const snapshot = await ref.get()\n if (!snapshot.exists) return { filed: false }\n if (!visibleToHost(snapshot.get('visibleTo') as string[] | undefined, request.hostId)) {\n return { filed: false }\n }\n if (request.record.kind === CRM_PERSON_KINDS.lead) {\n await ref.update({\n [containerMembershipField(request.containerKind)]: FieldValue.arrayUnion(...ids),\n updatedAt: FieldValue.serverTimestamp(),\n })\n await restampCrmListFieldsAt(ref, 'leads')\n return { filed: true }\n }\n const groupId = await deps.groupIdForHost(request.hostId)\n await ref.update({\n [contactContainerFieldPath(groupId, request.containerKind)]: FieldValue.arrayUnion(...ids),\n updatedAt: FieldValue.serverTimestamp(),\n })\n return { filed: true }\n },\n\n recordRefund: (request) => recordPersonRefund(request),\n\n /**\n * The people a saved view selects for a site (AGL-3234). A colleague's\n * private view is theirs: not listed for this member, so it does not\n * exist for them here either.\n *\n * A Leads view takes the site's most recently seen leads —\n * {@link CRM_LEADS_VIEW_WINDOW}, the Leads list's own window — narrowed by\n * the view's status clause the way the list narrows it: open leads when\n * the view names no status, one status when it does, everything for\n * `all`. Held, not merely seen: a lead another site only SHARED with this\n * one (AGL-3336) is not this site's to take.\n *\n * A Contacts view is read the way the dynamic-list sweep reads one, and\n * each address it selects is resolved to the contact the site may see. A\n * view filtering on something that reading cannot apply is refused whole:\n * dropping the filter would select more people than the view shows.\n */\n async peopleInView(request: PluginPersonViewRequest): Promise<PluginPersonViewPeople> {\n const firestore = deps.firestore()\n const org = firestore.collection('orgs').doc(request.orgId)\n const view = await org.collection(CRM_COLLECTIONS.views).doc(request.viewId).get()\n const data = view.exists ? ((view.data() ?? {}) as Record<string, unknown>) : null\n if (\n !data ||\n !crmViewIsListed(\n { shared: data['shared'] === true, ownerUid: String(data['ownerUid'] ?? '') },\n request.viewerUid,\n )\n ) {\n return { ok: false, reason: 'not-found' }\n }\n const filters = normalizeCrmViewFilters(data['filters'])\n if (data['section'] === 'leads') {\n const statusClause = filters.find((clause) => clause.field === 'status' && clause.op === 'equals')\n const wanted = String(statusClause?.value ?? 'open')\n // Narrowed to what the site may see before it is ordered (AGL-3275):\n // the collection is org-wide, and an agency's sequence must not be\n // offered another client's people.\n const window = await org\n .collection(STORED_IN[CRM_PERSON_KINDS.lead])\n .where('visibleTo', 'array-contains-any', scopeTokensForHost(request.hostId))\n .orderBy('lastSeenAtMs', 'desc')\n .limit(CRM_LEADS_VIEW_WINDOW + 1)\n .get()\n const matching = window.docs.slice(0, CRM_LEADS_VIEW_WINDOW).filter((doc) => {\n const lead = (doc.data() ?? {}) as Record<string, unknown>\n if (seenOnlyThroughGrant(lead, request.hostId)) return false\n if (wanted === 'all') return true\n if (wanted === 'open') return isCrmLeadOpen(lead as never) && !lead['convertedContactId']\n return crmLeadStatus(lead as never) === wanted\n })\n return {\n ok: true,\n people: matching.slice(0, request.limit).map((doc) => ({ kind: CRM_PERSON_KINDS.lead, id: doc.id })),\n total: matching.length,\n truncated: matching.length > request.limit || window.docs.length > CRM_LEADS_VIEW_WINDOW,\n }\n }\n if (data['section'] !== 'contacts') return { ok: false, reason: 'not-people' }\n const { unsupported } = dynamicListDimensionsForCrmView(filters)\n if (unsupported.length) {\n return {\n ok: false,\n reason: 'unsupported',\n unsupported: unsupported.map((clause) => clause.label || clause.field),\n }\n }\n const { emails, complete } = await deps.contactViewEmails({\n hostId: request.hostId,\n viewId: request.viewId,\n })\n const ordered = [...new Set(emails)].sort()\n const contacts = org.collection(STORED_IN[CRM_PERSON_KINDS.contact])\n const found = await Promise.all(\n ordered\n .slice(0, request.limit)\n .map((email) => findContactByEmail(contacts, email, { hostId: request.hostId })),\n )\n const ids = [...new Set(found.filter((snapshot) => snapshot).map((snapshot) => String(snapshot?.id)))]\n return {\n ok: true,\n people: ids.map((id): PluginPersonRecordRef => ({ kind: CRM_PERSON_KINDS.contact, id })),\n total: ordered.length,\n truncated: ordered.length > request.limit || !complete,\n }\n },\n\n /**\n * Whether each person has ever written in: an inbound email on their\n * timeline, filed on the contact, or on the lead while they were one\n * (AGL-3234). One keyed query each; a failed one is unknown.\n */\n async wroteIn(request: PluginPersonWroteInRequest) {\n const activities = deps\n .firestore()\n .collection('orgs')\n .doc(request.orgId)\n .collection(CRM_COLLECTIONS.activities)\n return Promise.all(\n request.records.map(async (record) => {\n const field =\n record.kind === CRM_PERSON_KINDS.contact\n ? 'contactId'\n : record.kind === CRM_PERSON_KINDS.lead\n ? 'leadId'\n : null\n const id = String(record.id ?? '').trim()\n if (!field || !id) return null\n try {\n const snapshot = await activities\n .where(field, '==', id)\n .where('direction', '==', 'inbound')\n .limit(1)\n .get()\n return !snapshot.empty\n } catch (error) {\n console.error('[crm] inbound email lookup failed; reading as unknown', error)\n return null\n }\n }),\n )\n },\n }\n}\n\n/** The service as the CRM registers it, its storage the admin app's. */\nexport const crmPersonRecords: Required<PluginPersonRecords> = {\n find: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).find(request),\n read: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).read(request),\n fileUnder: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).fileUnder!(request),\n recordRefund: (request) => recordPersonRefund(request),\n peopleInView: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).peopleInView!(request),\n wroteIn: (request) => createCrmPersonRecords(defaultCrmPersonRecordsDeps()).wroteIn!(request),\n}\n"],"names":["containerMembershipField","contactContainerFieldPath","normalizeContainerIds","normalizeContactEmail","CRM_COLLECTIONS","crmLeadStatus","crmViewIsListed","isCrmLeadOpen","normalizeCrmViewFilters","dynamicListDimensionsForCrmView","personKey","scopeTokensForHost","seenOnlyThroughGrant","visibleToHost","consentGroupForSite","firebaseAdmin","resolveOrgIdForHost","findContactByEmail","restampCrmListFieldsAt","collectDynamicListCandidates","FieldValue","recordPersonRefund","CRM_PERSON_KINDS","contact","lead","STORED_IN","GET_ALL_CHUNK","CRM_LEADS_VIEW_WINDOW","defaultCrmPersonRecordsDeps","firestore","app","orgIdForHost","hostId","groupIdForHost","groupId","contactViewEmails","viewId","scan","rule","sources","emails","candidates","map","candidate","email","complete","personOf","kind","snapshot","data","id","orgOf","deps","request","named","String","orgId","trim","resolved","Error","createCrmPersonRecords","find","org","collection","doc","narrowTo","onlyVisibleToSite","anyKind","key","get","exists","read","refs","records","record","wanted","filter","ref","snapshots","start","length","push","getAll","slice","at","index","fileUnder","ids","filed","update","containerKind","arrayUnion","updatedAt","serverTimestamp","recordRefund","peopleInView","view","views","shared","ownerUid","viewerUid","ok","reason","filters","statusClause","clause","field","op","value","window","where","orderBy","limit","matching","docs","people","total","truncated","unsupported","label","ordered","Set","sort","contacts","found","Promise","all","wroteIn","activities","empty","error","console","crmPersonRecords"],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginPersonRefundOutcome, PluginPersonRefundRequest } from '@aglyn/aglyn/plugin-manager/plugin-person-records';
18
+ /**
19
+ * Money going BACK to a customer, recorded on their contact (AGL-1754).
20
+ *
21
+ * The CRM's answer to `plugin-person-records`' `recordRefund` (AGL-3080):
22
+ * commerce reports a refund or a lost dispute through the seam, and what it
23
+ * does to the person is decided here, by the plugin that keeps them. It used
24
+ * to be commerce's own module, writing these fields onto the CRM's contacts.
25
+ *
26
+ * `upsertHostContact` accumulates `ltvCents` and nothing anywhere decremented
27
+ * it, so a customer who bought $500 and returned all of it read identically to
28
+ * one who bought $500 and kept it — and the first surface built on the field
29
+ * (RFM segmentation, a lifetime-value column, a best-customers list) would have
30
+ * ranked a serial returner among the merchant's best customers.
31
+ *
32
+ * WHAT THIS WRITES, AND WHY IT IS NOT A DECREMENT
33
+ *
34
+ * `ltvCents` is left exactly as it is and the reversal is recorded BESIDE it:
35
+ *
36
+ * | field | meaning |
37
+ * | -- | -- |
38
+ * | `ltvCents` | gross money handed over, unchanged (AGL-1748) |
39
+ * | `refundedCents` | gross money handed back |
40
+ * | `ordersCount` | orders placed, unchanged |
41
+ * | `refundedOrdersCount` | of those, how many ended fully reversed |
42
+ * | `lastRefundAtMs` | when money last went back |
43
+ *
44
+ * This is the shape AGL-1747 chose two days earlier for the same question on
45
+ * the orders CSV — `amountUsd` stayed gross under its existing name and
46
+ * `refundedUsd`/`netUsd` were appended — and it is the shape AGL-1748 named
47
+ * when it recorded the gross semantics in `upsert-contact.ts`. Netting in
48
+ * place would make a stored number mean one thing for rows written before this
49
+ * commit and another for rows written after, with nothing on the document to
50
+ * tell the two apart. Two fields cost one number's worth of storage and keep
51
+ * both facts, and a reader that wants the net has `ltvCents - refundedCents`.
52
+ *
53
+ * It also answers "can it go negative?" by moving the question off storage.
54
+ * Both figures are monotonic counters of money that actually moved in one
55
+ * direction, so neither can go negative. The DERIVED net can, for exactly one
56
+ * reason: an order placed before AGL-1748 taught POS, drafts, reservations and
57
+ * bookings to pass `purchaseCents` at all, whose sale was never added and whose
58
+ * refund is subtracted here. That is a true statement about the data, and
59
+ * clamping it away at write time would erase the only evidence that a purchase
60
+ * is missing. AGL-1753 is the backfill that reconciles it, by SETTING both
61
+ * fields from the orders — which already carry `refundedCents`, so whatever a
62
+ * reader decides here costs the rebuild nothing. A merchant-facing reader
63
+ * should show gross and refunded, and clamp only the net it ranks on.
64
+ *
65
+ * `ordersCount` is deliberately NOT decremented. A fully refunded order still
66
+ * happened, and a decrement cannot be undone by a reader that would rather
67
+ * count it — while `refundedOrdersCount` beside it lets RFM's frequency term
68
+ * net the two, or not, at the point of display. Same argument as the amount,
69
+ * one field along.
70
+ *
71
+ * WHEN THE CONTACT IS MISSING, THIS REFUSES — and records the refusal.
72
+ *
73
+ * Refusing is right on AGL-1732's test, "does refusing discard money or work
74
+ * that already happened?", because it does not: the refund is durable on the
75
+ * order (`refundedCents`, the timeline entry, Stripe), and the contact is a
76
+ * derived aggregate that AGL-1753's rebuild reads back out of those same
77
+ * orders. Creating one here would be worse three ways — creation is audience
78
+ * BAND gated, so it hard-bands a Free org (raising the AGL-891 dropped-contacts
79
+ * alert for a record the merchant never had) or meters onto a paid invoice; the
80
+ * contact it created would hold a refund and no purchase, which is the negative
81
+ * lifetime value above with nothing to reconcile it against; and it is the
82
+ * phantom-document shape the `updateExisting` sweep spent yesterday closing.
83
+ *
84
+ * So the write is an `update()`, never `set(…, { merge: true })` — the contact
85
+ * may have been deleted between the query and the write, and a merge-set would
86
+ * resurrect it as a document holding nothing but a refund. Every refusal
87
+ * increments `hosts/{hostId}/counters/contactRefundsUnmatched`, the counter
88
+ * shape `contactsDropped` already uses, so a refund that reached no contact is
89
+ * countable rather than silent.
90
+ *
91
+ * CHARGEBACKS (AGL-1554) are a separate decision and are NOT handled here.
92
+ * This takes `kind` so the handler that eventually answers
93
+ * `charge.dispute.closed` can record a lost dispute through the same writer
94
+ * with the same fields: money reversed is money reversed, whichever door it
95
+ * left by, and only the wording of the timeline entry differs.
96
+ */
97
+ export declare function recordPersonRefund(request: PluginPersonRefundRequest): Promise<PluginPersonRefundOutcome>;