@aglyn/plugins-crm 1.0.0-beta.220 → 1.0.0-beta.222

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 (514) 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-record-picker.d.ts +15 -0
  34. package/src/lib/components/crm-record-picker.js +16 -10
  35. package/src/lib/components/crm-record-picker.js.map +1 -1
  36. package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
  37. package/src/lib/components/crm-transfer-buttons.js +167 -0
  38. package/src/lib/components/crm-transfer-buttons.js.map +1 -0
  39. package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
  40. package/src/lib/components/deal-contact-roles-card.js +368 -0
  41. package/src/lib/components/deal-contact-roles-card.js.map +1 -0
  42. package/src/lib/components/deal-detail-page.d.ts +3 -2
  43. package/src/lib/components/deal-detail-page.js +25 -6
  44. package/src/lib/components/deal-detail-page.js.map +1 -1
  45. package/src/lib/components/deal-edit-drawer.d.ts +10 -0
  46. package/src/lib/components/deal-edit-drawer.js +219 -10
  47. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  48. package/src/lib/components/deal-properties-card.d.ts +7 -0
  49. package/src/lib/components/deal-properties-card.js +34 -3
  50. package/src/lib/components/deal-properties-card.js.map +1 -1
  51. package/src/lib/components/deal-stage-card.d.ts +4 -0
  52. package/src/lib/components/deal-stage-card.js +10 -2
  53. package/src/lib/components/deal-stage-card.js.map +1 -1
  54. package/src/lib/components/deals-bulk-bar.d.ts +0 -3
  55. package/src/lib/components/deals-bulk-bar.js +7 -20
  56. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  57. package/src/lib/components/deals-section.d.ts +3 -3
  58. package/src/lib/components/deals-section.js +123 -63
  59. package/src/lib/components/deals-section.js.map +1 -1
  60. package/src/lib/components/fields-section.js +31 -17
  61. package/src/lib/components/fields-section.js.map +1 -1
  62. package/src/lib/components/lead-convert-dialog.d.ts +4 -0
  63. package/src/lib/components/lead-convert-dialog.js +23 -1
  64. package/src/lib/components/lead-convert-dialog.js.map +1 -1
  65. package/src/lib/components/lead-detail-page.js +8 -2
  66. package/src/lib/components/lead-detail-page.js.map +1 -1
  67. package/src/lib/components/lead-profile-fields.d.ts +71 -0
  68. package/src/lib/components/lead-profile-fields.js +386 -0
  69. package/src/lib/components/lead-profile-fields.js.map +1 -0
  70. package/src/lib/components/lead-properties-card.d.ts +2 -1
  71. package/src/lib/components/lead-properties-card.js +85 -176
  72. package/src/lib/components/lead-properties-card.js.map +1 -1
  73. package/src/lib/components/lead-source-select.d.ts +4 -23
  74. package/src/lib/components/lead-source-select.js +9 -73
  75. package/src/lib/components/lead-source-select.js.map +1 -1
  76. package/src/lib/components/lead-status-chip.d.ts +9 -3
  77. package/src/lib/components/lead-status-chip.js +8 -4
  78. package/src/lib/components/lead-status-chip.js.map +1 -1
  79. package/src/lib/components/lead-status-options.d.ts +20 -0
  80. package/src/lib/components/lead-status-options.js +51 -0
  81. package/src/lib/components/lead-status-options.js.map +1 -0
  82. package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
  83. package/src/lib/components/lead-unqualify-dialog.js +50 -18
  84. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  85. package/src/lib/components/leads-bulk-bar.d.ts +0 -3
  86. package/src/lib/components/leads-bulk-bar.js +29 -29
  87. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  88. package/src/lib/components/leads-section.js +120 -68
  89. package/src/lib/components/leads-section.js.map +1 -1
  90. package/src/lib/components/linked-deals-card.d.ts +4 -0
  91. package/src/lib/components/linked-deals-card.js +47 -3
  92. package/src/lib/components/linked-deals-card.js.map +1 -1
  93. package/src/lib/components/log-activity-dialog.js +43 -3
  94. package/src/lib/components/log-activity-dialog.js.map +1 -1
  95. package/src/lib/components/new-contact-drawer.d.ts +8 -0
  96. package/src/lib/components/new-contact-drawer.js +108 -6
  97. package/src/lib/components/new-contact-drawer.js.map +1 -1
  98. package/src/lib/components/new-lead-drawer.d.ts +9 -2
  99. package/src/lib/components/new-lead-drawer.js +88 -121
  100. package/src/lib/components/new-lead-drawer.js.map +1 -1
  101. package/src/lib/components/picklist-select.d.ts +36 -0
  102. package/src/lib/components/picklist-select.js +114 -0
  103. package/src/lib/components/picklist-select.js.map +1 -0
  104. package/src/lib/components/picklist-values-card.d.ts +34 -0
  105. package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
  106. package/src/lib/components/picklist-values-card.js.map +1 -0
  107. package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
  108. package/src/lib/components/pipeline-stages-dialog.js +26 -4
  109. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  110. package/src/lib/components/pipelines-dialog.js +15 -6
  111. package/src/lib/components/pipelines-dialog.js.map +1 -1
  112. package/src/lib/components/recent-activity-feed.js +20 -5
  113. package/src/lib/components/recent-activity-feed.js.map +1 -1
  114. package/src/lib/components/recipes-card.js +4 -2
  115. package/src/lib/components/recipes-card.js.map +1 -1
  116. package/src/lib/components/record-tasks-card.js +15 -8
  117. package/src/lib/components/record-tasks-card.js.map +1 -1
  118. package/src/lib/components/reports/forecast-card.d.ts +6 -0
  119. package/src/lib/components/reports/forecast-card.js +101 -3
  120. package/src/lib/components/reports/forecast-card.js.map +1 -1
  121. package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
  122. package/src/lib/components/reports/lead-funnel-card.js +9 -5
  123. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  124. package/src/lib/components/reports/lead-source-card.d.ts +23 -0
  125. package/src/lib/components/reports/lead-source-card.js +318 -0
  126. package/src/lib/components/reports/lead-source-card.js.map +1 -0
  127. package/src/lib/components/reports/report-export.js +1 -1
  128. package/src/lib/components/reports/report-export.js.map +1 -1
  129. package/src/lib/components/reports-section.js +8 -0
  130. package/src/lib/components/reports-section.js.map +1 -1
  131. package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
  132. package/src/lib/components/sharing-rule-drawer.js +108 -19
  133. package/src/lib/components/sharing-rule-drawer.js.map +1 -1
  134. package/src/lib/components/sharing-rules-card.js +11 -3
  135. package/src/lib/components/sharing-rules-card.js.map +1 -1
  136. package/src/lib/components/task-cells.d.ts +9 -1
  137. package/src/lib/components/task-cells.js +9 -4
  138. package/src/lib/components/task-cells.js.map +1 -1
  139. package/src/lib/components/task-edit-drawer.js +111 -43
  140. package/src/lib/components/task-edit-drawer.js.map +1 -1
  141. package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
  142. package/src/lib/components/tasks-bulk-bar.js +7 -20
  143. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
  144. package/src/lib/components/tasks-calendar.d.ts +3 -0
  145. package/src/lib/components/tasks-calendar.js +8 -1
  146. package/src/lib/components/tasks-calendar.js.map +1 -1
  147. package/src/lib/components/tasks-section.d.ts +3 -3
  148. package/src/lib/components/tasks-section.js +58 -43
  149. package/src/lib/components/tasks-section.js.map +1 -1
  150. package/src/lib/constants/api-routes.d.ts +2 -9
  151. package/src/lib/constants/api-routes.js +1 -7
  152. package/src/lib/constants/api-routes.js.map +1 -1
  153. package/src/lib/constants/bundle-common.d.ts +6 -0
  154. package/src/lib/constants/bundle-common.js +11 -0
  155. package/src/lib/constants/bundle-common.js.map +1 -1
  156. package/src/lib/constants/company-filters.d.ts +14 -0
  157. package/src/lib/constants/company-filters.js +51 -1
  158. package/src/lib/constants/company-filters.js.map +1 -1
  159. package/src/lib/constants/contact-filters.d.ts +2 -0
  160. package/src/lib/constants/contact-filters.js +18 -1
  161. package/src/lib/constants/contact-filters.js.map +1 -1
  162. package/src/lib/constants/deal-filters.d.ts +22 -1
  163. package/src/lib/constants/deal-filters.js +67 -4
  164. package/src/lib/constants/deal-filters.js.map +1 -1
  165. package/src/lib/declarations.console-server.d.ts +3 -0
  166. package/src/lib/declarations.console-server.js +35 -0
  167. package/src/lib/declarations.console-server.js.map +1 -1
  168. package/src/lib/declarations.d.ts +26 -0
  169. package/src/lib/declarations.js +35 -0
  170. package/src/lib/declarations.js.map +1 -0
  171. package/src/lib/declarations.server.d.ts +11 -0
  172. package/src/lib/declarations.server.js +92 -1
  173. package/src/lib/declarations.server.js.map +1 -1
  174. package/src/lib/hooks/use-company-picklists.d.ts +15 -0
  175. package/src/lib/hooks/use-company-picklists.js +55 -0
  176. package/src/lib/hooks/use-company-picklists.js.map +1 -0
  177. package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
  178. package/src/lib/hooks/use-crm-picklist.js +53 -0
  179. package/src/lib/hooks/use-crm-picklist.js.map +1 -0
  180. package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
  181. package/src/lib/hooks/use-crm-record-names.js +45 -19
  182. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  183. package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
  184. package/src/lib/hooks/use-crm-task-picklists.js +45 -0
  185. package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
  186. package/src/lib/hooks/use-deals.d.ts +14 -0
  187. package/src/lib/hooks/use-deals.js +29 -0
  188. package/src/lib/hooks/use-deals.js.map +1 -1
  189. package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
  190. package/src/lib/hooks/use-lead-picklists.js +46 -0
  191. package/src/lib/hooks/use-lead-picklists.js.map +1 -0
  192. package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
  193. package/src/lib/hooks/use-lead-source-picklist.js +12 -22
  194. package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
  195. package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
  196. package/src/lib/hooks/use-lead-status-picklist.js +40 -0
  197. package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
  198. package/src/lib/model/companies.d.ts +32 -3
  199. package/src/lib/model/companies.js +133 -16
  200. package/src/lib/model/companies.js.map +1 -1
  201. package/src/lib/model/contact-merge.d.ts +27 -3
  202. package/src/lib/model/contact-merge.js +81 -7
  203. package/src/lib/model/contact-merge.js.map +1 -1
  204. package/src/lib/model/contact-record.d.ts +17 -0
  205. package/src/lib/model/contact-record.js +15 -1
  206. package/src/lib/model/contact-record.js.map +1 -1
  207. package/src/lib/model/contact-update.d.ts +31 -2
  208. package/src/lib/model/contact-update.js +14 -0
  209. package/src/lib/model/contact-update.js.map +1 -1
  210. package/src/lib/model/crm-list-query.d.ts +5 -0
  211. package/src/lib/model/crm-list-query.js +8 -0
  212. package/src/lib/model/crm-list-query.js.map +1 -1
  213. package/src/lib/model/crm-recipes.d.ts +40 -0
  214. package/src/lib/model/crm-recipes.js +244 -0
  215. package/src/lib/model/crm-recipes.js.map +1 -0
  216. package/src/lib/model/crm-record-counts.d.ts +36 -0
  217. package/src/lib/model/crm-record-counts.js +54 -0
  218. package/src/lib/model/crm-record-counts.js.map +1 -0
  219. package/src/lib/model/crm-record-lists.d.ts +23 -0
  220. package/src/lib/model/crm-record-lists.js +161 -0
  221. package/src/lib/model/crm-record-lists.js.map +1 -0
  222. package/src/lib/model/crm-reports.d.ts +73 -3
  223. package/src/lib/model/crm-reports.js +120 -1
  224. package/src/lib/model/crm-reports.js.map +1 -1
  225. package/src/lib/model/crm-sharing.d.ts +21 -3
  226. package/src/lib/model/crm-sharing.js +27 -6
  227. package/src/lib/model/crm-sharing.js.map +1 -1
  228. package/src/lib/model/deal-board-model.d.ts +8 -2
  229. package/src/lib/model/deal-board-model.js +15 -2
  230. package/src/lib/model/deal-board-model.js.map +1 -1
  231. package/src/lib/model/deal-contact-roles.d.ts +45 -0
  232. package/src/lib/model/deal-contact-roles.js +73 -0
  233. package/src/lib/model/deal-contact-roles.js.map +1 -0
  234. package/src/lib/model/deal-form-model.d.ts +56 -0
  235. package/src/lib/model/deal-form-model.js +79 -7
  236. package/src/lib/model/deal-form-model.js.map +1 -1
  237. package/src/lib/model/download-text-file.d.ts +23 -0
  238. package/src/lib/model/download-text-file.js +34 -0
  239. package/src/lib/model/download-text-file.js.map +1 -0
  240. package/src/lib/model/lead-filters.d.ts +36 -2
  241. package/src/lib/model/lead-filters.js +123 -2
  242. package/src/lib/model/lead-filters.js.map +1 -1
  243. package/src/lib/model/lead-source-direction.d.ts +38 -0
  244. package/src/lib/model/lead-source-direction.js +52 -0
  245. package/src/lib/model/lead-source-direction.js.map +1 -0
  246. package/src/lib/model/picklist-values.d.ts +119 -0
  247. package/src/lib/model/picklist-values.js +256 -0
  248. package/src/lib/model/picklist-values.js.map +1 -0
  249. package/src/lib/model/record-facts-disclosed.d.ts +81 -0
  250. package/src/lib/model/record-facts-disclosed.js +281 -0
  251. package/src/lib/model/record-facts-disclosed.js.map +1 -0
  252. package/src/lib/model/record-facts.d.ts +215 -34
  253. package/src/lib/model/record-facts.js +307 -128
  254. package/src/lib/model/record-facts.js.map +1 -1
  255. package/src/lib/model/task-picklists.d.ts +52 -0
  256. package/src/lib/model/task-picklists.js +83 -0
  257. package/src/lib/model/task-picklists.js.map +1 -0
  258. package/src/lib/model/task-routes.d.ts +13 -0
  259. package/src/lib/model/task-routes.js +23 -4
  260. package/src/lib/model/task-routes.js.map +1 -1
  261. package/src/lib/plan-entitlements.js +6 -4
  262. package/src/lib/plan-entitlements.js.map +1 -1
  263. package/src/lib/plugin.js +17 -0
  264. package/src/lib/plugin.js.map +1 -1
  265. package/src/lib/server/api-v1/contacts.js +220 -14
  266. package/src/lib/server/api-v1/contacts.js.map +1 -1
  267. package/src/lib/server/api-v1/crm-activities.js +14 -1
  268. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  269. package/src/lib/server/api-v1/crm-companies.js +123 -14
  270. package/src/lib/server/api-v1/crm-companies.js.map +1 -1
  271. package/src/lib/server/api-v1/crm-deals.js +291 -26
  272. package/src/lib/server/api-v1/crm-deals.js.map +1 -1
  273. package/src/lib/server/api-v1/crm-leads.js +144 -38
  274. package/src/lib/server/api-v1/crm-leads.js.map +1 -1
  275. package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
  276. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  277. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  278. package/src/lib/server/api-v1/crm-tasks.js +75 -33
  279. package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
  280. package/src/lib/server/api-v1/openapi.d.ts +0 -11
  281. package/src/lib/server/api-v1/openapi.js +171 -16
  282. package/src/lib/server/api-v1/openapi.js.map +1 -1
  283. package/src/lib/server/assign-contact-owner.d.ts +162 -0
  284. package/src/lib/server/assign-contact-owner.js +340 -0
  285. package/src/lib/server/assign-contact-owner.js.map +1 -0
  286. package/src/lib/server/associate-company-by-domain.d.ts +70 -0
  287. package/src/lib/server/associate-company-by-domain.js +153 -0
  288. package/src/lib/server/associate-company-by-domain.js.map +1 -0
  289. package/src/lib/server/automation-steps.d.ts +139 -0
  290. package/src/lib/server/automation-steps.js +472 -0
  291. package/src/lib/server/automation-steps.js.map +1 -0
  292. package/src/lib/server/capture-contact.js +69 -22
  293. package/src/lib/server/capture-contact.js.map +1 -1
  294. package/src/lib/server/capture-host-contact.d.ts +104 -0
  295. package/src/lib/server/capture-host-contact.js +151 -0
  296. package/src/lib/server/capture-host-contact.js.map +1 -0
  297. package/src/lib/server/company-children.d.ts +25 -0
  298. package/src/lib/server/company-children.js +42 -0
  299. package/src/lib/server/company-children.js.map +1 -0
  300. package/src/lib/server/company-delete.d.ts +18 -0
  301. package/src/lib/server/company-delete.js +44 -21
  302. package/src/lib/server/company-delete.js.map +1 -1
  303. package/src/lib/server/contact-email-history.js +21 -19
  304. package/src/lib/server/contact-email-history.js.map +1 -1
  305. package/src/lib/server/contact-remove.js +27 -2
  306. package/src/lib/server/contact-remove.js.map +1 -1
  307. package/src/lib/server/contact-reports-to.d.ts +61 -0
  308. package/src/lib/server/contact-reports-to.js +114 -0
  309. package/src/lib/server/contact-reports-to.js.map +1 -0
  310. package/src/lib/server/contact-update.d.ts +42 -1
  311. package/src/lib/server/contact-update.js +204 -23
  312. package/src/lib/server/contact-update.js.map +1 -1
  313. package/src/lib/server/convert-host-lead.d.ts +46 -1
  314. package/src/lib/server/convert-host-lead.js +173 -15
  315. package/src/lib/server/convert-host-lead.js.map +1 -1
  316. package/src/lib/server/convert-open-lead.js +12 -5
  317. package/src/lib/server/convert-open-lead.js.map +1 -1
  318. package/src/lib/server/crm-record-indexes.d.ts +45 -0
  319. package/src/lib/server/crm-record-indexes.js +78 -0
  320. package/src/lib/server/crm-record-indexes.js.map +1 -0
  321. package/src/lib/server/crm-sharing.d.ts +9 -1
  322. package/src/lib/server/crm-sharing.js +23 -3
  323. package/src/lib/server/crm-sharing.js.map +1 -1
  324. package/src/lib/server/deal-contact-roles.d.ts +27 -0
  325. package/src/lib/server/deal-contact-roles.js +89 -0
  326. package/src/lib/server/deal-contact-roles.js.map +1 -0
  327. package/src/lib/server/lead-convert.d.ts +5 -0
  328. package/src/lib/server/lead-convert.js +19 -3
  329. package/src/lib/server/lead-convert.js.map +1 -1
  330. package/src/lib/server/lead-create.d.ts +17 -2
  331. package/src/lib/server/lead-create.js +46 -14
  332. package/src/lib/server/lead-create.js.map +1 -1
  333. package/src/lib/server/lead-nurturing.d.ts +6 -0
  334. package/src/lib/server/lead-nurturing.js +57 -20
  335. package/src/lib/server/lead-nurturing.js.map +1 -1
  336. package/src/lib/server/lead-picklists.d.ts +35 -0
  337. package/src/lib/server/lead-picklists.js +52 -0
  338. package/src/lib/server/lead-picklists.js.map +1 -0
  339. package/src/lib/server/lead-source-picklist.d.ts +1 -13
  340. package/src/lib/server/lead-source-picklist.js +6 -33
  341. package/src/lib/server/lead-source-picklist.js.map +1 -1
  342. package/src/lib/server/merge-contacts.js +22 -4
  343. package/src/lib/server/merge-contacts.js.map +1 -1
  344. package/src/lib/server/person-eraser.d.ts +53 -0
  345. package/src/lib/server/person-eraser.js +149 -0
  346. package/src/lib/server/person-eraser.js.map +1 -0
  347. package/src/lib/server/person-records.d.ts +69 -0
  348. package/src/lib/server/person-records.js +317 -0
  349. package/src/lib/server/person-records.js.map +1 -0
  350. package/src/lib/server/person-refund.d.ts +97 -0
  351. package/src/lib/server/person-refund.js +221 -0
  352. package/src/lib/server/person-refund.js.map +1 -0
  353. package/src/lib/server/picklist-values.d.ts +21 -0
  354. package/src/lib/server/picklist-values.js +308 -0
  355. package/src/lib/server/picklist-values.js.map +1 -0
  356. package/src/lib/server/read-picklist.d.ts +44 -0
  357. package/src/lib/server/read-picklist.js +65 -0
  358. package/src/lib/server/read-picklist.js.map +1 -0
  359. package/src/lib/server/recipe-routes.js +15 -7
  360. package/src/lib/server/recipe-routes.js.map +1 -1
  361. package/src/lib/server/record-email-state.js +7 -1
  362. package/src/lib/server/record-email-state.js.map +1 -1
  363. package/src/lib/server/record-facts.d.ts +3 -0
  364. package/src/lib/server/record-facts.js +233 -31
  365. package/src/lib/server/record-facts.js.map +1 -1
  366. package/src/lib/server/record-origin.d.ts +27 -0
  367. package/src/lib/server/record-origin.js +104 -0
  368. package/src/lib/server/record-origin.js.map +1 -0
  369. package/src/lib/server/record-timeline.js +15 -2
  370. package/src/lib/server/record-timeline.js.map +1 -1
  371. package/src/lib/server/suite-gate.js +4 -3
  372. package/src/lib/server/suite-gate.js.map +1 -1
  373. package/src/lib/server/task-routes.js +30 -16
  374. package/src/lib/server/task-routes.js.map +1 -1
  375. package/src/lib/server-deal-stage.d.ts +5 -1
  376. package/src/lib/server-deal-stage.js +10 -5
  377. package/src/lib/server-deal-stage.js.map +1 -1
  378. package/src/lib/server.d.ts +8 -3
  379. package/src/lib/server.js +47 -43
  380. package/src/lib/server.js.map +1 -1
  381. package/src/lib/testing/memory-firestore.d.ts +55 -0
  382. package/src/lib/testing/memory-firestore.js +335 -0
  383. package/src/lib/testing/memory-firestore.js.map +1 -0
  384. package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
  385. package/src/lib/testing/stand-in-email-send-index.js +49 -0
  386. package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
  387. package/src/lib/transfer/activities.d.ts +30 -0
  388. package/src/lib/transfer/activities.js +132 -0
  389. package/src/lib/transfer/activities.js.map +1 -0
  390. package/src/lib/transfer/aliases.d.ts +22 -0
  391. package/src/lib/transfer/aliases.js +1342 -0
  392. package/src/lib/transfer/aliases.js.map +1 -0
  393. package/src/lib/transfer/common.d.ts +222 -0
  394. package/src/lib/transfer/common.js +832 -0
  395. package/src/lib/transfer/common.js.map +1 -0
  396. package/src/lib/transfer/companies.d.ts +43 -0
  397. package/src/lib/transfer/companies.js +549 -0
  398. package/src/lib/transfer/companies.js.map +1 -0
  399. package/src/lib/transfer/contacts.d.ts +31 -0
  400. package/src/lib/transfer/contacts.js +727 -0
  401. package/src/lib/transfer/contacts.js.map +1 -0
  402. package/src/lib/transfer/deals.d.ts +66 -0
  403. package/src/lib/transfer/deals.js +698 -0
  404. package/src/lib/transfer/deals.js.map +1 -0
  405. package/src/lib/transfer/email-templates-package.d.ts +40 -0
  406. package/src/lib/transfer/email-templates-package.js +76 -0
  407. package/src/lib/transfer/email-templates-package.js.map +1 -0
  408. package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
  409. package/src/lib/transfer/email-templates-package.server.js +176 -0
  410. package/src/lib/transfer/email-templates-package.server.js.map +1 -0
  411. package/src/lib/transfer/fields.d.ts +102 -0
  412. package/src/lib/transfer/fields.js +1755 -0
  413. package/src/lib/transfer/fields.js.map +1 -0
  414. package/src/lib/transfer/leads.d.ts +30 -0
  415. package/src/lib/transfer/leads.js +582 -0
  416. package/src/lib/transfer/leads.js.map +1 -0
  417. package/src/lib/transfer/pipelines.d.ts +21 -0
  418. package/src/lib/transfer/pipelines.js +172 -0
  419. package/src/lib/transfer/pipelines.js.map +1 -0
  420. package/src/lib/transfer/register.d.ts +47 -0
  421. package/src/lib/transfer/register.js +277 -0
  422. package/src/lib/transfer/register.js.map +1 -0
  423. package/src/lib/transfer/tasks.d.ts +30 -0
  424. package/src/lib/transfer/tasks.js +418 -0
  425. package/src/lib/transfer/tasks.js.map +1 -0
  426. package/src/lib/transfer/ui.d.ts +20 -0
  427. package/src/lib/transfer/ui.js +42 -0
  428. package/src/lib/transfer/ui.js.map +1 -0
  429. package/src/lib/components/company-import-drawer.d.ts +0 -42
  430. package/src/lib/components/company-import-drawer.js +0 -99
  431. package/src/lib/components/company-import-drawer.js.map +0 -1
  432. package/src/lib/components/contact-import-drawer.d.ts +0 -46
  433. package/src/lib/components/contact-import-drawer.js +0 -149
  434. package/src/lib/components/contact-import-drawer.js.map +0 -1
  435. package/src/lib/components/crm-export-all-button.d.ts +0 -39
  436. package/src/lib/components/crm-export-all-button.js +0 -98
  437. package/src/lib/components/crm-export-all-button.js.map +0 -1
  438. package/src/lib/components/csv-import-drawer.d.ts +0 -139
  439. package/src/lib/components/csv-import-drawer.js +0 -757
  440. package/src/lib/components/csv-import-drawer.js.map +0 -1
  441. package/src/lib/components/deal-import-drawer.d.ts +0 -24
  442. package/src/lib/components/deal-import-drawer.js +0 -83
  443. package/src/lib/components/deal-import-drawer.js.map +0 -1
  444. package/src/lib/components/lead-import-drawer.d.ts +0 -27
  445. package/src/lib/components/lead-import-drawer.js +0 -118
  446. package/src/lib/components/lead-import-drawer.js.map +0 -1
  447. package/src/lib/components/lead-source-values-card.d.ts +0 -27
  448. package/src/lib/components/lead-source-values-card.js.map +0 -1
  449. package/src/lib/components/task-import-drawer.d.ts +0 -24
  450. package/src/lib/components/task-import-drawer.js +0 -82
  451. package/src/lib/components/task-import-drawer.js.map +0 -1
  452. package/src/lib/model/companies-csv.d.ts +0 -36
  453. package/src/lib/model/companies-csv.js +0 -38
  454. package/src/lib/model/companies-csv.js.map +0 -1
  455. package/src/lib/model/contacts-csv.d.ts +0 -54
  456. package/src/lib/model/contacts-csv.js +0 -67
  457. package/src/lib/model/contacts-csv.js.map +0 -1
  458. package/src/lib/model/crm-company-import.d.ts +0 -154
  459. package/src/lib/model/crm-company-import.js +0 -331
  460. package/src/lib/model/crm-company-import.js.map +0 -1
  461. package/src/lib/model/crm-csv.d.ts +0 -239
  462. package/src/lib/model/crm-csv.js +0 -467
  463. package/src/lib/model/crm-csv.js.map +0 -1
  464. package/src/lib/model/crm-deal-import.d.ts +0 -112
  465. package/src/lib/model/crm-deal-import.js +0 -263
  466. package/src/lib/model/crm-deal-import.js.map +0 -1
  467. package/src/lib/model/crm-import.d.ts +0 -250
  468. package/src/lib/model/crm-import.js +0 -467
  469. package/src/lib/model/crm-import.js.map +0 -1
  470. package/src/lib/model/crm-lead-import.d.ts +0 -138
  471. package/src/lib/model/crm-lead-import.js +0 -446
  472. package/src/lib/model/crm-lead-import.js.map +0 -1
  473. package/src/lib/model/crm-task-import.d.ts +0 -94
  474. package/src/lib/model/crm-task-import.js +0 -275
  475. package/src/lib/model/crm-task-import.js.map +0 -1
  476. package/src/lib/model/deals-csv.d.ts +0 -32
  477. package/src/lib/model/deals-csv.js +0 -36
  478. package/src/lib/model/deals-csv.js.map +0 -1
  479. package/src/lib/model/import-column-shapes.d.ts +0 -36
  480. package/src/lib/model/import-column-shapes.js +0 -76
  481. package/src/lib/model/import-column-shapes.js.map +0 -1
  482. package/src/lib/model/lead-source-values.d.ts +0 -82
  483. package/src/lib/model/lead-source-values.js +0 -149
  484. package/src/lib/model/lead-source-values.js.map +0 -1
  485. package/src/lib/model/leads-csv.d.ts +0 -40
  486. package/src/lib/model/leads-csv.js +0 -40
  487. package/src/lib/model/leads-csv.js.map +0 -1
  488. package/src/lib/model/tasks-csv.d.ts +0 -28
  489. package/src/lib/model/tasks-csv.js +0 -28
  490. package/src/lib/model/tasks-csv.js.map +0 -1
  491. package/src/lib/server/companies-import.d.ts +0 -57
  492. package/src/lib/server/companies-import.js +0 -242
  493. package/src/lib/server/companies-import.js.map +0 -1
  494. package/src/lib/server/contacts-import.d.ts +0 -109
  495. package/src/lib/server/contacts-import.js +0 -393
  496. package/src/lib/server/contacts-import.js.map +0 -1
  497. package/src/lib/server/deals-import.d.ts +0 -76
  498. package/src/lib/server/deals-import.js +0 -248
  499. package/src/lib/server/deals-import.js.map +0 -1
  500. package/src/lib/server/export-route.d.ts +0 -60
  501. package/src/lib/server/export-route.js +0 -447
  502. package/src/lib/server/export-route.js.map +0 -1
  503. package/src/lib/server/import-context.d.ts +0 -87
  504. package/src/lib/server/import-context.js +0 -182
  505. package/src/lib/server/import-context.js.map +0 -1
  506. package/src/lib/server/lead-source-values.d.ts +0 -50
  507. package/src/lib/server/lead-source-values.js +0 -191
  508. package/src/lib/server/lead-source-values.js.map +0 -1
  509. package/src/lib/server/leads-import.d.ts +0 -84
  510. package/src/lib/server/leads-import.js +0 -367
  511. package/src/lib/server/leads-import.js.map +0 -1
  512. package/src/lib/server/tasks-import.d.ts +0 -54
  513. package/src/lib/server/tasks-import.js +0 -160
  514. package/src/lib/server/tasks-import.js.map +0 -1
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * A CONTACT'S REPORTS-TO (AGL-3515): Salesforce's Reports To, the person's
19
+ * manager, as another contact's id in the holder's facet.
20
+ *
21
+ * The pointer is per holder like the rest of the profile, so a chain is
22
+ * walked through ONE holder's facets: who reports to whom is one business's
23
+ * knowledge of an org chart, and another holder's guess at it is not part
24
+ * of this one's chain.
25
+ */
26
+ /** The longest chain a loop check walks — far past any real org chart. */
27
+ export declare const CONTACT_REPORTS_TO_DEPTH = 25;
28
+ export declare const CONTACT_REPORTS_TO_SELF_REFUSAL = "A contact cannot report to themselves.";
29
+ export declare const CONTACT_REPORTS_TO_LOOP_REFUSAL = "That would make a loop: the person picked already reports, through others, to this contact.";
30
+ /** A contact document by id, or `null` when there is none. */
31
+ export type ContactReader = (contactId: string) => Promise<Record<string, unknown> | null>;
32
+ /**
33
+ * Whether making `contactId` report to `managerId` closes a loop: the
34
+ * manager's own chain, through `groupId`'s facets, reaches `contactId`.
35
+ * A chain longer than {@link CONTACT_REPORTS_TO_DEPTH} is treated as a loop,
36
+ * because a walk that cannot finish cannot prove there is none.
37
+ */
38
+ export declare function contactReportsToLoops(read: ContactReader, contactId: string, managerId: string, groupId: string): Promise<boolean>;
39
+ /** A reader over one org's contacts that reads each document once. */
40
+ export declare function cachedContactReader(contacts: FirebaseFirestore.CollectionReference): ContactReader;
41
+ /**
42
+ * Every contact that reports to `from`, in any of `groupIds`' facets,
43
+ * pointed at `to` — or, with `to` null, cleared. A contact that would
44
+ * then report to itself is cleared instead. Answers how many pointers moved.
45
+ *
46
+ * Queried per holder, because the pointer lives at
47
+ * `facets.{groupId}.reportsToContactId` and a query names one path. Each
48
+ * failure is logged and the sweep goes on: a stale pointer is read as
49
+ * "nobody" by every surface, which is the safe way for one to be left.
50
+ */
51
+ export declare function repointContactReportsTo(firestore: FirebaseFirestore.Firestore, contacts: FirebaseFirestore.CollectionReference, groupIds: Iterable<string>, from: string, to: string | null, label: string): Promise<number>;
52
+ /** The holders a contact document carries a facet for — every facet a pointer at it can sit in. */
53
+ export declare function contactFacetHolders(contact: Record<string, unknown>): string[];
54
+ /**
55
+ * Every reports-to in `groupIds`' facets that names `contactId`, cleared —
56
+ * a contact deleted, or let go by those holders (AGL-3537). A holder can
57
+ * point only at a contact it holds, so the holders of the removed record
58
+ * are every facet a pointer can sit in; the sweep is `repointContactReportsTo`
59
+ * to nobody, which logs a failure and goes on rather than failing the removal.
60
+ */
61
+ export declare function clearReportsToOf(firestore: FirebaseFirestore.Firestore, contacts: FirebaseFirestore.CollectionReference, groupIds: Iterable<string>, contactId: string): Promise<number>;
@@ -0,0 +1,114 @@
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 { CONTACT_FACETS_FIELD, contactFacetPath, readContactFacet } from "@aglyn/aglyn/app-utils/contacts";
17
+ import { FieldValue } from "firebase-admin/firestore";
18
+ /**
19
+ * A CONTACT'S REPORTS-TO (AGL-3515): Salesforce's Reports To, the person's
20
+ * manager, as another contact's id in the holder's facet.
21
+ *
22
+ * The pointer is per holder like the rest of the profile, so a chain is
23
+ * walked through ONE holder's facets: who reports to whom is one business's
24
+ * knowledge of an org chart, and another holder's guess at it is not part
25
+ * of this one's chain.
26
+ */ /** The longest chain a loop check walks — far past any real org chart. */ export const CONTACT_REPORTS_TO_DEPTH = 25;
27
+ export const CONTACT_REPORTS_TO_SELF_REFUSAL = 'A contact cannot report to themselves.';
28
+ export const CONTACT_REPORTS_TO_LOOP_REFUSAL = 'That would make a loop: the person picked already reports, through others, to this contact.';
29
+ /**
30
+ * Whether making `contactId` report to `managerId` closes a loop: the
31
+ * manager's own chain, through `groupId`'s facets, reaches `contactId`.
32
+ * A chain longer than {@link CONTACT_REPORTS_TO_DEPTH} is treated as a loop,
33
+ * because a walk that cannot finish cannot prove there is none.
34
+ */ export async function contactReportsToLoops(read, contactId, managerId, groupId) {
35
+ let current = managerId;
36
+ const seen = new Set();
37
+ for(let step = 0; current && step < CONTACT_REPORTS_TO_DEPTH; step += 1){
38
+ if (current === contactId) return true;
39
+ // A loop above the contact that does not pass through it is someone
40
+ // else's to untangle, and does not stop this write.
41
+ if (seen.has(current)) return false;
42
+ seen.add(current);
43
+ const doc = await read(current);
44
+ if (!doc) return false;
45
+ current = readContactFacet(doc, groupId).reportsToContactId || undefined;
46
+ }
47
+ return Boolean(current);
48
+ }
49
+ /** A reader over one org's contacts that reads each document once. */ export function cachedContactReader(contacts) {
50
+ const cache = new Map();
51
+ return (contactId)=>{
52
+ let pending = cache.get(contactId);
53
+ if (!pending) {
54
+ pending = contacts.doc(contactId).get().then((snapshot)=>{
55
+ var _snapshot_data;
56
+ return snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {} : null;
57
+ });
58
+ cache.set(contactId, pending);
59
+ }
60
+ return pending;
61
+ };
62
+ }
63
+ /** How many pointing rows one pass reads and one batch writes. */ const PAGE = 400;
64
+ /** A ceiling on passes per holder. */ const PASSES = 25;
65
+ /**
66
+ * Every contact that reports to `from`, in any of `groupIds`' facets,
67
+ * pointed at `to` — or, with `to` null, cleared. A contact that would
68
+ * then report to itself is cleared instead. Answers how many pointers moved.
69
+ *
70
+ * Queried per holder, because the pointer lives at
71
+ * `facets.{groupId}.reportsToContactId` and a query names one path. Each
72
+ * failure is logged and the sweep goes on: a stale pointer is read as
73
+ * "nobody" by every surface, which is the safe way for one to be left.
74
+ */ export async function repointContactReportsTo(firestore, contacts, groupIds, from, to, label) {
75
+ let moved = 0;
76
+ for (const groupId of new Set(groupIds)){
77
+ if (!groupId) continue;
78
+ const path = contactFacetPath(groupId, 'reportsToContactId');
79
+ try {
80
+ for(let pass = 0; pass < PASSES; pass += 1){
81
+ const page = await contacts.where(path, '==', from).limit(PAGE).get();
82
+ if (page.empty) break;
83
+ const batch = firestore.batch();
84
+ for (const row of page.docs){
85
+ batch.update(row.ref, {
86
+ [path]: to && row.id !== to ? to : FieldValue.delete(),
87
+ updatedAt: FieldValue.serverTimestamp()
88
+ });
89
+ }
90
+ await batch.commit();
91
+ moved += page.size;
92
+ if (page.size < PAGE) break;
93
+ }
94
+ } catch (error) {
95
+ console.error(`${label}: reports-to sweep failed for a holder`, error);
96
+ }
97
+ }
98
+ return moved;
99
+ }
100
+ /** The holders a contact document carries a facet for — every facet a pointer at it can sit in. */ export function contactFacetHolders(contact) {
101
+ const facets = contact[CONTACT_FACETS_FIELD];
102
+ return facets && typeof facets === 'object' && !Array.isArray(facets) ? Object.keys(facets) : [];
103
+ }
104
+ /**
105
+ * Every reports-to in `groupIds`' facets that names `contactId`, cleared —
106
+ * a contact deleted, or let go by those holders (AGL-3537). A holder can
107
+ * point only at a contact it holds, so the holders of the removed record
108
+ * are every facet a pointer can sit in; the sweep is `repointContactReportsTo`
109
+ * to nobody, which logs a failure and goes on rather than failing the removal.
110
+ */ export function clearReportsToOf(firestore, contacts, groupIds, contactId) {
111
+ return repointContactReportsTo(firestore, contacts, groupIds, contactId, null, 'contact delete');
112
+ }
113
+
114
+ //# sourceMappingURL=contact-reports-to.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/contact-reports-to.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 CONTACT_FACETS_FIELD,\n contactFacetPath,\n readContactFacet,\n} from '@aglyn/aglyn/app-utils/contacts'\nimport { FieldValue } from 'firebase-admin/firestore'\n\n/**\n * A CONTACT'S REPORTS-TO (AGL-3515): Salesforce's Reports To, the person's\n * manager, as another contact's id in the holder's facet.\n *\n * The pointer is per holder like the rest of the profile, so a chain is\n * walked through ONE holder's facets: who reports to whom is one business's\n * knowledge of an org chart, and another holder's guess at it is not part\n * of this one's chain.\n */\n\n/** The longest chain a loop check walks — far past any real org chart. */\nexport const CONTACT_REPORTS_TO_DEPTH = 25\n\nexport const CONTACT_REPORTS_TO_SELF_REFUSAL = 'A contact cannot report to themselves.'\n\nexport const CONTACT_REPORTS_TO_LOOP_REFUSAL =\n 'That would make a loop: the person picked already reports, through others, to this contact.'\n\n/** A contact document by id, or `null` when there is none. */\nexport type ContactReader = (contactId: string) => Promise<Record<string, unknown> | null>\n\n/**\n * Whether making `contactId` report to `managerId` closes a loop: the\n * manager's own chain, through `groupId`'s facets, reaches `contactId`.\n * A chain longer than {@link CONTACT_REPORTS_TO_DEPTH} is treated as a loop,\n * because a walk that cannot finish cannot prove there is none.\n */\nexport async function contactReportsToLoops(\n read: ContactReader,\n contactId: string,\n managerId: string,\n groupId: string,\n): Promise<boolean> {\n let current: string | undefined = managerId\n const seen = new Set<string>()\n for (let step = 0; current && step < CONTACT_REPORTS_TO_DEPTH; step += 1) {\n if (current === contactId) return true\n // A loop above the contact that does not pass through it is someone\n // else's to untangle, and does not stop this write.\n if (seen.has(current)) return false\n seen.add(current)\n const doc = await read(current)\n if (!doc) return false\n current = readContactFacet(doc, groupId).reportsToContactId || undefined\n }\n return Boolean(current)\n}\n\n/** A reader over one org's contacts that reads each document once. */\nexport function cachedContactReader(\n contacts: FirebaseFirestore.CollectionReference,\n): ContactReader {\n const cache = new Map<string, Promise<Record<string, unknown> | null>>()\n return (contactId) => {\n let pending = cache.get(contactId)\n if (!pending) {\n pending = contacts\n .doc(contactId)\n .get()\n .then((snapshot) =>\n snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null,\n )\n cache.set(contactId, pending)\n }\n return pending\n }\n}\n\n/** How many pointing rows one pass reads and one batch writes. */\nconst PAGE = 400\n/** A ceiling on passes per holder. */\nconst PASSES = 25\n\n/**\n * Every contact that reports to `from`, in any of `groupIds`' facets,\n * pointed at `to` — or, with `to` null, cleared. A contact that would\n * then report to itself is cleared instead. Answers how many pointers moved.\n *\n * Queried per holder, because the pointer lives at\n * `facets.{groupId}.reportsToContactId` and a query names one path. Each\n * failure is logged and the sweep goes on: a stale pointer is read as\n * \"nobody\" by every surface, which is the safe way for one to be left.\n */\nexport async function repointContactReportsTo(\n firestore: FirebaseFirestore.Firestore,\n contacts: FirebaseFirestore.CollectionReference,\n groupIds: Iterable<string>,\n from: string,\n to: string | null,\n label: string,\n): Promise<number> {\n let moved = 0\n for (const groupId of new Set(groupIds)) {\n if (!groupId) continue\n const path = contactFacetPath(groupId, 'reportsToContactId')\n try {\n for (let pass = 0; pass < PASSES; pass += 1) {\n const page = await contacts.where(path, '==', from).limit(PAGE).get()\n if (page.empty) break\n const batch = firestore.batch()\n for (const row of page.docs) {\n batch.update(row.ref, {\n [path]: to && row.id !== to ? to : FieldValue.delete(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n await batch.commit()\n moved += page.size\n if (page.size < PAGE) break\n }\n } catch (error) {\n console.error(`${label}: reports-to sweep failed for a holder`, error)\n }\n }\n return moved\n}\n\n/** The holders a contact document carries a facet for — every facet a pointer at it can sit in. */\nexport function contactFacetHolders(contact: Record<string, unknown>): string[] {\n const facets = contact[CONTACT_FACETS_FIELD]\n return facets && typeof facets === 'object' && !Array.isArray(facets) ? Object.keys(facets) : []\n}\n\n/**\n * Every reports-to in `groupIds`' facets that names `contactId`, cleared —\n * a contact deleted, or let go by those holders (AGL-3537). A holder can\n * point only at a contact it holds, so the holders of the removed record\n * are every facet a pointer can sit in; the sweep is `repointContactReportsTo`\n * to nobody, which logs a failure and goes on rather than failing the removal.\n */\nexport function clearReportsToOf(\n firestore: FirebaseFirestore.Firestore,\n contacts: FirebaseFirestore.CollectionReference,\n groupIds: Iterable<string>,\n contactId: string,\n): Promise<number> {\n return repointContactReportsTo(firestore, contacts, groupIds, contactId, null, 'contact delete')\n}\n"],"names":["CONTACT_FACETS_FIELD","contactFacetPath","readContactFacet","FieldValue","CONTACT_REPORTS_TO_DEPTH","CONTACT_REPORTS_TO_SELF_REFUSAL","CONTACT_REPORTS_TO_LOOP_REFUSAL","contactReportsToLoops","read","contactId","managerId","groupId","current","seen","Set","step","has","add","doc","reportsToContactId","undefined","Boolean","cachedContactReader","contacts","cache","Map","pending","get","then","snapshot","exists","data","set","PAGE","PASSES","repointContactReportsTo","firestore","groupIds","from","to","label","moved","path","pass","page","where","limit","empty","batch","row","docs","update","ref","id","delete","updatedAt","serverTimestamp","commit","size","error","console","contactFacetHolders","contact","facets","Array","isArray","Object","keys","clearReportsToOf"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,oBAAoB,EACpBC,gBAAgB,EAChBC,gBAAgB,QACX,kCAAiC;AACxC,SAASC,UAAU,QAAQ,2BAA0B;AAErD;;;;;;;;CAQC,GAED,wEAAwE,GACxE,OAAO,MAAMC,2BAA2B,GAAE;AAE1C,OAAO,MAAMC,kCAAkC,yCAAwC;AAEvF,OAAO,MAAMC,kCACX,8FAA6F;AAK/F;;;;;CAKC,GACD,OAAO,eAAeC,sBACpBC,IAAmB,EACnBC,SAAiB,EACjBC,SAAiB,EACjBC,OAAe;IAEf,IAAIC,UAA8BF;IAClC,MAAMG,OAAO,IAAIC;IACjB,IAAK,IAAIC,OAAO,GAAGH,WAAWG,OAAOX,0BAA0BW,QAAQ,EAAG;QACxE,IAAIH,YAAYH,WAAW,OAAO;QAClC,oEAAoE;QACpE,oDAAoD;QACpD,IAAII,KAAKG,GAAG,CAACJ,UAAU,OAAO;QAC9BC,KAAKI,GAAG,CAACL;QACT,MAAMM,MAAM,MAAMV,KAAKI;QACvB,IAAI,CAACM,KAAK,OAAO;QACjBN,UAAUV,iBAAiBgB,KAAKP,SAASQ,kBAAkB,IAAIC;IACjE;IACA,OAAOC,QAAQT;AACjB;AAEA,oEAAoE,GACpE,OAAO,SAASU,oBACdC,QAA+C;IAE/C,MAAMC,QAAQ,IAAIC;IAClB,OAAO,CAAChB;QACN,IAAIiB,UAAUF,MAAMG,GAAG,CAAClB;QACxB,IAAI,CAACiB,SAAS;YACZA,UAAUH,SACPL,GAAG,CAACT,WACJkB,GAAG,GACHC,IAAI,CAAC,CAACC;oBACeA;uBAApBA,SAASC,MAAM,IAAKD,iBAAAA,SAASE,IAAI,cAAbF,iBAAmB,CAAC,IAAiC;;YAE7EL,MAAMQ,GAAG,CAACvB,WAAWiB;QACvB;QACA,OAAOA;IACT;AACF;AAEA,gEAAgE,GAChE,MAAMO,OAAO;AACb,oCAAoC,GACpC,MAAMC,SAAS;AAEf;;;;;;;;;CASC,GACD,OAAO,eAAeC,wBACpBC,SAAsC,EACtCb,QAA+C,EAC/Cc,QAA0B,EAC1BC,IAAY,EACZC,EAAiB,EACjBC,KAAa;IAEb,IAAIC,QAAQ;IACZ,KAAK,MAAM9B,WAAW,IAAIG,IAAIuB,UAAW;QACvC,IAAI,CAAC1B,SAAS;QACd,MAAM+B,OAAOzC,iBAAiBU,SAAS;QACvC,IAAI;YACF,IAAK,IAAIgC,OAAO,GAAGA,OAAOT,QAAQS,QAAQ,EAAG;gBAC3C,MAAMC,OAAO,MAAMrB,SAASsB,KAAK,CAACH,MAAM,MAAMJ,MAAMQ,KAAK,CAACb,MAAMN,GAAG;gBACnE,IAAIiB,KAAKG,KAAK,EAAE;gBAChB,MAAMC,QAAQZ,UAAUY,KAAK;gBAC7B,KAAK,MAAMC,OAAOL,KAAKM,IAAI,CAAE;oBAC3BF,MAAMG,MAAM,CAACF,IAAIG,GAAG,EAAE;wBACpB,CAACV,KAAK,EAAEH,MAAMU,IAAII,EAAE,KAAKd,KAAKA,KAAKpC,WAAWmD,MAAM;wBACpDC,WAAWpD,WAAWqD,eAAe;oBACvC;gBACF;gBACA,MAAMR,MAAMS,MAAM;gBAClBhB,SAASG,KAAKc,IAAI;gBAClB,IAAId,KAAKc,IAAI,GAAGzB,MAAM;YACxB;QACF,EAAE,OAAO0B,OAAO;YACdC,QAAQD,KAAK,CAAC,GAAGnB,MAAM,sCAAsC,CAAC,EAAEmB;QAClE;IACF;IACA,OAAOlB;AACT;AAEA,iGAAiG,GACjG,OAAO,SAASoB,oBAAoBC,OAAgC;IAClE,MAAMC,SAASD,OAAO,CAAC9D,qBAAqB;IAC5C,OAAO+D,UAAU,OAAOA,WAAW,YAAY,CAACC,MAAMC,OAAO,CAACF,UAAUG,OAAOC,IAAI,CAACJ,UAAU,EAAE;AAClG;AAEA;;;;;;CAMC,GACD,OAAO,SAASK,iBACdhC,SAAsC,EACtCb,QAA+C,EAC/Cc,QAA0B,EAC1B5B,SAAiB;IAEjB,OAAO0B,wBAAwBC,WAAWb,UAAUc,UAAU5B,WAAW,MAAM;AACjF"}
@@ -69,8 +69,10 @@
69
69
  * company deleted in between cannot fail the save of the people at it — the
70
70
  * company page's live aggregate is what corrects a count that did not move.
71
71
  */
72
- import { type PluginApiHandler } from '@aglyn/aglyn/server';
72
+ import { type ContactCompanyLinkPlan, type ContactCustomValue, type PluginApiHandler } from '@aglyn/aglyn/server';
73
73
  import { type ContactUpdateFields } from '../model/contact-update';
74
+ /** The sentence a `name` that contradicts the holder's first and last names is refused with. */
75
+ export declare const CONTACT_COMPOSED_NAME_REFUSAL = "This person's name is made of their first and last name \u2014 change those instead.";
74
76
  /**
75
77
  * The act a request is refused for on a plan without the CRM: the first
76
78
  * field it carries, in the order the route reads them. Every field is the
@@ -89,4 +91,43 @@ export declare function readContactUpdateFields(input: unknown): {
89
91
  ok: false;
90
92
  error: string;
91
93
  };
94
+ /**
95
+ * Salesforce's standard contact fields (AGL-3515) a create may carry beside
96
+ * the identity — every one `upsertHostContact` writes into the capturing
97
+ * facet. The reports-to is not one: it names another record, which only
98
+ * this route checks the holder can see.
99
+ */
100
+ export declare const CONTACT_CREATE_PROFILE_FIELDS: readonly ["salutation", "firstName", "lastName", "department", "mobilePhone", "homePhone", "otherPhone", "fax", "birthdate", "assistantName", "assistantPhone", "otherAddress", "doNotCall"];
101
+ /**
102
+ * Those fields of a create's body, read by the same rules an update reads
103
+ * them with — or the sentence refusing one. A body naming none of them
104
+ * reads as no fields.
105
+ */
106
+ export declare function readContactCreateProfile(body: Record<string, unknown>): {
107
+ ok: true;
108
+ fields: ContactUpdateFields;
109
+ } | {
110
+ ok: false;
111
+ error: string;
112
+ };
113
+ /** One contact's planned write, and the company counts it moves. */
114
+ export interface ContactWrite {
115
+ contactId: string;
116
+ ref: FirebaseFirestore.DocumentReference;
117
+ update: Record<string, unknown>;
118
+ counts: ContactCompanyLinkPlan['counts'];
119
+ }
120
+ /**
121
+ * One contact's update for one holder, or the sentence refusing the row.
122
+ * `update` is `null` when the document already says everything asked — a tag
123
+ * the row already carries, a link already made.
124
+ */
125
+ export declare function contactPatch(contact: Record<string, unknown>, groupId: string, fields: ContactUpdateFields, custom: Record<string, ContactCustomValue> | undefined, linkedCompanyName: string): {
126
+ update: Record<string, unknown> | null;
127
+ counts: ContactCompanyLinkPlan['counts'];
128
+ } | {
129
+ refused: string;
130
+ };
131
+ /** Each company's contacts count moved once, by the sum of what the written links moved. */
132
+ export declare function settleCompanyCounts(companies: FirebaseFirestore.CollectionReference, writes: readonly ContactWrite[]): Promise<void>;
92
133
  export declare const crmContactUpdateHandler: PluginApiHandler;
@@ -68,7 +68,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
68
68
  * link: summed per company and settled once the contacts are written, so a
69
69
  * company deleted in between cannot fail the save of the people at it — the
70
70
  * company page's live aggregate is what corrects a count that did not move.
71
- */ import { consentGroupForHost, CONTACT_FIELDS_MAX_PER_ORG, containerMembershipValue, contactContainerFieldPath, contactFacetPath, CRM_COLLECTIONS, CRM_LEAD_SOURCE_PICKLIST, CRM_MEDIA_IDS_FIELD, crmReadTokens, effectiveCrmLeadSourcePicklist, isOrgWideMember, judgeCrmLeadSource, normalizeAddress, normalizeCrmMediaIds, normalizeCrmPicklistLabel, normalizePhone, planContactCompanyLink, readContactCompanyLink, readContactFacet, readCrmCustomInput, visibleToTokens } from "@aglyn/aglyn/server";
71
+ */ import { composeContactName, CONTACT_BIRTHDATE_REFUSAL, CONTACT_EXTRA_PHONE_FIELDS, CONTACT_PHONE_FIELD_LABELS, consentGroupForHost, CONTACT_FIELDS_MAX_PER_ORG, containerMembershipValue, contactContainerFieldPath, contactFacetPath, CRM_COLLECTIONS, CRM_LEAD_SOURCE_PICKLIST, CRM_MEDIA_IDS_FIELD, CRM_SALUTATION_PICKLIST, crmReadTokens, effectiveCrmLeadSourcePicklist, isOrgWideMember, judgeCrmLeadSource, judgeCrmPicklistValue, normalizeAddress, normalizeContactBirthdate, normalizeCrmMediaIds, normalizeCrmPicklistLabel, normalizePhone, planContactCompanyLink, readContactCompanyLink, readContactFacet, readCrmCustomInput, visibleToTokens } from "@aglyn/aglyn/server";
72
72
  import { companyContactsCountFields, contactCompanyLinkFields, firebaseAdmin, resolveOrgMembership, restampCrmListFieldsOf } from "@aglyn/tenant-data-admin";
73
73
  import { FieldValue } from "firebase-admin/firestore";
74
74
  import { CONTACT_UPDATE_FIELDS, CRM_CONTACT_UPDATE_MAX } from "../model/contact-update.js";
@@ -77,12 +77,28 @@ import { readCrmRouteScope } from "./org-caller.js";
77
77
  import { crmSuiteRefusal } from "./suite-gate.js";
78
78
  import { authorizeCrmWriter, canReach } from "./task-routes.js";
79
79
  import { contactPrimaryGroup } from "../model/contact-holder.js";
80
+ import { cachedContactReader, CONTACT_REPORTS_TO_LOOP_REFUSAL, CONTACT_REPORTS_TO_SELF_REFUSAL, contactReportsToLoops } from "./contact-reports-to.js";
81
+ import { readCrmPicklist } from "./read-picklist.js";
80
82
  /** What the suite gate names for each field a request can carry. */ const SUITE_ACTS = {
81
83
  name: "Editing a contact's profile",
84
+ salutation: "Editing a contact's profile",
85
+ firstName: "Editing a contact's profile",
86
+ lastName: "Editing a contact's profile",
82
87
  phone: "Editing a contact's profile",
88
+ mobilePhone: "Editing a contact's profile",
89
+ homePhone: "Editing a contact's profile",
90
+ otherPhone: "Editing a contact's profile",
91
+ fax: "Editing a contact's profile",
83
92
  jobTitle: "Editing a contact's profile",
93
+ department: "Editing a contact's profile",
94
+ birthdate: "Editing a contact's profile",
95
+ assistantName: "Editing a contact's profile",
96
+ assistantPhone: "Editing a contact's profile",
97
+ reportsToContactId: "Editing a contact's profile",
84
98
  leadSource: "Editing a contact's profile",
85
99
  address: "Editing a contact's profile",
100
+ otherAddress: "Editing a contact's profile",
101
+ doNotCall: "Editing a contact's profile",
86
102
  notes: "Editing a contact's notes",
87
103
  tags: 'Tagging a contact',
88
104
  addTag: 'Tagging a contact',
@@ -94,6 +110,7 @@ import { contactPrimaryGroup } from "../model/contact-holder.js";
94
110
  custom: "Editing a contact's custom fields",
95
111
  mediaIds: 'Attaching files to a contact'
96
112
  };
113
+ /** The sentence a `name` that contradicts the holder's first and last names is refused with. */ export const CONTACT_COMPOSED_NAME_REFUSAL = "This person's name is made of their first and last name — change those instead.";
97
114
  /** A contact read at the organization level that no site has captured. */ const NO_HOLDER_REFUSAL = 'No site holds this contact yet, so it has no profile to edit.';
98
115
  /**
99
116
  * The act a request is refused for on a plan without the CRM: the first
@@ -125,22 +142,66 @@ import { contactPrimaryGroup } from "../model/contact-holder.js";
125
142
  }
126
143
  const fields = {};
127
144
  if ('name' in raw) fields.name = typed(raw['name'], 120);
128
- if ('phone' in raw) {
129
- const text = typed(raw['phone'], 40);
145
+ for (const key of [
146
+ 'phone',
147
+ ...CONTACT_EXTRA_PHONE_FIELDS
148
+ ]){
149
+ if (!(key in raw)) continue;
150
+ const text = typed(raw[key], 40);
130
151
  const phone = text ? normalizePhone(text) : '';
131
- if (phone === null) return {
152
+ if (phone === null) {
153
+ return {
154
+ ok: false,
155
+ error: key === 'phone' ? CONTACT_PHONE_REFUSAL : `${CONTACT_PHONE_FIELD_LABELS[key]}: ${CONTACT_PHONE_REFUSAL}`
156
+ };
157
+ }
158
+ fields[key] = phone;
159
+ }
160
+ if ('jobTitle' in raw) fields.jobTitle = typed(raw['jobTitle'], 120);
161
+ // Held to the label's shape here and judged against the org's list by the
162
+ // handler, like the lead source (AGL-3515).
163
+ if ('salutation' in raw) fields.salutation = normalizeCrmPicklistLabel(raw['salutation']);
164
+ if ('firstName' in raw) fields.firstName = composeContactName(raw['firstName'], '');
165
+ if ('lastName' in raw) fields.lastName = composeContactName('', raw['lastName']);
166
+ if ('department' in raw) fields.department = typed(raw['department'], 120);
167
+ if ('assistantName' in raw) fields.assistantName = typed(raw['assistantName'], 120);
168
+ if ('birthdate' in raw) {
169
+ const birthdate = normalizeContactBirthdate(raw['birthdate']);
170
+ if (birthdate === null) return {
132
171
  ok: false,
133
- error: CONTACT_PHONE_REFUSAL
172
+ error: CONTACT_BIRTHDATE_REFUSAL
134
173
  };
135
- fields.phone = phone;
174
+ fields.birthdate = birthdate;
175
+ }
176
+ if ('reportsToContactId' in raw) {
177
+ const reportsTo = raw['reportsToContactId'] === null ? '' : typed(raw['reportsToContactId'], 200);
178
+ if (reportsTo.includes('/')) {
179
+ return {
180
+ ok: false,
181
+ error: 'The contact this person reports to could not be read.'
182
+ };
183
+ }
184
+ fields.reportsToContactId = reportsTo || null;
185
+ }
186
+ if ('doNotCall' in raw) {
187
+ if (typeof raw['doNotCall'] !== 'boolean') {
188
+ return {
189
+ ok: false,
190
+ error: 'Do not call must be true or false.'
191
+ };
192
+ }
193
+ fields.doNotCall = raw['doNotCall'];
136
194
  }
137
- if ('jobTitle' in raw) fields.jobTitle = typed(raw['jobTitle'], 120);
138
195
  // Held to the label's shape here; judged against the org's list by the
139
196
  // handler, which alone can read it (AGL-3298).
140
197
  if ('leadSource' in raw) fields.leadSource = normalizeCrmPicklistLabel(raw['leadSource']);
141
- if ('address' in raw) {
142
- const address = raw['address'];
143
- fields.address = address && typeof address === 'object' && !Array.isArray(address) ? normalizeAddress(address) : null;
198
+ for (const key of [
199
+ 'address',
200
+ 'otherAddress'
201
+ ]){
202
+ if (!(key in raw)) continue;
203
+ const address = raw[key];
204
+ fields[key] = address && typeof address === 'object' && !Array.isArray(address) ? normalizeAddress(address) : null;
144
205
  }
145
206
  if ('notes' in raw) fields.notes = String((_raw_notes = raw['notes']) != null ? _raw_notes : '').slice(0, CONTACT_NOTES_MAX);
146
207
  // One write may carry one transform per field, so the tags are replaced,
@@ -222,6 +283,41 @@ import { contactPrimaryGroup } from "../model/contact-holder.js";
222
283
  fields
223
284
  };
224
285
  }
286
+ /**
287
+ * Salesforce's standard contact fields (AGL-3515) a create may carry beside
288
+ * the identity — every one `upsertHostContact` writes into the capturing
289
+ * facet. The reports-to is not one: it names another record, which only
290
+ * this route checks the holder can see.
291
+ */ export const CONTACT_CREATE_PROFILE_FIELDS = [
292
+ 'salutation',
293
+ 'firstName',
294
+ 'lastName',
295
+ 'department',
296
+ 'mobilePhone',
297
+ 'homePhone',
298
+ 'otherPhone',
299
+ 'fax',
300
+ 'birthdate',
301
+ 'assistantName',
302
+ 'assistantPhone',
303
+ 'otherAddress',
304
+ 'doNotCall'
305
+ ];
306
+ /**
307
+ * Those fields of a create's body, read by the same rules an update reads
308
+ * them with — or the sentence refusing one. A body naming none of them
309
+ * reads as no fields.
310
+ */ export function readContactCreateProfile(body) {
311
+ const given = Object.fromEntries(CONTACT_CREATE_PROFILE_FIELDS.filter((key)=>body[key] !== undefined).map((key)=>[
312
+ key,
313
+ body[key]
314
+ ]));
315
+ if (!Object.keys(given).length) return {
316
+ ok: true,
317
+ fields: {}
318
+ };
319
+ return readContactUpdateFields(given);
320
+ }
225
321
  /** The ids a body names, deduplicated — or `null` when any is unreadable or there are too many. */ function readContactIds(value) {
226
322
  if (!Array.isArray(value) || !value.length || value.length > CRM_CONTACT_UPDATE_MAX) {
227
323
  return null;
@@ -250,27 +346,66 @@ const refused = (contactId, error)=>({
250
346
  * One contact's update for one holder, or the sentence refusing the row.
251
347
  * `update` is `null` when the document already says everything asked — a tag
252
348
  * the row already carries, a link already made.
253
- */ function contactPatch(contact, groupId, fields, custom, linkedCompanyName) {
254
- var _readContactFacet_tags;
349
+ */ export function contactPatch(contact, groupId, fields, custom, linkedCompanyName) {
350
+ var _fields_firstName, _fields_lastName, _stored_tags;
255
351
  const path = (field)=>contactFacetPath(groupId, field);
256
352
  /** A text field as stored: the value, or the field's absence. */ const text = (value)=>value || FieldValue.delete();
257
353
  const update = {};
258
354
  let counts = [];
259
- if (fields.name !== undefined) update[path('name')] = text(fields.name);
355
+ const stored = readContactFacet(contact, groupId);
356
+ /*
357
+ * THE NAME FOLLOWS THE FIRST AND LAST NAMES (AGL-3515). While the holder
358
+ * keeps either, the facet's `name` is their composition, written whenever
359
+ * either moves; a `name` sent beside them must agree, because a save that
360
+ * stored one and showed the other would read as a save that kept both.
361
+ * Both cleared leaves the name as it stands, unless one was sent.
362
+ */ const namesSent = fields.firstName !== undefined || fields.lastName !== undefined;
363
+ const composed = composeContactName((_fields_firstName = fields.firstName) != null ? _fields_firstName : stored.firstName, (_fields_lastName = fields.lastName) != null ? _fields_lastName : stored.lastName);
364
+ if (composed && fields.name !== undefined && fields.name !== composed) {
365
+ return {
366
+ refused: CONTACT_COMPOSED_NAME_REFUSAL
367
+ };
368
+ }
369
+ if (fields.firstName !== undefined) update[path('firstName')] = text(fields.firstName);
370
+ if (fields.lastName !== undefined) update[path('lastName')] = text(fields.lastName);
371
+ if (namesSent && composed) update[path('name')] = composed;
372
+ else if (fields.name !== undefined && !composed) update[path('name')] = text(fields.name);
373
+ if (fields.salutation !== undefined) update[path('salutation')] = text(fields.salutation);
260
374
  if (fields.phone !== undefined) {
261
375
  update[path('phone')] = text(fields.phone);
262
376
  // The search echo — see `HostContact.phone`.
263
377
  update['phone'] = text(fields.phone);
264
378
  }
379
+ for (const key of CONTACT_EXTRA_PHONE_FIELDS){
380
+ var _fields_key;
381
+ if (fields[key] !== undefined) update[path(key)] = text((_fields_key = fields[key]) != null ? _fields_key : '');
382
+ }
265
383
  if (fields.jobTitle !== undefined) update[path('jobTitle')] = text(fields.jobTitle);
384
+ if (fields.department !== undefined) update[path('department')] = text(fields.department);
385
+ if (fields.birthdate !== undefined) update[path('birthdate')] = text(fields.birthdate);
386
+ if (fields.assistantName !== undefined) {
387
+ update[path('assistantName')] = text(fields.assistantName);
388
+ }
389
+ if (fields.reportsToContactId !== undefined) {
390
+ var _fields_reportsToContactId;
391
+ update[path('reportsToContactId')] = text((_fields_reportsToContactId = fields.reportsToContactId) != null ? _fields_reportsToContactId : '');
392
+ }
393
+ if (fields.doNotCall !== undefined) {
394
+ // Stored only as `true` — see `ContactFacet.doNotCall`.
395
+ update[path('doNotCall')] = fields.doNotCall ? true : FieldValue.delete();
396
+ }
266
397
  if (fields.leadSource !== undefined) update[path('leadSource')] = text(fields.leadSource);
267
398
  if (fields.address !== undefined) {
268
399
  var _fields_address;
269
400
  update[path('address')] = (_fields_address = fields.address) != null ? _fields_address : FieldValue.delete();
270
401
  }
402
+ if (fields.otherAddress !== undefined) {
403
+ var _fields_otherAddress;
404
+ update[path('otherAddress')] = (_fields_otherAddress = fields.otherAddress) != null ? _fields_otherAddress : FieldValue.delete();
405
+ }
271
406
  if (fields.notes !== undefined) update[path('notes')] = fields.notes;
272
407
  if (fields.tags !== undefined) update[path('tags')] = fields.tags;
273
- const held = ((_readContactFacet_tags = readContactFacet(contact, groupId).tags) != null ? _readContactFacet_tags : []).map((tag)=>String(tag).toLowerCase());
408
+ const held = ((_stored_tags = stored.tags) != null ? _stored_tags : []).map((tag)=>String(tag).toLowerCase());
274
409
  if (fields.addTag && !held.includes(fields.addTag)) {
275
410
  // The record page's cap, which a selection must not slip past.
276
411
  if (held.length >= CONTACT_TAGS_MAX) {
@@ -350,7 +485,7 @@ const refused = (contactId, error)=>({
350
485
  await restampCrmListFieldsOf(writes.filter((write)=>!failed.has(write.contactId)).map((write)=>write.ref), 'contacts');
351
486
  return failed;
352
487
  }
353
- /** Each company's contacts count moved once, by the sum of what the written links moved. */ async function settleCompanyCounts(companies, writes) {
488
+ /** Each company's contacts count moved once, by the sum of what the written links moved. */ export async function settleCompanyCounts(companies, writes) {
354
489
  const deltas = new Map();
355
490
  for (const write of writes){
356
491
  for (const count of write.counts){
@@ -438,6 +573,8 @@ export const crmContactUpdateHandler = async (req, res)=>{
438
573
  * Judged per contact below, because a contact keeps the value it
439
574
  * already holds even when the list has since deactivated it.
440
575
  */ const leadSources = fields.leadSource ? effectiveCrmLeadSourcePicklist((await orgRef.collection(CRM_COLLECTIONS.picklists).doc(CRM_LEAD_SOURCE_PICKLIST).get()).data()) : null;
576
+ // The org's salutations (AGL-3515), judged per contact like the lead source.
577
+ const salutations = fields.salutation ? await readCrmPicklist(firestore, writer.orgId, CRM_SALUTATION_PICKLIST) : null;
441
578
  if (fields.ownerUid) {
442
579
  const owner = await resolveOrgMembership(fields.ownerUid, writer.orgId).catch(()=>null);
443
580
  if (!(owner == null ? void 0 : owner.member)) {
@@ -463,9 +600,24 @@ export const crmContactUpdateHandler = async (req, res)=>{
463
600
  linkedCompanyName = typed(company.get('name'), 120);
464
601
  }
465
602
  const contacts = orgRef.collection('contacts');
603
+ const readContact = cachedContactReader(contacts);
604
+ // The manager a reports-to names has to be a contact the caller and the
605
+ // site can see, for the company link's reason (AGL-3515).
606
+ if (fields.reportsToContactId) {
607
+ const manager = await contacts.doc(fields.reportsToContactId).get();
608
+ const tokens = manager.exists ? manager.get('visibleTo') : undefined;
609
+ const visible = manager.exists && canReach(writer, tokens) && (!siteTokens || visibleToTokens(tokens, siteTokens));
610
+ if (!visible) {
611
+ res.status(404).json({
612
+ error: 'Unknown contact to report to'
613
+ });
614
+ return;
615
+ }
616
+ }
466
617
  const snapshots = await firestore.getAll(...contactIds.map((id)=>contacts.doc(id)));
467
618
  const outcomes = new Map();
468
619
  const writes = [];
620
+ const rows = [];
469
621
  snapshots.forEach((snapshot, index)=>{
470
622
  var _snapshot_data;
471
623
  const contactId = contactIds[index];
@@ -488,22 +640,51 @@ export const crmContactUpdateHandler = async (req, res)=>{
488
640
  outcomes.set(contactId, refused(contactId, NO_HOLDER_REFUSAL));
489
641
  return;
490
642
  }
643
+ rows.push({
644
+ contactId,
645
+ snapshot,
646
+ data,
647
+ groupId: group.groupId
648
+ });
649
+ });
650
+ for (const { contactId, snapshot, data, groupId } of rows){
651
+ if (fields.reportsToContactId) {
652
+ if (fields.reportsToContactId === contactId) {
653
+ outcomes.set(contactId, refused(contactId, CONTACT_REPORTS_TO_SELF_REFUSAL));
654
+ continue;
655
+ }
656
+ if (await contactReportsToLoops(readContact, contactId, fields.reportsToContactId, groupId)) {
657
+ outcomes.set(contactId, refused(contactId, CONTACT_REPORTS_TO_LOOP_REFUSAL));
658
+ continue;
659
+ }
660
+ }
491
661
  let rowFields = fields;
492
- if (leadSources && fields.leadSource) {
662
+ if (salutations && fields.salutation) {
493
663
  var _judged_value;
494
- const judged = judgeCrmLeadSource(leadSources, fields.leadSource, readContactFacet(data, group.groupId).leadSource);
664
+ const judged = judgeCrmPicklistValue(CRM_SALUTATION_PICKLIST, salutations, fields.salutation, readContactFacet(data, groupId).salutation);
665
+ if (judged.ok === false) {
666
+ outcomes.set(contactId, refused(contactId, judged.error));
667
+ continue;
668
+ }
669
+ rowFields = _extends({}, rowFields, {
670
+ salutation: (_judged_value = judged.value) != null ? _judged_value : ''
671
+ });
672
+ }
673
+ if (leadSources && fields.leadSource) {
674
+ var _judged_value1;
675
+ const judged = judgeCrmLeadSource(leadSources, fields.leadSource, readContactFacet(data, groupId).leadSource);
495
676
  if (judged.ok === false) {
496
677
  outcomes.set(contactId, refused(contactId, judged.error));
497
- return;
678
+ continue;
498
679
  }
499
- rowFields = _extends({}, fields, {
500
- leadSource: (_judged_value = judged.value) != null ? _judged_value : ''
680
+ rowFields = _extends({}, rowFields, {
681
+ leadSource: (_judged_value1 = judged.value) != null ? _judged_value1 : ''
501
682
  });
502
683
  }
503
- const patch = contactPatch(data, group.groupId, rowFields, custom, linkedCompanyName);
684
+ const patch = contactPatch(data, groupId, rowFields, custom, linkedCompanyName);
504
685
  if ('refused' in patch) {
505
686
  outcomes.set(contactId, refused(contactId, patch.refused));
506
- return;
687
+ continue;
507
688
  }
508
689
  outcomes.set(contactId, {
509
690
  contactId,
@@ -517,7 +698,7 @@ export const crmContactUpdateHandler = async (req, res)=>{
517
698
  counts: patch.counts
518
699
  });
519
700
  }
520
- });
701
+ }
521
702
  const failed = await commitContactWrites(firestore, writes);
522
703
  for (const contactId of failed){
523
704
  outcomes.set(contactId, refused(contactId, 'The contact could not be saved.'));