@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
@@ -25,7 +25,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
25
25
  * holds, what it is stored as, and the company an address suggests — each
26
26
  * rule once, as a function of the document and nothing else, so a spec can
27
27
  * pin it without mounting a card.
28
- */ import { CONTACT_COMPANY_IDS_FIELD, companyDomainForEmail, isBlankAddress, nameSearchFields, normalizeAddress, normalizeCompanyDomain, normalizeCompanyWebsite, normalizePhone } from "@aglyn/aglyn";
28
+ */ import { CONTACT_COMPANY_IDS_FIELD, CRM_COMPANY_EMPLOYEES_MAX, CRM_COMPANY_PICKLIST_FIELDS, companyDomainForEmail, isBlankAddress, judgeCrmCompanyPicklists, nameSearchFields, normalizeAddress, normalizeCompanyDomain, normalizeCompanyWebsite, normalizePhone, readCrmCompanyAccountFields } from "@aglyn/aglyn";
29
+ import { amountInputValue, DEFAULT_DEAL_CURRENCY, parseAmountInput } from "./deal-board-model.js";
29
30
  /*
30
31
  * The mirror field lives with the planner in `@aglyn/aglyn` now, because the
31
32
  * server doors that link on capture read it too; it is re-exported here so
@@ -56,12 +57,37 @@ export const EMPTY_COMPANY_DRAFT = {
56
57
  domain: '',
57
58
  website: '',
58
59
  phone: '',
60
+ type: '',
59
61
  industry: '',
62
+ rating: '',
63
+ ownership: '',
64
+ accountSource: '',
60
65
  ownerUid: '',
61
66
  address: {},
67
+ shippingAddress: {},
68
+ annualRevenue: '',
69
+ currency: DEFAULT_DEAL_CURRENCY,
70
+ numberOfEmployees: '',
71
+ fax: '',
72
+ accountNumber: '',
73
+ site: '',
74
+ tickerSymbol: '',
75
+ sicCode: '',
76
+ parentCompanyId: '',
62
77
  tags: '',
63
78
  notes: ''
64
79
  };
80
+ /** How each account field is named in a refusal the drawer shows. */ const ACCOUNT_FIELD_LABELS = {
81
+ annualRevenueCents: 'Annual revenue',
82
+ currency: 'The currency',
83
+ numberOfEmployees: 'Employees',
84
+ fax: 'The fax number',
85
+ accountNumber: 'The account number',
86
+ site: 'The account site',
87
+ tickerSymbol: 'The ticker symbol',
88
+ sicCode: 'The SIC code',
89
+ shippingAddress: 'The shipping address'
90
+ };
65
91
  /** The drawer's cap on a company's tags — a contact's, so the two agree. */ export const COMPANY_TAGS_MAX = 20;
66
92
  /**
67
93
  * A typed tag list as the document stores one: split on `,` or `|`,
@@ -74,20 +100,41 @@ export const EMPTY_COMPANY_DRAFT = {
74
100
  ].slice(0, COMPANY_TAGS_MAX);
75
101
  }
76
102
  /** A stored company, as the form should start from it. */ export function companyDraftFrom(company) {
77
- var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8;
103
+ var _ref, _ref1, _ref2;
104
+ const text = (value)=>value === null || value === undefined ? '' : String(value);
78
105
  return {
79
- name: String((_ref = company == null ? void 0 : company.name) != null ? _ref : ''),
80
- domain: String((_ref1 = company == null ? void 0 : company.domain) != null ? _ref1 : ''),
81
- website: String((_ref2 = company == null ? void 0 : company.website) != null ? _ref2 : ''),
82
- phone: String((_ref3 = company == null ? void 0 : company.phone) != null ? _ref3 : ''),
83
- industry: String((_ref4 = company == null ? void 0 : company.industry) != null ? _ref4 : ''),
84
- ownerUid: String((_ref5 = company == null ? void 0 : company.ownerUid) != null ? _ref5 : ''),
85
- address: _extends({}, (_ref6 = company == null ? void 0 : company.address) != null ? _ref6 : {}),
86
- tags: ((_ref7 = company == null ? void 0 : company.tags) != null ? _ref7 : []).join(', '),
87
- notes: String((_ref8 = company == null ? void 0 : company.notes) != null ? _ref8 : '')
106
+ name: text(company == null ? void 0 : company.name),
107
+ domain: text(company == null ? void 0 : company.domain),
108
+ website: text(company == null ? void 0 : company.website),
109
+ phone: text(company == null ? void 0 : company.phone),
110
+ type: text(company == null ? void 0 : company.type),
111
+ industry: text(company == null ? void 0 : company.industry),
112
+ rating: text(company == null ? void 0 : company.rating),
113
+ ownership: text(company == null ? void 0 : company.ownership),
114
+ accountSource: text(company == null ? void 0 : company.accountSource),
115
+ ownerUid: text(company == null ? void 0 : company.ownerUid),
116
+ address: _extends({}, (_ref = company == null ? void 0 : company.address) != null ? _ref : {}),
117
+ shippingAddress: _extends({}, (_ref1 = company == null ? void 0 : company.shippingAddress) != null ? _ref1 : {}),
118
+ annualRevenue: amountInputValue(company == null ? void 0 : company.annualRevenueCents),
119
+ currency: text(company == null ? void 0 : company.currency).toLowerCase() || DEFAULT_DEAL_CURRENCY,
120
+ numberOfEmployees: typeof (company == null ? void 0 : company.numberOfEmployees) === 'number' ? String(company.numberOfEmployees) : '',
121
+ fax: text(company == null ? void 0 : company.fax),
122
+ accountNumber: text(company == null ? void 0 : company.accountNumber),
123
+ site: text(company == null ? void 0 : company.site),
124
+ tickerSymbol: text(company == null ? void 0 : company.tickerSymbol),
125
+ sicCode: text(company == null ? void 0 : company.sicCode),
126
+ parentCompanyId: text(company == null ? void 0 : company.parentCompanyId),
127
+ tags: ((_ref2 = company == null ? void 0 : company.tags) != null ? _ref2 : []).join(', '),
128
+ notes: text(company == null ? void 0 : company.notes)
88
129
  };
89
130
  }
90
- const INDUSTRY_MAX = 80;
131
+ /** A typed head count, as the whole number stored, `null` for blank, `undefined` for unreadable. */ function parseEmployees(input) {
132
+ const cleaned = input.replace(/[\s,]/g, '');
133
+ if (!cleaned) return null;
134
+ if (!/^\d+$/.test(cleaned)) return undefined;
135
+ const value = Number(cleaned);
136
+ return value <= CRM_COMPANY_EMPLOYEES_MAX ? value : undefined;
137
+ }
91
138
  const NOTES_MAX = 4000;
92
139
  /**
93
140
  * The document a draft becomes — normalized, keyed for search, and refused
@@ -99,7 +146,10 @@ const NOTES_MAX = 4000;
99
146
  * the one required field, and it is spread through `nameSearchFields` so the
100
147
  * list's name filter — a range over `nameLower` — can find the record; a
101
148
  * company written without those keys still lists and cannot be searched.
102
- */ export function companyDraftFields(draft) {
149
+ */ export function companyDraftFields(draft, picklists = {
150
+ lists: {}
151
+ }) {
152
+ var _picklists_current;
103
153
  const name = draft.name.trim().replace(/\s+/g, ' ');
104
154
  if (!name) return {
105
155
  ok: false,
@@ -142,9 +192,26 @@ const NOTES_MAX = 4000;
142
192
  } else {
143
193
  cleared.push('phone');
144
194
  }
145
- const industry = draft.industry.trim().slice(0, INDUSTRY_MAX);
146
- if (industry) set['industry'] = industry;
147
- else cleared.push('industry');
195
+ /*
196
+ * THE PICKLISTS (AGL-3514): each a label of the org's list, or the value
197
+ * the company already holds — an industry typed before Industry was a
198
+ * picklist stays until it is changed.
199
+ */ const judged = judgeCrmCompanyPicklists(picklists.lists, Object.fromEntries(CRM_COMPANY_PICKLIST_FIELDS.map(({ field })=>[
200
+ field,
201
+ draft[field]
202
+ ])), {
203
+ current: (_picklists_current = picklists.current) != null ? _picklists_current : {}
204
+ });
205
+ const picklistError = Object.values(judged.errors)[0];
206
+ if (picklistError) return {
207
+ ok: false,
208
+ error: picklistError
209
+ };
210
+ for (const { field } of CRM_COMPANY_PICKLIST_FIELDS){
211
+ const value = judged.values[field];
212
+ if (value) set[field] = value;
213
+ else cleared.push(field);
214
+ }
148
215
  const ownerUid = draft.ownerUid.trim();
149
216
  if (ownerUid) set['ownerUid'] = ownerUid;
150
217
  else cleared.push('ownerUid');
@@ -152,6 +219,56 @@ const NOTES_MAX = 4000;
152
219
  // an edit that clears it does not need a delete: `normalizeAddress`
153
220
  // already answers `null` for a form with nothing in it.
154
221
  set['address'] = isBlankAddress(draft.address) ? null : normalizeAddress(draft.address);
222
+ /*
223
+ * THE ACCOUNT FIELDS (AGL-3514), through the reader every door uses. The
224
+ * revenue is typed in the currency's major unit and stored in its minor
225
+ * one, with the currency beside it; without a revenue there is nothing
226
+ * for a currency to describe.
227
+ */ const revenueText = draft.annualRevenue.trim();
228
+ const revenue = revenueText ? parseAmountInput(revenueText) : null;
229
+ if (revenueText && revenue === null) {
230
+ return {
231
+ ok: false,
232
+ error: 'The annual revenue is not an amount.'
233
+ };
234
+ }
235
+ const employees = parseEmployees(draft.numberOfEmployees);
236
+ if (employees === undefined) {
237
+ return {
238
+ ok: false,
239
+ error: `Employees must be a whole number from 0 to ${CRM_COMPANY_EMPLOYEES_MAX.toLocaleString()}.`
240
+ };
241
+ }
242
+ const account = readCrmCompanyAccountFields({
243
+ annualRevenueCents: revenue,
244
+ currency: revenue === null ? null : draft.currency,
245
+ numberOfEmployees: employees,
246
+ fax: draft.fax,
247
+ accountNumber: draft.accountNumber,
248
+ site: draft.site,
249
+ tickerSymbol: draft.tickerSymbol,
250
+ sicCode: draft.sicCode,
251
+ shippingAddress: isBlankAddress(draft.shippingAddress) ? null : draft.shippingAddress
252
+ });
253
+ const [failed] = Object.entries(account.errors);
254
+ if (failed) {
255
+ var _ACCOUNT_FIELD_LABELS_field;
256
+ const [field, message] = failed;
257
+ return {
258
+ ok: false,
259
+ error: `${(_ACCOUNT_FIELD_LABELS_field = ACCOUNT_FIELD_LABELS[field]) != null ? _ACCOUNT_FIELD_LABELS_field : field} ${message.charAt(0).toLowerCase()}${message.slice(1)}.`
260
+ };
261
+ }
262
+ for (const [field, value] of Object.entries(account.values)){
263
+ // The shipping address is nullable like the billing one; the rest are
264
+ // absent when blank, so an edit deletes them.
265
+ if (field === 'shippingAddress') set[field] = value != null ? value : null;
266
+ else if (value === null || value === undefined) cleared.push(field);
267
+ else set[field] = value;
268
+ }
269
+ const parentCompanyId = draft.parentCompanyId.trim();
270
+ if (parentCompanyId) set['parentCompanyId'] = parentCompanyId;
271
+ else cleared.push('parentCompanyId');
155
272
  const tags = normalizeCompanyTags(draft.tags);
156
273
  if (tags.length) set['tags'] = tags;
157
274
  else cleared.push('tags');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/companies.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Companies (AGL-2597): the rules every surface that writes a company, or\n * suggests one for a contact, has to agree on.\n *\n * A contact's link to a company is the server's to write (AGL-2804): the\n * link lives in a holder's facet, `crm/contact-update` plans it with\n * `planContactCompanyLink`, and `crm/company-delete` unlinks a company being\n * deleted. What stays here is the company's own document — the draft a form\n * holds, what it is stored as, and the company an address suggests — each\n * rule once, as a function of the document and nothing else, so a spec can\n * pin it without mounting a card.\n */\n\nimport {\n type AglynPostalAddress,\n CONTACT_COMPANY_IDS_FIELD,\n type CrmCompany,\n companyDomainForEmail,\n isBlankAddress,\n nameSearchFields,\n normalizeAddress,\n normalizeCompanyDomain,\n normalizeCompanyWebsite,\n normalizePhone,\n} from '@aglyn/aglyn'\n\n/*\n * The mirror field lives with the planner in `@aglyn/aglyn` now, because the\n * server doors that link on capture read it too; it is re-exported here so\n * the surfaces that always imported it from this module keep one name.\n */\nexport { CONTACT_COMPANY_IDS_FIELD }\n\n/*\n * The delete pass's bound lives with `crm/company-delete`'s contract, which\n * the route and the console both import; re-exported here for the surfaces\n * that say it.\n */\nexport { COMPANY_DETACH_LIMIT } from './company-delete-route'\n\n/** One company as a picker or a suggestion needs it. */\nexport interface CompanyOption {\n id: string\n name: string\n domain?: string | null\n}\n\n/**\n * The company a contact's email address implies, from a list the caller\n * already holds, or `null`.\n *\n * `companyDomainForEmail` already refuses the public mailbox providers, so\n * `jane@gmail.com` suggests nothing rather than whichever company somebody\n * once filed under \"gmail.com\". The match is on the normalized domain both\n * sides store, so `www.acme.com` on the company and `jane@ACME.com` on the\n * contact still meet.\n */\nexport function suggestCompanyForEmail(\n email: unknown,\n companies: readonly CompanyOption[],\n): CompanyOption | null {\n const domain = companyDomainForEmail(email)\n if (!domain) return null\n return companies.find((company) => company.domain === domain) ?? null\n}\n\n/** What the form holds — every field as text, the address as its parts. */\nexport interface CompanyDraft {\n name: string\n domain: string\n website: string\n phone: string\n industry: string\n ownerUid: string\n address: AglynPostalAddress\n /** Comma-separated as typed; stored as the list `companyDraftFields` reads. */\n tags: string\n notes: string\n}\n\nexport const EMPTY_COMPANY_DRAFT: CompanyDraft = {\n name: '',\n domain: '',\n website: '',\n phone: '',\n industry: '',\n ownerUid: '',\n address: {},\n tags: '',\n notes: '',\n}\n\n/** The drawer's cap on a company's tags — a contact's, so the two agree. */\nexport const COMPANY_TAGS_MAX = 20\n\n/**\n * A typed tag list as the document stores one: split on `,` or `|`,\n * lowercased and trimmed, deduplicated, capped — the same shape a contact's\n * tags take, so the bulk bar's \"Add tag\" over companies and the drawer's\n * field write one kind of value.\n */\nexport function normalizeCompanyTags(input: string): string[] {\n return [\n ...new Set(\n String(input ?? '')\n .split(/[|,]/)\n .map((tag) => tag.trim().toLowerCase().slice(0, 60))\n .filter(Boolean),\n ),\n ].slice(0, COMPANY_TAGS_MAX)\n}\n\n/** A stored company, as the form should start from it. */\nexport function companyDraftFrom(\n company: Partial<CrmCompany> | null | undefined,\n): CompanyDraft {\n return {\n name: String(company?.name ?? ''),\n domain: String(company?.domain ?? ''),\n website: String(company?.website ?? ''),\n phone: String(company?.phone ?? ''),\n industry: String(company?.industry ?? ''),\n ownerUid: String(company?.ownerUid ?? ''),\n address: { ...(company?.address ?? {}) },\n tags: (company?.tags ?? []).join(', '),\n notes: String(company?.notes ?? ''),\n }\n}\n\nexport type CompanyDraftResult =\n | {\n ok: true\n /** Fields with a value, ready to `setDoc` or `updateDoc`. */\n set: Record<string, unknown>\n /**\n * Optional fields the draft left blank. A create omits them; an edit\n * has to DELETE them, or clearing the domain would leave the old one\n * stored and still matching contacts by email.\n */\n cleared: string[]\n }\n | { ok: false; error: string }\n\nconst INDUSTRY_MAX = 80\nconst NOTES_MAX = 4000\n\n/**\n * The document a draft becomes — normalized, keyed for search, and refused\n * as a whole when one field cannot be stored honestly.\n *\n * Refused rather than silently dropped: a domain typed as `acme` is not a\n * domain, and storing nothing while the form said something is how a company\n * comes to match no contact by email with no indication why. The name is\n * the one required field, and it is spread through `nameSearchFields` so the\n * list's name filter — a range over `nameLower` — can find the record; a\n * company written without those keys still lists and cannot be searched.\n */\nexport function companyDraftFields(draft: CompanyDraft): CompanyDraftResult {\n const name = draft.name.trim().replace(/\\s+/g, ' ')\n if (!name) return { ok: false, error: 'A company needs a name.' }\n const set: Record<string, unknown> = { ...nameSearchFields(name) }\n const cleared: string[] = []\n\n const rawDomain = draft.domain.trim()\n if (rawDomain) {\n const domain = normalizeCompanyDomain(rawDomain)\n if (!domain) {\n return {\n ok: false,\n error:\n 'The domain should be a bare hostname such as acme.com — a ' +\n 'single word or an IP address is not one.',\n }\n }\n set['domain'] = domain\n } else {\n cleared.push('domain')\n }\n\n const website = normalizeCompanyWebsite(draft.website)\n if (website === null) {\n return { ok: false, error: 'The website is not a web address.' }\n }\n if (website) set['website'] = website\n else cleared.push('website')\n\n const rawPhone = draft.phone.trim()\n if (rawPhone) {\n const phone = normalizePhone(rawPhone)\n if (!phone) {\n return {\n ok: false,\n error:\n 'The phone number could not be read. Include the country code, ' +\n 'as in +1 512 555 0123.',\n }\n }\n set['phone'] = phone\n } else {\n cleared.push('phone')\n }\n\n const industry = draft.industry.trim().slice(0, INDUSTRY_MAX)\n if (industry) set['industry'] = industry\n else cleared.push('industry')\n\n const ownerUid = draft.ownerUid.trim()\n if (ownerUid) set['ownerUid'] = ownerUid\n else cleared.push('ownerUid')\n\n // Nullable rather than absent, so \"no address\" has one stored shape and\n // an edit that clears it does not need a delete: `normalizeAddress`\n // already answers `null` for a form with nothing in it.\n set['address'] = isBlankAddress(draft.address)\n ? null\n : normalizeAddress(draft.address)\n\n const tags = normalizeCompanyTags(draft.tags)\n if (tags.length) set['tags'] = tags\n else cleared.push('tags')\n\n const notes = draft.notes.trim().slice(0, NOTES_MAX)\n if (notes) set['notes'] = notes\n else cleared.push('notes')\n\n return { ok: true, set, cleared }\n}\n"],"names":["CONTACT_COMPANY_IDS_FIELD","companyDomainForEmail","isBlankAddress","nameSearchFields","normalizeAddress","normalizeCompanyDomain","normalizeCompanyWebsite","normalizePhone","COMPANY_DETACH_LIMIT","suggestCompanyForEmail","email","companies","domain","find","company","EMPTY_COMPANY_DRAFT","name","website","phone","industry","ownerUid","address","tags","notes","COMPANY_TAGS_MAX","normalizeCompanyTags","input","Set","String","split","map","tag","trim","toLowerCase","slice","filter","Boolean","companyDraftFrom","join","INDUSTRY_MAX","NOTES_MAX","companyDraftFields","draft","replace","ok","error","set","cleared","rawDomain","push","rawPhone","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/companies.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Companies (AGL-2597): the rules every surface that writes a company, or\n * suggests one for a contact, has to agree on.\n *\n * A contact's link to a company is the server's to write (AGL-2804): the\n * link lives in a holder's facet, `crm/contact-update` plans it with\n * `planContactCompanyLink`, and `crm/company-delete` unlinks a company being\n * deleted. What stays here is the company's own document — the draft a form\n * holds, what it is stored as, and the company an address suggests — each\n * rule once, as a function of the document and nothing else, so a spec can\n * pin it without mounting a card.\n */\n\nimport {\n type AglynPostalAddress,\n CONTACT_COMPANY_IDS_FIELD,\n CRM_COMPANY_EMPLOYEES_MAX,\n CRM_COMPANY_PICKLIST_FIELDS,\n type CrmCompany,\n type CrmCompanyPicklistField,\n type CrmPicklist,\n type CrmPicklistId,\n companyDomainForEmail,\n isBlankAddress,\n judgeCrmCompanyPicklists,\n nameSearchFields,\n normalizeAddress,\n normalizeCompanyDomain,\n normalizeCompanyWebsite,\n normalizePhone,\n readCrmCompanyAccountFields,\n} from '@aglyn/aglyn'\nimport { amountInputValue, DEFAULT_DEAL_CURRENCY, parseAmountInput } from './deal-board-model'\n\n/*\n * The mirror field lives with the planner in `@aglyn/aglyn` now, because the\n * server doors that link on capture read it too; it is re-exported here so\n * the surfaces that always imported it from this module keep one name.\n */\nexport { CONTACT_COMPANY_IDS_FIELD }\n\n/*\n * The delete pass's bound lives with `crm/company-delete`'s contract, which\n * the route and the console both import; re-exported here for the surfaces\n * that say it.\n */\nexport { COMPANY_DETACH_LIMIT } from './company-delete-route'\n\n/** One company as a picker or a suggestion needs it. */\nexport interface CompanyOption {\n id: string\n name: string\n domain?: string | null\n}\n\n/**\n * The company a contact's email address implies, from a list the caller\n * already holds, or `null`.\n *\n * `companyDomainForEmail` already refuses the public mailbox providers, so\n * `jane@gmail.com` suggests nothing rather than whichever company somebody\n * once filed under \"gmail.com\". The match is on the normalized domain both\n * sides store, so `www.acme.com` on the company and `jane@ACME.com` on the\n * contact still meet.\n */\nexport function suggestCompanyForEmail(\n email: unknown,\n companies: readonly CompanyOption[],\n): CompanyOption | null {\n const domain = companyDomainForEmail(email)\n if (!domain) return null\n return companies.find((company) => company.domain === domain) ?? null\n}\n\n/** What the form holds — every field as text, the addresses as their parts. */\nexport interface CompanyDraft {\n name: string\n domain: string\n website: string\n phone: string\n /** Each company picklist's label (AGL-3514), `''` for none. */\n type: string\n industry: string\n rating: string\n ownership: string\n accountSource: string\n ownerUid: string\n /** The billing address. */\n address: AglynPostalAddress\n shippingAddress: AglynPostalAddress\n /** As typed, in the currency's major unit — `parseAmountInput` reads it. */\n annualRevenue: string\n /** Lowercase ISO 4217. */\n currency: string\n numberOfEmployees: string\n fax: string\n accountNumber: string\n site: string\n tickerSymbol: string\n sicCode: string\n /** The parent company's id, `''` for none. */\n parentCompanyId: string\n /** Comma-separated as typed; stored as the list `companyDraftFields` reads. */\n tags: string\n notes: string\n}\n\nexport const EMPTY_COMPANY_DRAFT: CompanyDraft = {\n name: '',\n domain: '',\n website: '',\n phone: '',\n type: '',\n industry: '',\n rating: '',\n ownership: '',\n accountSource: '',\n ownerUid: '',\n address: {},\n shippingAddress: {},\n annualRevenue: '',\n currency: DEFAULT_DEAL_CURRENCY,\n numberOfEmployees: '',\n fax: '',\n accountNumber: '',\n site: '',\n tickerSymbol: '',\n sicCode: '',\n parentCompanyId: '',\n tags: '',\n notes: '',\n}\n\n/** How each account field is named in a refusal the drawer shows. */\nconst ACCOUNT_FIELD_LABELS: Record<string, string> = {\n annualRevenueCents: 'Annual revenue',\n currency: 'The currency',\n numberOfEmployees: 'Employees',\n fax: 'The fax number',\n accountNumber: 'The account number',\n site: 'The account site',\n tickerSymbol: 'The ticker symbol',\n sicCode: 'The SIC code',\n shippingAddress: 'The shipping address',\n}\n\n/** The drawer's cap on a company's tags — a contact's, so the two agree. */\nexport const COMPANY_TAGS_MAX = 20\n\n/**\n * A typed tag list as the document stores one: split on `,` or `|`,\n * lowercased and trimmed, deduplicated, capped — the same shape a contact's\n * tags take, so the bulk bar's \"Add tag\" over companies and the drawer's\n * field write one kind of value.\n */\nexport function normalizeCompanyTags(input: string): string[] {\n return [\n ...new Set(\n String(input ?? '')\n .split(/[|,]/)\n .map((tag) => tag.trim().toLowerCase().slice(0, 60))\n .filter(Boolean),\n ),\n ].slice(0, COMPANY_TAGS_MAX)\n}\n\n/** A stored company, as the form should start from it. */\nexport function companyDraftFrom(\n company: Partial<CrmCompany> | null | undefined,\n): CompanyDraft {\n const text = (value: unknown) => (value === null || value === undefined ? '' : String(value))\n return {\n name: text(company?.name),\n domain: text(company?.domain),\n website: text(company?.website),\n phone: text(company?.phone),\n type: text(company?.type),\n industry: text(company?.industry),\n rating: text(company?.rating),\n ownership: text(company?.ownership),\n accountSource: text(company?.accountSource),\n ownerUid: text(company?.ownerUid),\n address: { ...(company?.address ?? {}) },\n shippingAddress: { ...(company?.shippingAddress ?? {}) },\n annualRevenue: amountInputValue(company?.annualRevenueCents),\n currency: text(company?.currency).toLowerCase() || DEFAULT_DEAL_CURRENCY,\n numberOfEmployees:\n typeof company?.numberOfEmployees === 'number' ? String(company.numberOfEmployees) : '',\n fax: text(company?.fax),\n accountNumber: text(company?.accountNumber),\n site: text(company?.site),\n tickerSymbol: text(company?.tickerSymbol),\n sicCode: text(company?.sicCode),\n parentCompanyId: text(company?.parentCompanyId),\n tags: (company?.tags ?? []).join(', '),\n notes: text(company?.notes),\n }\n}\n\n/**\n * What a draft's picklist fields are judged against (AGL-3514): the org's\n * lists the form read, and the company as stored — whose own values are\n * kept even when a list no longer offers them. Absent, each field is\n * judged against its standard values alone.\n */\nexport interface CompanyDraftPicklists {\n lists: Readonly<Partial<Record<CrmPicklistId, CrmPicklist>>>\n current?: Partial<CrmCompany> | null\n}\n\n/** A typed head count, as the whole number stored, `null` for blank, `undefined` for unreadable. */\nfunction parseEmployees(input: string): number | null | undefined {\n const cleaned = input.replace(/[\\s,]/g, '')\n if (!cleaned) return null\n if (!/^\\d+$/.test(cleaned)) return undefined\n const value = Number(cleaned)\n return value <= CRM_COMPANY_EMPLOYEES_MAX ? value : undefined\n}\n\nexport type CompanyDraftResult =\n | {\n ok: true\n /** Fields with a value, ready to `setDoc` or `updateDoc`. */\n set: Record<string, unknown>\n /**\n * Optional fields the draft left blank. A create omits them; an edit\n * has to DELETE them, or clearing the domain would leave the old one\n * stored and still matching contacts by email.\n */\n cleared: string[]\n }\n | { ok: false; error: string }\n\nconst NOTES_MAX = 4000\n\n/**\n * The document a draft becomes — normalized, keyed for search, and refused\n * as a whole when one field cannot be stored honestly.\n *\n * Refused rather than silently dropped: a domain typed as `acme` is not a\n * domain, and storing nothing while the form said something is how a company\n * comes to match no contact by email with no indication why. The name is\n * the one required field, and it is spread through `nameSearchFields` so the\n * list's name filter — a range over `nameLower` — can find the record; a\n * company written without those keys still lists and cannot be searched.\n */\nexport function companyDraftFields(\n draft: CompanyDraft,\n picklists: CompanyDraftPicklists = { lists: {} },\n): CompanyDraftResult {\n const name = draft.name.trim().replace(/\\s+/g, ' ')\n if (!name) return { ok: false, error: 'A company needs a name.' }\n const set: Record<string, unknown> = { ...nameSearchFields(name) }\n const cleared: string[] = []\n\n const rawDomain = draft.domain.trim()\n if (rawDomain) {\n const domain = normalizeCompanyDomain(rawDomain)\n if (!domain) {\n return {\n ok: false,\n error:\n 'The domain should be a bare hostname such as acme.com — a ' +\n 'single word or an IP address is not one.',\n }\n }\n set['domain'] = domain\n } else {\n cleared.push('domain')\n }\n\n const website = normalizeCompanyWebsite(draft.website)\n if (website === null) {\n return { ok: false, error: 'The website is not a web address.' }\n }\n if (website) set['website'] = website\n else cleared.push('website')\n\n const rawPhone = draft.phone.trim()\n if (rawPhone) {\n const phone = normalizePhone(rawPhone)\n if (!phone) {\n return {\n ok: false,\n error:\n 'The phone number could not be read. Include the country code, ' +\n 'as in +1 512 555 0123.',\n }\n }\n set['phone'] = phone\n } else {\n cleared.push('phone')\n }\n\n /*\n * THE PICKLISTS (AGL-3514): each a label of the org's list, or the value\n * the company already holds — an industry typed before Industry was a\n * picklist stays until it is changed.\n */\n const judged = judgeCrmCompanyPicklists(\n picklists.lists,\n Object.fromEntries(\n CRM_COMPANY_PICKLIST_FIELDS.map(({ field }) => [field, draft[field]]),\n ),\n { current: picklists.current ?? {} },\n )\n const picklistError = Object.values(judged.errors)[0]\n if (picklistError) return { ok: false, error: picklistError }\n for (const { field } of CRM_COMPANY_PICKLIST_FIELDS) {\n const value = judged.values[field as CrmCompanyPicklistField]\n if (value) set[field] = value\n else cleared.push(field)\n }\n\n const ownerUid = draft.ownerUid.trim()\n if (ownerUid) set['ownerUid'] = ownerUid\n else cleared.push('ownerUid')\n\n // Nullable rather than absent, so \"no address\" has one stored shape and\n // an edit that clears it does not need a delete: `normalizeAddress`\n // already answers `null` for a form with nothing in it.\n set['address'] = isBlankAddress(draft.address)\n ? null\n : normalizeAddress(draft.address)\n\n /*\n * THE ACCOUNT FIELDS (AGL-3514), through the reader every door uses. The\n * revenue is typed in the currency's major unit and stored in its minor\n * one, with the currency beside it; without a revenue there is nothing\n * for a currency to describe.\n */\n const revenueText = draft.annualRevenue.trim()\n const revenue = revenueText ? parseAmountInput(revenueText) : null\n if (revenueText && revenue === null) {\n return { ok: false, error: 'The annual revenue is not an amount.' }\n }\n const employees = parseEmployees(draft.numberOfEmployees)\n if (employees === undefined) {\n return {\n ok: false,\n error: `Employees must be a whole number from 0 to ${CRM_COMPANY_EMPLOYEES_MAX.toLocaleString()}.`,\n }\n }\n const account = readCrmCompanyAccountFields({\n annualRevenueCents: revenue,\n currency: revenue === null ? null : draft.currency,\n numberOfEmployees: employees,\n fax: draft.fax,\n accountNumber: draft.accountNumber,\n site: draft.site,\n tickerSymbol: draft.tickerSymbol,\n sicCode: draft.sicCode,\n shippingAddress: isBlankAddress(draft.shippingAddress) ? null : draft.shippingAddress,\n })\n const [failed] = Object.entries(account.errors)\n if (failed) {\n const [field, message] = failed\n return {\n ok: false,\n error: `${ACCOUNT_FIELD_LABELS[field] ?? field} ${message.charAt(0).toLowerCase()}${message.slice(1)}.`,\n }\n }\n for (const [field, value] of Object.entries(account.values)) {\n // The shipping address is nullable like the billing one; the rest are\n // absent when blank, so an edit deletes them.\n if (field === 'shippingAddress') set[field] = value ?? null\n else if (value === null || value === undefined) cleared.push(field)\n else set[field] = value\n }\n\n const parentCompanyId = draft.parentCompanyId.trim()\n if (parentCompanyId) set['parentCompanyId'] = parentCompanyId\n else cleared.push('parentCompanyId')\n\n const tags = normalizeCompanyTags(draft.tags)\n if (tags.length) set['tags'] = tags\n else cleared.push('tags')\n\n const notes = draft.notes.trim().slice(0, NOTES_MAX)\n if (notes) set['notes'] = notes\n else cleared.push('notes')\n\n return { ok: true, set, cleared }\n}\n"],"names":["CONTACT_COMPANY_IDS_FIELD","CRM_COMPANY_EMPLOYEES_MAX","CRM_COMPANY_PICKLIST_FIELDS","companyDomainForEmail","isBlankAddress","judgeCrmCompanyPicklists","nameSearchFields","normalizeAddress","normalizeCompanyDomain","normalizeCompanyWebsite","normalizePhone","readCrmCompanyAccountFields","amountInputValue","DEFAULT_DEAL_CURRENCY","parseAmountInput","COMPANY_DETACH_LIMIT","suggestCompanyForEmail","email","companies","domain","find","company","EMPTY_COMPANY_DRAFT","name","website","phone","type","industry","rating","ownership","accountSource","ownerUid","address","shippingAddress","annualRevenue","currency","numberOfEmployees","fax","accountNumber","site","tickerSymbol","sicCode","parentCompanyId","tags","notes","ACCOUNT_FIELD_LABELS","annualRevenueCents","COMPANY_TAGS_MAX","normalizeCompanyTags","input","Set","String","split","map","tag","trim","toLowerCase","slice","filter","Boolean","companyDraftFrom","text","value","undefined","join","parseEmployees","cleaned","replace","test","Number","NOTES_MAX","companyDraftFields","draft","picklists","lists","ok","error","set","cleared","rawDomain","push","rawPhone","judged","Object","fromEntries","field","current","picklistError","values","errors","revenueText","revenue","employees","toLocaleString","account","failed","entries","message","charAt","length"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;CAWC,GAED,SAEEA,yBAAyB,EACzBC,yBAAyB,EACzBC,2BAA2B,EAK3BC,qBAAqB,EACrBC,cAAc,EACdC,wBAAwB,EACxBC,gBAAgB,EAChBC,gBAAgB,EAChBC,sBAAsB,EACtBC,uBAAuB,EACvBC,cAAc,EACdC,2BAA2B,QACtB,eAAc;AACrB,SAASC,gBAAgB,EAAEC,qBAAqB,EAAEC,gBAAgB,QAAQ,wBAAoB;AAE9F;;;;CAIC,GACD,SAASd,yBAAyB,GAAE;AAEpC;;;;CAIC,GACD,SAASe,oBAAoB,QAAQ,4BAAwB;AAS7D;;;;;;;;;CASC,GACD,OAAO,SAASC,uBACdC,KAAc,EACdC,SAAmC;QAI5BA;IAFP,MAAMC,SAAShB,sBAAsBc;IACrC,IAAI,CAACE,QAAQ,OAAO;IACpB,QAAOD,kBAAAA,UAAUE,IAAI,CAAC,CAACC,UAAYA,QAAQF,MAAM,KAAKA,mBAA/CD,kBAA0D;AACnE;AAmCA,OAAO,MAAMI,sBAAoC;IAC/CC,MAAM;IACNJ,QAAQ;IACRK,SAAS;IACTC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,QAAQ;IACRC,WAAW;IACXC,eAAe;IACfC,UAAU;IACVC,SAAS,CAAC;IACVC,iBAAiB,CAAC;IAClBC,eAAe;IACfC,UAAUtB;IACVuB,mBAAmB;IACnBC,KAAK;IACLC,eAAe;IACfC,MAAM;IACNC,cAAc;IACdC,SAAS;IACTC,iBAAiB;IACjBC,MAAM;IACNC,OAAO;AACT,EAAC;AAED,mEAAmE,GACnE,MAAMC,uBAA+C;IACnDC,oBAAoB;IACpBX,UAAU;IACVC,mBAAmB;IACnBC,KAAK;IACLC,eAAe;IACfC,MAAM;IACNC,cAAc;IACdC,SAAS;IACTR,iBAAiB;AACnB;AAEA,0EAA0E,GAC1E,OAAO,MAAMc,mBAAmB,GAAE;AAElC;;;;;CAKC,GACD,OAAO,SAASC,qBAAqBC,KAAa;IAChD,OAAO;WACF,IAAIC,IACLC,OAAOF,gBAAAA,QAAS,IACbG,KAAK,CAAC,QACNC,GAAG,CAAC,CAACC,MAAQA,IAAIC,IAAI,GAAGC,WAAW,GAAGC,KAAK,CAAC,GAAG,KAC/CC,MAAM,CAACC;KAEb,CAACF,KAAK,CAAC,GAAGV;AACb;AAEA,wDAAwD,GACxD,OAAO,SAASa,iBACdvC,OAA+C;;IAE/C,MAAMwC,OAAO,CAACC,QAAoBA,UAAU,QAAQA,UAAUC,YAAY,KAAKZ,OAAOW;IACtF,OAAO;QACLvC,MAAMsC,KAAKxC,2BAAAA,QAASE,IAAI;QACxBJ,QAAQ0C,KAAKxC,2BAAAA,QAASF,MAAM;QAC5BK,SAASqC,KAAKxC,2BAAAA,QAASG,OAAO;QAC9BC,OAAOoC,KAAKxC,2BAAAA,QAASI,KAAK;QAC1BC,MAAMmC,KAAKxC,2BAAAA,QAASK,IAAI;QACxBC,UAAUkC,KAAKxC,2BAAAA,QAASM,QAAQ;QAChCC,QAAQiC,KAAKxC,2BAAAA,QAASO,MAAM;QAC5BC,WAAWgC,KAAKxC,2BAAAA,QAASQ,SAAS;QAClCC,eAAe+B,KAAKxC,2BAAAA,QAASS,aAAa;QAC1CC,UAAU8B,KAAKxC,2BAAAA,QAASU,QAAQ;QAChCC,SAAS,qBAAMX,2BAAAA,QAASW,OAAO,mBAAI,CAAC;QACpCC,iBAAiB,sBAAMZ,2BAAAA,QAASY,eAAe,oBAAI,CAAC;QACpDC,eAAetB,iBAAiBS,2BAAAA,QAASyB,kBAAkB;QAC3DX,UAAU0B,KAAKxC,2BAAAA,QAASc,QAAQ,EAAEqB,WAAW,MAAM3C;QACnDuB,mBACE,QAAOf,2BAAAA,QAASe,iBAAiB,MAAK,WAAWe,OAAO9B,QAAQe,iBAAiB,IAAI;QACvFC,KAAKwB,KAAKxC,2BAAAA,QAASgB,GAAG;QACtBC,eAAeuB,KAAKxC,2BAAAA,QAASiB,aAAa;QAC1CC,MAAMsB,KAAKxC,2BAAAA,QAASkB,IAAI;QACxBC,cAAcqB,KAAKxC,2BAAAA,QAASmB,YAAY;QACxCC,SAASoB,KAAKxC,2BAAAA,QAASoB,OAAO;QAC9BC,iBAAiBmB,KAAKxC,2BAAAA,QAASqB,eAAe;QAC9CC,MAAM,UAACtB,2BAAAA,QAASsB,IAAI,oBAAI,EAAE,EAAEqB,IAAI,CAAC;QACjCpB,OAAOiB,KAAKxC,2BAAAA,QAASuB,KAAK;IAC5B;AACF;AAaA,kGAAkG,GAClG,SAASqB,eAAehB,KAAa;IACnC,MAAMiB,UAAUjB,MAAMkB,OAAO,CAAC,UAAU;IACxC,IAAI,CAACD,SAAS,OAAO;IACrB,IAAI,CAAC,QAAQE,IAAI,CAACF,UAAU,OAAOH;IACnC,MAAMD,QAAQO,OAAOH;IACrB,OAAOJ,SAAS7D,4BAA4B6D,QAAQC;AACtD;AAgBA,MAAMO,YAAY;AAElB;;;;;;;;;;CAUC,GACD,OAAO,SAASC,mBACdC,KAAmB,EACnBC,YAAmC;IAAEC,OAAO,CAAC;AAAE,CAAC;QAwDnCD;IAtDb,MAAMlD,OAAOiD,MAAMjD,IAAI,CAACgC,IAAI,GAAGY,OAAO,CAAC,QAAQ;IAC/C,IAAI,CAAC5C,MAAM,OAAO;QAAEoD,IAAI;QAAOC,OAAO;IAA0B;IAChE,MAAMC,MAA+B,aAAKvE,iBAAiBiB;IAC3D,MAAMuD,UAAoB,EAAE;IAE5B,MAAMC,YAAYP,MAAMrD,MAAM,CAACoC,IAAI;IACnC,IAAIwB,WAAW;QACb,MAAM5D,SAASX,uBAAuBuE;QACtC,IAAI,CAAC5D,QAAQ;YACX,OAAO;gBACLwD,IAAI;gBACJC,OACE,+DACA;YACJ;QACF;QACAC,GAAG,CAAC,SAAS,GAAG1D;IAClB,OAAO;QACL2D,QAAQE,IAAI,CAAC;IACf;IAEA,MAAMxD,UAAUf,wBAAwB+D,MAAMhD,OAAO;IACrD,IAAIA,YAAY,MAAM;QACpB,OAAO;YAAEmD,IAAI;YAAOC,OAAO;QAAoC;IACjE;IACA,IAAIpD,SAASqD,GAAG,CAAC,UAAU,GAAGrD;SACzBsD,QAAQE,IAAI,CAAC;IAElB,MAAMC,WAAWT,MAAM/C,KAAK,CAAC8B,IAAI;IACjC,IAAI0B,UAAU;QACZ,MAAMxD,QAAQf,eAAeuE;QAC7B,IAAI,CAACxD,OAAO;YACV,OAAO;gBACLkD,IAAI;gBACJC,OACE,mEACA;YACJ;QACF;QACAC,GAAG,CAAC,QAAQ,GAAGpD;IACjB,OAAO;QACLqD,QAAQE,IAAI,CAAC;IACf;IAEA;;;;GAIC,GACD,MAAME,SAAS7E,yBACboE,UAAUC,KAAK,EACfS,OAAOC,WAAW,CAChBlF,4BAA4BmD,GAAG,CAAC,CAAC,EAAEgC,KAAK,EAAE,GAAK;YAACA;YAAOb,KAAK,CAACa,MAAM;SAAC,IAEtE;QAAEC,OAAO,GAAEb,qBAAAA,UAAUa,OAAO,YAAjBb,qBAAqB,CAAC;IAAE;IAErC,MAAMc,gBAAgBJ,OAAOK,MAAM,CAACN,OAAOO,MAAM,CAAC,CAAC,EAAE;IACrD,IAAIF,eAAe,OAAO;QAAEZ,IAAI;QAAOC,OAAOW;IAAc;IAC5D,KAAK,MAAM,EAAEF,KAAK,EAAE,IAAInF,4BAA6B;QACnD,MAAM4D,QAAQoB,OAAOM,MAAM,CAACH,MAAiC;QAC7D,IAAIvB,OAAOe,GAAG,CAACQ,MAAM,GAAGvB;aACnBgB,QAAQE,IAAI,CAACK;IACpB;IAEA,MAAMtD,WAAWyC,MAAMzC,QAAQ,CAACwB,IAAI;IACpC,IAAIxB,UAAU8C,GAAG,CAAC,WAAW,GAAG9C;SAC3B+C,QAAQE,IAAI,CAAC;IAElB,wEAAwE;IACxE,oEAAoE;IACpE,wDAAwD;IACxDH,GAAG,CAAC,UAAU,GAAGzE,eAAeoE,MAAMxC,OAAO,IACzC,OACAzB,iBAAiBiE,MAAMxC,OAAO;IAElC;;;;;GAKC,GACD,MAAM0D,cAAclB,MAAMtC,aAAa,CAACqB,IAAI;IAC5C,MAAMoC,UAAUD,cAAc5E,iBAAiB4E,eAAe;IAC9D,IAAIA,eAAeC,YAAY,MAAM;QACnC,OAAO;YAAEhB,IAAI;YAAOC,OAAO;QAAuC;IACpE;IACA,MAAMgB,YAAY3B,eAAeO,MAAMpC,iBAAiB;IACxD,IAAIwD,cAAc7B,WAAW;QAC3B,OAAO;YACLY,IAAI;YACJC,OAAO,CAAC,2CAA2C,EAAE3E,0BAA0B4F,cAAc,GAAG,CAAC,CAAC;QACpG;IACF;IACA,MAAMC,UAAUnF,4BAA4B;QAC1CmC,oBAAoB6C;QACpBxD,UAAUwD,YAAY,OAAO,OAAOnB,MAAMrC,QAAQ;QAClDC,mBAAmBwD;QACnBvD,KAAKmC,MAAMnC,GAAG;QACdC,eAAekC,MAAMlC,aAAa;QAClCC,MAAMiC,MAAMjC,IAAI;QAChBC,cAAcgC,MAAMhC,YAAY;QAChCC,SAAS+B,MAAM/B,OAAO;QACtBR,iBAAiB7B,eAAeoE,MAAMvC,eAAe,IAAI,OAAOuC,MAAMvC,eAAe;IACvF;IACA,MAAM,CAAC8D,OAAO,GAAGZ,OAAOa,OAAO,CAACF,QAAQL,MAAM;IAC9C,IAAIM,QAAQ;YAIElD;QAHZ,MAAM,CAACwC,OAAOY,QAAQ,GAAGF;QACzB,OAAO;YACLpB,IAAI;YACJC,OAAO,IAAG/B,8BAAAA,oBAAoB,CAACwC,MAAM,YAA3BxC,8BAA+BwC,MAAM,CAAC,EAAEY,QAAQC,MAAM,CAAC,GAAG1C,WAAW,KAAKyC,QAAQxC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzG;IACF;IACA,KAAK,MAAM,CAAC4B,OAAOvB,MAAM,IAAIqB,OAAOa,OAAO,CAACF,QAAQN,MAAM,EAAG;QAC3D,sEAAsE;QACtE,8CAA8C;QAC9C,IAAIH,UAAU,mBAAmBR,GAAG,CAACQ,MAAM,GAAGvB,gBAAAA,QAAS;aAClD,IAAIA,UAAU,QAAQA,UAAUC,WAAWe,QAAQE,IAAI,CAACK;aACxDR,GAAG,CAACQ,MAAM,GAAGvB;IACpB;IAEA,MAAMpB,kBAAkB8B,MAAM9B,eAAe,CAACa,IAAI;IAClD,IAAIb,iBAAiBmC,GAAG,CAAC,kBAAkB,GAAGnC;SACzCoC,QAAQE,IAAI,CAAC;IAElB,MAAMrC,OAAOK,qBAAqBwB,MAAM7B,IAAI;IAC5C,IAAIA,KAAKwD,MAAM,EAAEtB,GAAG,CAAC,OAAO,GAAGlC;SAC1BmC,QAAQE,IAAI,CAAC;IAElB,MAAMpC,QAAQ4B,MAAM5B,KAAK,CAACW,IAAI,GAAGE,KAAK,CAAC,GAAGa;IAC1C,IAAI1B,OAAOiC,GAAG,CAAC,QAAQ,GAAGjC;SACrBkC,QAAQE,IAAI,CAAC;IAElB,OAAO;QAAEL,IAAI;QAAME;QAAKC;IAAQ;AAClC"}
@@ -40,7 +40,16 @@
40
40
  * that is otherwise lossless.
41
41
  * - A recorded REFUSAL of marketing mail on either record stands on the
42
42
  * survivor. The merge asserts the two are one person, and that person
43
- * said no.
43
+ * said no. So does a do-not-call (AGL-3515), for the same reason.
44
+ * - The name is ONE value made of three fields (AGL-3515): a holder's
45
+ * `name`, `firstName` and `lastName` come across together, from the
46
+ * survivor when it names the person at all and from the merged record
47
+ * only when it does not — so a survivor called "Bob" never gains a first
48
+ * name of "Robert" that its name does not show.
49
+ * - A reports-to that would point at the survivor itself — the survivor's
50
+ * pointing at the merged record, or the merged record's at the survivor —
51
+ * is cleared (`null`) rather than kept as a person reporting to
52
+ * themselves.
44
53
  *
45
54
  * Applied per holder: each group's facet on the merged record is folded
46
55
  * into the same group's facet on the survivor, and a group only the merged
@@ -58,6 +67,11 @@
58
67
  * the rule above needs.
59
68
  */
60
69
  import { type ContactInteraction } from '@aglyn/aglyn/app-utils/contacts';
70
+ /** The two records' ids, when the caller knows them — what a self-pointer is judged by. */
71
+ export interface ContactMergeIds {
72
+ survivorId: string;
73
+ mergedId: string;
74
+ }
61
75
  export interface ContactMergePlan {
62
76
  /**
63
77
  * What the survivor becomes, as a merge-`set` payload: every derived
@@ -97,14 +111,14 @@ export declare function mergeInteractions(survivor: readonly ContactInteraction[
97
111
  * absent, when only the merged record was held by this group — and `merged`
98
112
  * the same holder's facet on the record being folded in.
99
113
  */
100
- export declare function mergeContactFacet(survivor: Doc | undefined, merged: Doc | undefined): Doc;
114
+ export declare function mergeContactFacet(survivor: Doc | undefined, merged: Doc | undefined, ids?: ContactMergeIds): Doc;
101
115
  /**
102
116
  * The plan for folding `merged` into `survivor`.
103
117
  *
104
118
  * Both are the stored documents as read. The survivor's `email` is the
105
119
  * identity that stands; the merged record's becomes an alternate.
106
120
  */
107
- export declare function planContactMerge(survivor: Doc, merged: Doc): ContactMergePlan;
121
+ export declare function planContactMerge(survivor: Doc, merged: Doc, ids?: ContactMergeIds): ContactMergePlan;
108
122
  /** Where a previewed value comes from. */
109
123
  export type ContactMergeSource = 'survivor' | 'merged' | 'both' | 'none';
110
124
  /** One line of the side-by-side the dialog shows. */
@@ -122,7 +136,17 @@ export interface ContactMergePreviewRow {
122
136
  export interface ContactMergePreviewOptions {
123
137
  /** The owner's display name for a uid; the uid itself when absent. */
124
138
  memberName?: (uid: string) => string;
139
+ /**
140
+ * The two records' ids, so the preview applies the rule the write does
141
+ * to a reports-to (AGL-3537): a pointer at either record would be the
142
+ * person reporting to themselves, and becomes nobody.
143
+ */
144
+ ids?: ContactMergeIds;
145
+ /** A contact's name for an id, for Reports to; the id itself when absent. */
146
+ contactName?: (contactId: string) => string;
125
147
  }
148
+ /** What Reports to reads when the merge leaves the person reporting to nobody because the pointer was at themselves. */
149
+ export declare const CONTACT_MERGE_REPORTS_TO_SELF = "Nobody \u2014 it named one of these two records";
126
150
  /**
127
151
  * The merge as the viewing group would see it, field by field.
128
152
  *
@@ -40,7 +40,16 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
40
40
  * that is otherwise lossless.
41
41
  * - A recorded REFUSAL of marketing mail on either record stands on the
42
42
  * survivor. The merge asserts the two are one person, and that person
43
- * said no.
43
+ * said no. So does a do-not-call (AGL-3515), for the same reason.
44
+ * - The name is ONE value made of three fields (AGL-3515): a holder's
45
+ * `name`, `firstName` and `lastName` come across together, from the
46
+ * survivor when it names the person at all and from the merged record
47
+ * only when it does not — so a survivor called "Bob" never gains a first
48
+ * name of "Robert" that its name does not show.
49
+ * - A reports-to that would point at the survivor itself — the survivor's
50
+ * pointing at the merged record, or the merged record's at the survivor —
51
+ * is cleared (`null`) rather than kept as a person reporting to
52
+ * themselves.
44
53
  *
45
54
  * Applied per holder: each group's facet on the merged record is folded
46
55
  * into the same group's facet on the survivor, and a group only the merged
@@ -76,6 +85,11 @@ import { nameSearchFields } from "@aglyn/aglyn/app-utils/name-search";
76
85
  /** Facet instants where the earlier one is the truth. */ const EARLIEST_FIELDS = [
77
86
  'firstPurchaseAtMs'
78
87
  ];
88
+ /** The facet fields that together name the person, merged as one (AGL-3515). */ const NAME_FIELDS = [
89
+ 'name',
90
+ 'firstName',
91
+ 'lastName'
92
+ ];
79
93
  /** Whether a value counts as "nothing here" for the fill rule. */ function isEmpty(value) {
80
94
  if (value === undefined || value === null) return true;
81
95
  if (typeof value === 'string') return value.trim() === '';
@@ -157,7 +171,21 @@ function number(value) {
157
171
  * `survivor` is that holder's facet on the surviving record — possibly
158
172
  * absent, when only the merged record was held by this group — and `merged`
159
173
  * the same holder's facet on the record being folded in.
160
- */ export function mergeContactFacet(survivor, merged) {
174
+ */ export function mergeContactFacet(survivor, merged, ids) {
175
+ const out = foldContactFacet(survivor, merged);
176
+ /*
177
+ * Nobody reports to themselves — see the rule above. Written as `null`
178
+ * rather than left out, because the plan is a merge-`set`, which keeps a
179
+ * stored key it is not given.
180
+ */ if (ids && [
181
+ ids.survivorId,
182
+ ids.mergedId
183
+ ].includes(text(out['reportsToContactId']))) {
184
+ out['reportsToContactId'] = null;
185
+ }
186
+ return out;
187
+ }
188
+ function foldContactFacet(survivor, merged) {
161
189
  var _b_sources, _a_sources;
162
190
  if (!merged) return _extends({}, survivor != null ? survivor : {});
163
191
  if (!survivor) return _extends({}, merged);
@@ -166,6 +194,14 @@ function number(value) {
166
194
  for (const [key, value] of Object.entries(survivor)){
167
195
  out[key] = fill(value, merged[key]);
168
196
  }
197
+ // The name's three fields, from one record — see the rule above.
198
+ const named = NAME_FIELDS.some((key)=>!isEmpty(survivor[key]));
199
+ for (const key of NAME_FIELDS){
200
+ const value = named ? survivor[key] : merged[key];
201
+ if (isEmpty(value)) delete out[key];
202
+ else out[key] = value;
203
+ }
204
+ if (survivor['doNotCall'] === true || merged['doNotCall'] === true) out['doNotCall'] = true;
169
205
  const a = survivor;
170
206
  const b = merged;
171
207
  out['sources'] = _extends({}, (_b_sources = b.sources) != null ? _b_sources : {}, (_a_sources = a.sources) != null ? _a_sources : {});
@@ -234,7 +270,7 @@ function mapOf(value) {
234
270
  *
235
271
  * Both are the stored documents as read. The survivor's `email` is the
236
272
  * identity that stands; the merged record's becomes an alternate.
237
- */ export function planContactMerge(survivor, merged) {
273
+ */ export function planContactMerge(survivor, merged, ids) {
238
274
  var _normalizeContactEmail;
239
275
  const survivorEmail = (_normalizeContactEmail = normalizeContactEmail(survivor['email'])) != null ? _normalizeContactEmail : '';
240
276
  const mergedEmails = contactEmails(merged);
@@ -313,7 +349,14 @@ function mapOf(value) {
313
349
  const mergedFacets = facetsOf(merged);
314
350
  const facets = {};
315
351
  for (const [groupId, facet] of Object.entries(mergedFacets)){
316
- facets[groupId] = mergeContactFacet(survivorFacets[groupId], facet);
352
+ facets[groupId] = mergeContactFacet(survivorFacets[groupId], facet, ids);
353
+ }
354
+ // A survivor facet the merged record did not hold may still point at it.
355
+ if (ids) {
356
+ for (const [groupId, facet] of Object.entries(survivorFacets)){
357
+ if (groupId in facets || facet['reportsToContactId'] !== ids.mergedId) continue;
358
+ facets[groupId] = mergeContactFacet(facet, undefined, ids);
359
+ }
317
360
  }
318
361
  if (Object.keys(facets).length) out[CONTACT_FACETS_FIELD] = facets;
319
362
  return {
@@ -327,6 +370,7 @@ function mapOf(value) {
327
370
  visibleTo
328
371
  };
329
372
  }
373
+ /** What Reports to reads when the merge leaves the person reporting to nobody because the pointer was at themselves. */ export const CONTACT_MERGE_REPORTS_TO_SELF = 'Nobody — it named one of these two records';
330
374
  function addressLine(value) {
331
375
  const address = mapOf(value);
332
376
  return [
@@ -355,8 +399,8 @@ function sourceOf(survivor, merged, result) {
355
399
  * result the plan would write, so the dialog is a rendering and not a second
356
400
  * opinion.
357
401
  */ export function contactMergePreview(survivor, merged, groupId, options = {}) {
358
- var _options_memberName;
359
- const plan = planContactMerge(survivor, merged);
402
+ var _options_memberName, _options_contactName;
403
+ const plan = planContactMerge(survivor, merged, options.ids);
360
404
  const after = _extends({}, survivor, plan.survivor, {
361
405
  [CONTACT_FACETS_FIELD]: _extends({}, facetsOf(survivor), mapOf(plan.survivor[CONTACT_FACETS_FIELD]))
362
406
  });
@@ -366,7 +410,23 @@ function sourceOf(survivor, merged, result) {
366
410
  const memberName = (_options_memberName = options.memberName) != null ? _options_memberName : (uid)=>uid;
367
411
  const stage = (value)=>typeof value === 'string' && value in CONTACT_LIFECYCLE_STAGE_LABELS ? CONTACT_LIFECYCLE_STAGE_LABELS[value] : '';
368
412
  const owner = (value)=>text(value) ? memberName(text(value)) : '';
413
+ const contactName = (_options_contactName = options.contactName) != null ? _options_contactName : (contactId)=>contactId;
414
+ const person = (value)=>text(value) ? contactName(text(value)) : '';
415
+ /*
416
+ * Reports to (AGL-3537), as the write leaves it: the kept record's own
417
+ * manager stands, an empty one fills from the other record's, and a
418
+ * pointer at either of the two records — which after the merge are one
419
+ * person — becomes nobody, and says why.
420
+ */ const pair = options.ids ? [
421
+ options.ids.survivorId,
422
+ options.ids.mergedId
423
+ ] : [];
424
+ const reportsToResult = person(c.reportsToContactId) || ([
425
+ a.reportsToContactId,
426
+ b.reportsToContactId
427
+ ].some((id)=>pair.includes(text(id))) ? CONTACT_MERGE_REPORTS_TO_SELF : '');
369
428
  const list = (value)=>strings(value).join(', ');
429
+ const flag = (value)=>value === true ? 'Yes' : '';
370
430
  const money = (facet)=>{
371
431
  var _facet_ltvCents;
372
432
  return facet.ordersCount ? `${facet.ordersCount} · $${(((_facet_ltvCents = facet.ltvCents) != null ? _facet_ltvCents : 0) / 100).toFixed(2)}` : '';
@@ -382,14 +442,28 @@ function sourceOf(survivor, merged, result) {
382
442
  const rows = [
383
443
  row('name', 'Name', contactDisplayName(survivor, groupId), contactDisplayName(merged, groupId), contactDisplayName(after, groupId)),
384
444
  row('email', 'Email', contactEmails(survivor).join(', '), contactEmails(merged).join(', '), plan.emails.join(', ')),
445
+ row('salutation', 'Salutation', text(a.salutation), text(b.salutation), text(c.salutation)),
385
446
  row('phone', 'Phone', text(a.phone), text(b.phone), text(c.phone)),
447
+ row('mobilePhone', 'Mobile phone', text(a.mobilePhone), text(b.mobilePhone), text(c.mobilePhone)),
448
+ row('homePhone', 'Home phone', text(a.homePhone), text(b.homePhone), text(c.homePhone)),
449
+ row('otherPhone', 'Other phone', text(a.otherPhone), text(b.otherPhone), text(c.otherPhone)),
450
+ row('fax', 'Fax', text(a.fax), text(b.fax), text(c.fax)),
451
+ row('doNotCall', 'Do not call', flag(a.doNotCall), flag(b.doNotCall), flag(c.doNotCall)),
386
452
  row('jobTitle', 'Job title', text(a.jobTitle), text(b.jobTitle), text(c.jobTitle)),
453
+ row('department', 'Department', text(a.department), text(b.department), text(c.department)),
454
+ _extends({}, row('reportsTo', 'Reports to', person(a.reportsToContactId), person(b.reportsToContactId), reportsToResult), reportsToResult === CONTACT_MERGE_REPORTS_TO_SELF ? {
455
+ from: 'none'
456
+ } : {}),
387
457
  row('company', 'Company', text(a.companyName), text(b.companyName), text(c.companyName)),
388
458
  row('lifecycleStage', 'Stage', stage(a.lifecycleStage), stage(b.lifecycleStage), stage(c.lifecycleStage)),
389
459
  row('owner', 'Owner', owner(a.ownerUid), owner(b.ownerUid), owner(c.ownerUid)),
390
460
  row('tags', 'Tags', list(a.tags), list(b.tags), list(c.tags)),
391
461
  row('notes', 'Notes', text(a.notes), text(b.notes), text(c.notes)),
392
- row('address', 'Address', addressLine(a.address), addressLine(b.address), addressLine(c.address)),
462
+ row('address', 'Mailing address', addressLine(a.address), addressLine(b.address), addressLine(c.address)),
463
+ row('otherAddress', 'Other address', addressLine(a.otherAddress), addressLine(b.otherAddress), addressLine(c.otherAddress)),
464
+ row('birthdate', 'Birthdate', text(a.birthdate), text(b.birthdate), text(c.birthdate)),
465
+ row('assistant', 'Assistant', text(a.assistantName), text(b.assistantName), text(c.assistantName)),
466
+ row('assistantPhone', 'Assistant phone', text(a.assistantPhone), text(b.assistantPhone), text(c.assistantPhone)),
393
467
  row('orders', 'Orders', money(a), money(b), money(c)),
394
468
  row('timeline', 'Timeline entries', a.interactions.length ? String(a.interactions.length) : '', b.interactions.length ? String(b.interactions.length) : '', c.interactions.length ? String(c.interactions.length) : '')
395
469
  ];