@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
@@ -1,367 +0,0 @@
1
- import { _ as _extends } from "@swc/helpers/_/_extends";
2
- /**
3
- * @license
4
- * Copyright 2026 Aglyn LLC
5
- *
6
- * Licensed under the Apache License, Version 2.0 (the "License");
7
- * you may not use this file except in compliance with the License.
8
- * You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing, software
13
- * distributed under the License is distributed on an "AS IS" BASIS,
14
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- * See the License for the specific language governing permissions and
16
- * limitations under the License.
17
- */ /**
18
- * `POST /api/crm/leads-import` — one chunk of a leads file, written
19
- * (AGL-2701).
20
- *
21
- * The browser has already read the file and applied the operator's column
22
- * mapping; what arrives here is up to {@link LEAD_IMPORT_CHUNK_SIZE} raw
23
- * rows in the vocabulary `crm-lead-import.ts` defines. This route judges
24
- * each one through the same normalizer, resolves the owner against the
25
- * org's roster, and writes it in two halves.
26
- *
27
- * ## The capture half goes through the door every capture goes through
28
- *
29
- * `addHostLead` — the ONE writer of `hosts/{hostId}/leads` for the sign-up
30
- * handler and both booking paths — is the writer here too. That is what
31
- * makes an imported row key on `personKey` the way a second form
32
- * submission does, land under the site by path with `capturedByHostIds`
33
- * naming it, count against `LEADS_MAX_PER_HOST` inside the transaction
34
- * that writes, and record NO marketing basis. A bulk path with its own
35
- * `add()` would grow an unkeyed, unbounded copy of the site's leads under
36
- * a second set of rules.
37
- *
38
- * ## A lead is host-scoped by path, so nothing here stamps `visibleTo`
39
- *
40
- * The five org collections carry `visibleTo` because they sit under
41
- * `orgs/{orgId}` and the rules decide per document which sites may read
42
- * them. A lead does not: `hosts/{hostId}/leads` is private to the site by
43
- * path, the rules admit the site's own members, and the list therefore
44
- * reads it with no scope clause. An import that stamped scope tokens onto
45
- * a lead would be writing a field nothing reads, and would suggest a
46
- * sharing decision the collection does not have. The site is the whole
47
- * scope, and the drawer's picker is where it is chosen.
48
- *
49
- * ## No file may hand a lead a consent it did not give
50
- *
51
- * A capture writes a marketing basis only when the visitor ticked a box in
52
- * front of them. A CSV row has no such event behind it, so the lead
53
- * vocabulary has no consent column and this route passes no
54
- * `marketingConsent`: an imported lead is mailable only if some earlier
55
- * capture on this site already recorded a basis, which `addHostLead`
56
- * carries forward untouched. The contacts import has a consent column
57
- * because its own export writes one and the merchant is asserting a basis
58
- * they hold per person; the leads export writes none, and a column the
59
- * file cannot fill is a column that cannot be misread.
60
- *
61
- * ## The working half is written the way the list writes it
62
- *
63
- * A status, an owner, a reason and notes are the CRM's annotations, not
64
- * the capture's, and the leads list already writes them client-direct onto
65
- * the same document. A row that carries any of them gets one merge write
66
- * after the door's, stamped `updatedAt` exactly as the list stamps it. A
67
- * row that carries none costs nothing extra.
68
- *
69
- * ## Created or merged is read once for the chunk
70
- *
71
- * The door reports only whether the lead was STORED, and the result panel
72
- * has to say how many people were added and how many updated. One
73
- * `getAll` over the chunk's document ids answers that for every row in a
74
- * single round trip, before any of them is written — which is also the
75
- * only moment the answer is still true.
76
- */ import { checkVisitorRecordCeiling, LEADS_MAX_PER_HOST, personKey } from "@aglyn/aglyn/server";
77
- import { LEAD_IMPORT_CHUNK_SIZE, LEAD_IMPORT_MAX_BODY_BYTES, normalizeLeadImportRow } from "../model/crm-lead-import.js";
78
- import { addHostLead, firebaseAdmin, orgLeadsForHost, restampCrmListFieldsAt } from "@aglyn/tenant-data-admin";
79
- import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
80
- import { listOrgContainers } from "@aglyn/tenant-data-admin/server/org-containers";
81
- // The leaf, for the reason the webhook imports the delivery log from its
82
- // leaf: a spec that stands a partial barrel in must still reach it.
83
- import { countUndeliverableEmails, findUndeliverableEmails, IMPORT_MAIL_CHECK_GRACE_MS, settleWithin } from "@aglyn/tenant-data-admin/server/capture-email-check";
84
- import { FieldValue } from "firebase-admin/firestore";
85
- import { ownerDirectory, readImportRows, resolveImportContext } from "./import-context.js";
86
- import { readLeadSourcePicklist, resolveLeadSourceWrite } from "./lead-source-picklist.js";
87
- /**
88
- * The surface an imported lead names, beside `signup`, `booking` and
89
- * `form:{formId}`.
90
- *
91
- * Typed as the capture-source union rather than a bare string, which is
92
- * what the contacts import gets for free from the door it calls: a lead's
93
- * `source` is plain text on the way in, so this is the only place the word
94
- * can be held to the same vocabulary the source filters and the labels
95
- * read. One person brought in by one file reads the same on both records.
96
- */ const LEAD_IMPORT_SOURCE = 'import';
97
- /**
98
- * How many of the org's campaigns one chunk reads to resolve the names a
99
- * file carries (AGL-3254). Well past what an org keeps — the picker offers
100
- * fifty — and read once per chunk rather than once per row.
101
- */ const CAMPAIGN_DIRECTORY_CEILING = 200;
102
- /**
103
- * The org's live campaigns by NAME, lower-cased, for the `campaigns`
104
- * column. A lead is an org record, so every live container of the org's
105
- * `campaign` kind may be named, whichever sites it is
106
- * placed on; where two share a name, the one placed on the importing site
107
- * wins, because that is the one its picker shows. A name the org does not
108
- * have is not guessed at: the row is refused whole and named, because a
109
- * lead filed under half its campaigns is a lead nobody asked for. Read
110
- * only when some row names one.
111
- */ async function campaignDirectory(firestore, orgId, hostId) {
112
- const directory = new Map();
113
- if (!orgId) return directory;
114
- const containers = await listOrgContainers(firestore, 'campaign', orgId, CAMPAIGN_DIRECTORY_CEILING);
115
- const live = containers.filter((container)=>container.live);
116
- const placed = (container)=>visibleToHost(container.visibleTo, hostId);
117
- for (const container of [
118
- ...live.filter(placed),
119
- ...live.filter((entry)=>!placed(entry))
120
- ]){
121
- const name = container.name.toLowerCase();
122
- if (name && !directory.has(name)) directory.set(name, container.id);
123
- }
124
- return directory;
125
- }
126
- /**
127
- * The team's annotations on one row and the lead's own profile
128
- * (AGL-3231), or nothing when the file named none of either. A profile
129
- * value lands as the record's card would write it; the file never clears
130
- * one, because a blank cell is a cell nobody filled.
131
- */ function workingState(row, ownerUid, campaignIds) {
132
- const fields = _extends({}, row.status ? {
133
- status: row.status
134
- } : {}, ownerUid ? {
135
- ownerUid
136
- } : {}, row.unqualifiedReason ? {
137
- unqualifiedReason: row.unqualifiedReason
138
- } : {}, row.notes ? {
139
- notes: row.notes
140
- } : {}, row.profile, campaignIds.length ? {
141
- campaignIds: FieldValue.arrayUnion(...campaignIds)
142
- } : {});
143
- return Object.keys(fields).length ? fields : null;
144
- }
145
- /**
146
- * `POST crm/leads-import` — `{ hostId, rows }` → a {@link LeadImportChunkResult}.
147
- *
148
- * Rows are written one after another so the ceiling's local count stays
149
- * honest across the request.
150
- */ export const crmLeadsImportHandler = async (req, res)=>{
151
- if (req.method !== 'POST') {
152
- res.setHeader('Allow', 'POST');
153
- return res.status(405).json({
154
- error: 'Method not allowed'
155
- });
156
- }
157
- const read = readImportRows(req, {
158
- maxBodyBytes: LEAD_IMPORT_MAX_BODY_BYTES,
159
- chunkSize: LEAD_IMPORT_CHUNK_SIZE
160
- });
161
- if ('error' in read) return res.status(read.status).json({
162
- error: read.error
163
- });
164
- try {
165
- const context = await resolveImportContext(req);
166
- if (context.ok === false) return res.status(context.status).json(context.body);
167
- const skipped = [];
168
- const dropped = {};
169
- const normalized = [];
170
- /*
171
- * A duplicate WITHIN the request is skipped rather than merged, for
172
- * the reason the contacts import gives: the second row would merge
173
- * onto the first's document and the tally would read one created and
174
- * one merged for one person in one file. Across requests the door's
175
- * own dedupe answers, and reports a merge.
176
- */ const seen = new Set();
177
- read.rows.forEach((raw, index)=>{
178
- const verdict = normalizeLeadImportRow(raw);
179
- if (verdict.ok === false) {
180
- skipped.push({
181
- index,
182
- email: verdict.input,
183
- reason: verdict.reason
184
- });
185
- return;
186
- }
187
- // Non-null by construction: the normalizer refused every address
188
- // this derivation could not key.
189
- const key = personKey(verdict.row.email);
190
- if (seen.has(key)) {
191
- skipped.push({
192
- index,
193
- email: verdict.row.email,
194
- reason: 'duplicate'
195
- });
196
- return;
197
- }
198
- seen.add(key);
199
- for (const entry of verdict.row.dropped){
200
- var _dropped_entry_field;
201
- dropped[entry.field] = ((_dropped_entry_field = dropped[entry.field]) != null ? _dropped_entry_field : 0) + 1;
202
- }
203
- normalized.push({
204
- index,
205
- row: verdict.row,
206
- key
207
- });
208
- });
209
- /*
210
- * THE ADDRESSES' MAIL SERVERS (AGL-3328), asked now so the answers
211
- * arrive while the rows are written, and read after the last one for
212
- * the result's "N addresses have no mail server". The records
213
- * themselves are stamped by the check `addHostLead` queues.
214
- */ const mailServers = findUndeliverableEmails(normalized.map((entry)=>entry.row.email));
215
- const imported = [];
216
- const firestore = firebaseAdmin.app().firestore();
217
- const hostRef = firestore.collection('hosts').doc(context.hostId);
218
- const leadsRef = await orgLeadsForHost(context.hostId);
219
- const refs = normalized.map((entry)=>leadsRef.doc(entry.key));
220
- const namesCampaigns = normalized.some((entry)=>{
221
- var _entry_row_campaigns;
222
- return (_entry_row_campaigns = entry.row.campaigns) == null ? void 0 : _entry_row_campaigns.length;
223
- });
224
- const [owners, before, campaigns, leadSources] = await Promise.all([
225
- ownerDirectory(context.orgId, normalized.map((entry)=>entry.row)),
226
- refs.length ? firestore.getAll(...refs) : Promise.resolve([]),
227
- namesCampaigns ? campaignDirectory(firestore, context.orgId, context.hostId) : Promise.resolve(new Map()),
228
- // The org's lead source list (AGL-3298): one read per chunk, for the
229
- // values rows name and the default a new lead starts from.
230
- readLeadSourcePicklist(firestore, context.orgId)
231
- ]);
232
- const beforeById = new Map(before.map((snapshot)=>[
233
- snapshot.id,
234
- snapshot
235
- ]));
236
- const held = new Set(before.filter((snapshot)=>snapshot.exists).map((snapshot)=>snapshot.id));
237
- const ownersUnresolved = new Set();
238
- let counted = null;
239
- let createdHere = 0;
240
- let created = 0;
241
- let merged = 0;
242
- for (const { index, row, key } of normalized){
243
- var _row_campaigns;
244
- var _beforeById_get;
245
- // The campaigns the row names, every one of them the org's, or the
246
- // row is refused whole and named (AGL-3254).
247
- const campaignIds = ((_row_campaigns = row.campaigns) != null ? _row_campaigns : []).map((name)=>{
248
- var _campaigns_get;
249
- return (_campaigns_get = campaigns.get(name.toLowerCase())) != null ? _campaigns_get : '';
250
- });
251
- if (campaignIds.some((id)=>!id)) {
252
- skipped.push({
253
- index,
254
- email: row.email,
255
- reason: 'campaign-unknown'
256
- });
257
- continue;
258
- }
259
- const isNew = !held.has(key);
260
- /*
261
- * THE LEAD SOURCE, restricted to the org's list (AGL-3298). A value
262
- * the list does not hold is refused whole and named, as a campaign
263
- * the org does not have is — the drawer's notice has already listed
264
- * the values the list allows — and a lead the site already held
265
- * keeps the value it has. A new lead with no value starts from the
266
- * list's default.
267
- */ const leadSource = resolveLeadSourceWrite(leadSources, row.profile.leadSource, {
268
- current: (_beforeById_get = beforeById.get(key)) == null ? void 0 : _beforeById_get.get('leadSource'),
269
- created: isNew
270
- });
271
- if (leadSource.ok === false) {
272
- skipped.push({
273
- index,
274
- email: row.email,
275
- reason: 'lead-source-unknown'
276
- });
277
- continue;
278
- }
279
- if (leadSource.write) row.profile.leadSource = leadSource.write;
280
- /*
281
- * The platform lead ceiling, judged the way the deals import judges
282
- * the records band: one count at the first create, re-judged locally
283
- * as the request creates more. The door re-judges it authoritatively
284
- * inside its own transaction, so a race is still refused there — this
285
- * is what lets the operator's skipped file say WHY rather than only
286
- * that the row could not be saved.
287
- */ if (isNew) {
288
- if (counted === null) {
289
- counted = (await leadsRef.count().get()).data().count;
290
- }
291
- if (checkVisitorRecordCeiling(counted + createdHere, LEADS_MAX_PER_HOST).exceeded) {
292
- skipped.push({
293
- index,
294
- email: row.email,
295
- reason: 'lead-ceiling'
296
- });
297
- continue;
298
- }
299
- }
300
- let ownerUid;
301
- if (row.ownerEmail) {
302
- ownerUid = owners.get(row.ownerEmail);
303
- if (!ownerUid) ownersUnresolved.add(row.ownerEmail);
304
- }
305
- const stored = await addHostLead({
306
- hostRef,
307
- hostId: context.hostId,
308
- lead: _extends({
309
- email: row.email
310
- }, row.name ? {
311
- name: row.name
312
- } : {}, {
313
- source: LEAD_IMPORT_SOURCE
314
- })
315
- });
316
- if (!stored) {
317
- skipped.push({
318
- index,
319
- email: row.email,
320
- reason: 'write-failed'
321
- });
322
- continue;
323
- }
324
- const working = workingState(row, ownerUid, [
325
- ...new Set(campaignIds)
326
- ]);
327
- if (working) {
328
- await leadsRef.doc(key).set(_extends({}, working, {
329
- updatedAt: FieldValue.serverTimestamp()
330
- }), {
331
- merge: true
332
- });
333
- // The file's profile, tags, status and lead source are what the
334
- // Leads list searches and filters by (AGL-3321).
335
- await restampCrmListFieldsAt(leadsRef.doc(key), 'leads');
336
- }
337
- imported.push(row.email);
338
- if (isNew) {
339
- created += 1;
340
- createdHere += 1;
341
- } else {
342
- merged += 1;
343
- }
344
- }
345
- const noMailServer = countUndeliverableEmails(imported, await settleWithin(mailServers, IMPORT_MAIL_CHECK_GRACE_MS));
346
- const result = _extends({
347
- received: read.rows.length,
348
- created,
349
- merged,
350
- skipped: skipped.sort((a, b)=>a.index - b.index),
351
- dropped,
352
- ownersUnresolved: [
353
- ...ownersUnresolved
354
- ]
355
- }, noMailServer !== undefined ? {
356
- noMailServer
357
- } : {});
358
- return res.status(200).json(result);
359
- } catch (error) {
360
- console.error('crm/leads-import failed', error);
361
- return res.status(500).json({
362
- error: 'The import could not continue.'
363
- });
364
- }
365
- };
366
-
367
- //# sourceMappingURL=leads-import.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/leads-import.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `POST /api/crm/leads-import` — one chunk of a leads file, written\n * (AGL-2701).\n *\n * The browser has already read the file and applied the operator's column\n * mapping; what arrives here is up to {@link LEAD_IMPORT_CHUNK_SIZE} raw\n * rows in the vocabulary `crm-lead-import.ts` defines. This route judges\n * each one through the same normalizer, resolves the owner against the\n * org's roster, and writes it in two halves.\n *\n * ## The capture half goes through the door every capture goes through\n *\n * `addHostLead` — the ONE writer of `hosts/{hostId}/leads` for the sign-up\n * handler and both booking paths — is the writer here too. That is what\n * makes an imported row key on `personKey` the way a second form\n * submission does, land under the site by path with `capturedByHostIds`\n * naming it, count against `LEADS_MAX_PER_HOST` inside the transaction\n * that writes, and record NO marketing basis. A bulk path with its own\n * `add()` would grow an unkeyed, unbounded copy of the site's leads under\n * a second set of rules.\n *\n * ## A lead is host-scoped by path, so nothing here stamps `visibleTo`\n *\n * The five org collections carry `visibleTo` because they sit under\n * `orgs/{orgId}` and the rules decide per document which sites may read\n * them. A lead does not: `hosts/{hostId}/leads` is private to the site by\n * path, the rules admit the site's own members, and the list therefore\n * reads it with no scope clause. An import that stamped scope tokens onto\n * a lead would be writing a field nothing reads, and would suggest a\n * sharing decision the collection does not have. The site is the whole\n * scope, and the drawer's picker is where it is chosen.\n *\n * ## No file may hand a lead a consent it did not give\n *\n * A capture writes a marketing basis only when the visitor ticked a box in\n * front of them. A CSV row has no such event behind it, so the lead\n * vocabulary has no consent column and this route passes no\n * `marketingConsent`: an imported lead is mailable only if some earlier\n * capture on this site already recorded a basis, which `addHostLead`\n * carries forward untouched. The contacts import has a consent column\n * because its own export writes one and the merchant is asserting a basis\n * they hold per person; the leads export writes none, and a column the\n * file cannot fill is a column that cannot be misread.\n *\n * ## The working half is written the way the list writes it\n *\n * A status, an owner, a reason and notes are the CRM's annotations, not\n * the capture's, and the leads list already writes them client-direct onto\n * the same document. A row that carries any of them gets one merge write\n * after the door's, stamped `updatedAt` exactly as the list stamps it. A\n * row that carries none costs nothing extra.\n *\n * ## Created or merged is read once for the chunk\n *\n * The door reports only whether the lead was STORED, and the result panel\n * has to say how many people were added and how many updated. One\n * `getAll` over the chunk's document ids answers that for every row in a\n * single round trip, before any of them is written — which is also the\n * only moment the answer is still true.\n */\n\nimport {\n checkVisitorRecordCeiling,\n type ContactSource,\n LEADS_MAX_PER_HOST,\n personKey,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport {\n LEAD_IMPORT_CHUNK_SIZE,\n LEAD_IMPORT_MAX_BODY_BYTES,\n type LeadImportChunkResult,\n type LeadImportRawRow,\n type LeadImportRow,\n type LeadImportSkippedRow,\n normalizeLeadImportRow,\n} from '../model/crm-lead-import'\nimport {\n addHostLead,\n firebaseAdmin,\n orgLeadsForHost,\n restampCrmListFieldsAt,\n} from '@aglyn/tenant-data-admin'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n listOrgContainers,\n type OrgContainerRecord,\n} from '@aglyn/tenant-data-admin/server/org-containers'\n// The leaf, for the reason the webhook imports the delivery log from its\n// leaf: a spec that stands a partial barrel in must still reach it.\nimport {\n countUndeliverableEmails,\n findUndeliverableEmails,\n IMPORT_MAIL_CHECK_GRACE_MS,\n settleWithin,\n} from '@aglyn/tenant-data-admin/server/capture-email-check'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n ownerDirectory,\n readImportRows,\n resolveImportContext,\n} from './import-context'\nimport { readLeadSourcePicklist, resolveLeadSourceWrite } from './lead-source-picklist'\n\n/**\n * The surface an imported lead names, beside `signup`, `booking` and\n * `form:{formId}`.\n *\n * Typed as the capture-source union rather than a bare string, which is\n * what the contacts import gets for free from the door it calls: a lead's\n * `source` is plain text on the way in, so this is the only place the word\n * can be held to the same vocabulary the source filters and the labels\n * read. One person brought in by one file reads the same on both records.\n */\nconst LEAD_IMPORT_SOURCE: ContactSource = 'import'\n\n/**\n * How many of the org's campaigns one chunk reads to resolve the names a\n * file carries (AGL-3254). Well past what an org keeps — the picker offers\n * fifty — and read once per chunk rather than once per row.\n */\nconst CAMPAIGN_DIRECTORY_CEILING = 200\n\n/**\n * The org's live campaigns by NAME, lower-cased, for the `campaigns`\n * column. A lead is an org record, so every live container of the org's\n * `campaign` kind may be named, whichever sites it is\n * placed on; where two share a name, the one placed on the importing site\n * wins, because that is the one its picker shows. A name the org does not\n * have is not guessed at: the row is refused whole and named, because a\n * lead filed under half its campaigns is a lead nobody asked for. Read\n * only when some row names one.\n */\nasync function campaignDirectory(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n hostId: string,\n): Promise<Map<string, string>> {\n const directory = new Map<string, string>()\n if (!orgId) return directory\n const containers = await listOrgContainers(firestore, 'campaign', orgId, CAMPAIGN_DIRECTORY_CEILING)\n const live = containers.filter((container) => container.live)\n const placed = (container: OrgContainerRecord) => visibleToHost(container.visibleTo, hostId)\n for (const container of [...live.filter(placed), ...live.filter((entry) => !placed(entry))]) {\n const name = container.name.toLowerCase()\n if (name && !directory.has(name)) directory.set(name, container.id)\n }\n return directory\n}\n\n/**\n * The team's annotations on one row and the lead's own profile\n * (AGL-3231), or nothing when the file named none of either. A profile\n * value lands as the record's card would write it; the file never clears\n * one, because a blank cell is a cell nobody filled.\n */\nfunction workingState(\n row: LeadImportRow,\n ownerUid: string | undefined,\n campaignIds: readonly string[],\n): Record<string, unknown> | null {\n const fields: Record<string, unknown> = {\n ...(row.status ? { status: row.status } : {}),\n ...(ownerUid ? { ownerUid } : {}),\n ...(row.unqualifiedReason ? { unqualifiedReason: row.unqualifiedReason } : {}),\n ...(row.notes ? { notes: row.notes } : {}),\n ...row.profile,\n // Added to what a lead the site already held carries (AGL-3254): a\n // file re-imported under a new campaign files the person under one\n // more, and never takes them out of the last.\n ...(campaignIds.length ? { campaignIds: FieldValue.arrayUnion(...campaignIds) } : {}),\n }\n return Object.keys(fields).length ? fields : null\n}\n\n/**\n * `POST crm/leads-import` — `{ hostId, rows }` → a {@link LeadImportChunkResult}.\n *\n * Rows are written one after another so the ceiling's local count stays\n * honest across the request.\n */\nexport const crmLeadsImportHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const read = readImportRows<LeadImportRawRow>(req, {\n maxBodyBytes: LEAD_IMPORT_MAX_BODY_BYTES,\n chunkSize: LEAD_IMPORT_CHUNK_SIZE,\n })\n if ('error' in read) return res.status(read.status).json({ error: read.error })\n try {\n const context = await resolveImportContext(req)\n if (context.ok === false) return res.status(context.status).json(context.body)\n\n const skipped: LeadImportSkippedRow[] = []\n const dropped: Record<string, number> = {}\n const normalized: { index: number; row: LeadImportRow; key: string }[] = []\n /*\n * A duplicate WITHIN the request is skipped rather than merged, for\n * the reason the contacts import gives: the second row would merge\n * onto the first's document and the tally would read one created and\n * one merged for one person in one file. Across requests the door's\n * own dedupe answers, and reports a merge.\n */\n const seen = new Set<string>()\n read.rows.forEach((raw, index) => {\n const verdict = normalizeLeadImportRow(raw)\n if (verdict.ok === false) {\n skipped.push({ index, email: verdict.input, reason: verdict.reason })\n return\n }\n // Non-null by construction: the normalizer refused every address\n // this derivation could not key.\n const key = personKey(verdict.row.email) as string\n if (seen.has(key)) {\n skipped.push({ index, email: verdict.row.email, reason: 'duplicate' })\n return\n }\n seen.add(key)\n for (const entry of verdict.row.dropped) {\n dropped[entry.field] = (dropped[entry.field] ?? 0) + 1\n }\n normalized.push({ index, row: verdict.row, key })\n })\n\n /*\n * THE ADDRESSES' MAIL SERVERS (AGL-3328), asked now so the answers\n * arrive while the rows are written, and read after the last one for\n * the result's \"N addresses have no mail server\". The records\n * themselves are stamped by the check `addHostLead` queues.\n */\n const mailServers = findUndeliverableEmails(normalized.map((entry) => entry.row.email))\n const imported: string[] = []\n\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(context.hostId)\n const leadsRef = await orgLeadsForHost(context.hostId)\n const refs = normalized.map((entry) => leadsRef.doc(entry.key))\n const namesCampaigns = normalized.some((entry) => entry.row.campaigns?.length)\n const [owners, before, campaigns, leadSources] = await Promise.all([\n ownerDirectory(\n context.orgId,\n normalized.map((entry) => entry.row),\n ),\n refs.length ? firestore.getAll(...refs) : Promise.resolve([]),\n namesCampaigns\n ? campaignDirectory(firestore, context.orgId, context.hostId)\n : Promise.resolve(new Map<string, string>()),\n // The org's lead source list (AGL-3298): one read per chunk, for the\n // values rows name and the default a new lead starts from.\n readLeadSourcePicklist(firestore, context.orgId),\n ])\n const beforeById = new Map(before.map((snapshot) => [snapshot.id, snapshot]))\n const held = new Set(\n before.filter((snapshot) => snapshot.exists).map((snapshot) => snapshot.id),\n )\n const ownersUnresolved = new Set<string>()\n let counted: number | null = null\n let createdHere = 0\n let created = 0\n let merged = 0\n\n for (const { index, row, key } of normalized) {\n // The campaigns the row names, every one of them the org's, or the\n // row is refused whole and named (AGL-3254).\n const campaignIds = (row.campaigns ?? []).map((name) => campaigns.get(name.toLowerCase()) ?? '')\n if (campaignIds.some((id) => !id)) {\n skipped.push({ index, email: row.email, reason: 'campaign-unknown' })\n continue\n }\n const isNew = !held.has(key)\n /*\n * THE LEAD SOURCE, restricted to the org's list (AGL-3298). A value\n * the list does not hold is refused whole and named, as a campaign\n * the org does not have is — the drawer's notice has already listed\n * the values the list allows — and a lead the site already held\n * keeps the value it has. A new lead with no value starts from the\n * list's default.\n */\n const leadSource = resolveLeadSourceWrite(leadSources, row.profile.leadSource, {\n current: beforeById.get(key)?.get('leadSource'),\n created: isNew,\n })\n if (leadSource.ok === false) {\n skipped.push({ index, email: row.email, reason: 'lead-source-unknown' })\n continue\n }\n if (leadSource.write) row.profile.leadSource = leadSource.write\n /*\n * The platform lead ceiling, judged the way the deals import judges\n * the records band: one count at the first create, re-judged locally\n * as the request creates more. The door re-judges it authoritatively\n * inside its own transaction, so a race is still refused there — this\n * is what lets the operator's skipped file say WHY rather than only\n * that the row could not be saved.\n */\n if (isNew) {\n if (counted === null) {\n counted = (await leadsRef.count().get()).data().count\n }\n if (\n checkVisitorRecordCeiling(counted + createdHere, LEADS_MAX_PER_HOST)\n .exceeded\n ) {\n skipped.push({ index, email: row.email, reason: 'lead-ceiling' })\n continue\n }\n }\n let ownerUid: string | undefined\n if (row.ownerEmail) {\n ownerUid = owners.get(row.ownerEmail)\n if (!ownerUid) ownersUnresolved.add(row.ownerEmail)\n }\n const stored = await addHostLead({\n hostRef,\n hostId: context.hostId,\n lead: {\n email: row.email,\n ...(row.name ? { name: row.name } : {}),\n source: LEAD_IMPORT_SOURCE,\n },\n })\n if (!stored) {\n skipped.push({ index, email: row.email, reason: 'write-failed' })\n continue\n }\n const working = workingState(row, ownerUid, [...new Set(campaignIds)])\n if (working) {\n await leadsRef\n .doc(key)\n .set(\n { ...working, updatedAt: FieldValue.serverTimestamp() },\n { merge: true },\n )\n // The file's profile, tags, status and lead source are what the\n // Leads list searches and filters by (AGL-3321).\n await restampCrmListFieldsAt(leadsRef.doc(key), 'leads')\n }\n imported.push(row.email)\n if (isNew) {\n created += 1\n createdHere += 1\n } else {\n merged += 1\n }\n }\n const noMailServer = countUndeliverableEmails(\n imported,\n await settleWithin(mailServers, IMPORT_MAIL_CHECK_GRACE_MS),\n )\n\n const result: LeadImportChunkResult = {\n received: read.rows.length,\n created,\n merged,\n skipped: skipped.sort((a, b) => a.index - b.index),\n dropped,\n ownersUnresolved: [...ownersUnresolved],\n ...(noMailServer !== undefined ? { noMailServer } : {}),\n }\n return res.status(200).json(result)\n } catch (error) {\n console.error('crm/leads-import failed', error)\n return res.status(500).json({ error: 'The import could not continue.' })\n }\n}\n"],"names":["checkVisitorRecordCeiling","LEADS_MAX_PER_HOST","personKey","LEAD_IMPORT_CHUNK_SIZE","LEAD_IMPORT_MAX_BODY_BYTES","normalizeLeadImportRow","addHostLead","firebaseAdmin","orgLeadsForHost","restampCrmListFieldsAt","visibleToHost","listOrgContainers","countUndeliverableEmails","findUndeliverableEmails","IMPORT_MAIL_CHECK_GRACE_MS","settleWithin","FieldValue","ownerDirectory","readImportRows","resolveImportContext","readLeadSourcePicklist","resolveLeadSourceWrite","LEAD_IMPORT_SOURCE","CAMPAIGN_DIRECTORY_CEILING","campaignDirectory","firestore","orgId","hostId","directory","Map","containers","live","filter","container","placed","visibleTo","entry","name","toLowerCase","has","set","id","workingState","row","ownerUid","campaignIds","fields","status","unqualifiedReason","notes","profile","length","arrayUnion","Object","keys","crmLeadsImportHandler","req","res","method","setHeader","json","error","read","maxBodyBytes","chunkSize","context","ok","body","skipped","dropped","normalized","seen","Set","rows","forEach","raw","index","verdict","push","email","input","reason","key","add","field","mailServers","map","imported","app","hostRef","collection","doc","leadsRef","refs","namesCampaigns","some","campaigns","owners","before","leadSources","Promise","all","getAll","resolve","beforeById","snapshot","held","exists","ownersUnresolved","counted","createdHere","created","merged","get","isNew","leadSource","current","write","count","data","exceeded","ownerEmail","stored","lead","source","working","updatedAt","serverTimestamp","merge","noMailServer","result","received","sort","a","b","undefined","console"],"mappings":"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"}
@@ -1,54 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- /**
18
- * `POST /api/crm/tasks-import` — one chunk of a tasks file, written
19
- * (AGL-2662).
20
- *
21
- * The browser has already read the file and applied the operator's column
22
- * mapping; what arrives here is up to {@link TASK_IMPORT_CHUNK_SIZE} raw
23
- * rows in the vocabulary `crm-task-import.ts` defines. This route judges
24
- * each one through the same normalizer, resolves the assignee against the
25
- * org's roster, and writes the document in the shape the task-save route
26
- * writes — the optional ids absent rather than null, `dueAtMs` null when
27
- * unset so the ordered views keep the row.
28
- *
29
- * ## A stranger refuses the row
30
- *
31
- * The task-save route refuses an assignee who is not a member, because a
32
- * notification to a stranger's inbox is the one thing it must never do.
33
- * The import refuses the same way, per row, as `unknown-assignee`: the
34
- * roster is read once for the request, and an address it does not hold
35
- * sends the row back to the operator's skipped file. No notification is
36
- * sent for an imported task, so nothing here reaches an inbox at all.
37
- *
38
- * ## Every task is the site's
39
- *
40
- * The shared drawer names a site even at the organization level, so an
41
- * imported task carries that site's scope the way a task typed under it
42
- * does. The organization's own tasks — no site — are filed one at a time
43
- * from the organization hub, not from a file.
44
- *
45
- * Tasks are not CRM records for the band: no quota is asked.
46
- */
47
- import { type PluginApiHandler } from '@aglyn/aglyn/server';
48
- /**
49
- * `POST crm/tasks-import` — `{ hostId, rows }` → a {@link TaskImportChunkResult}.
50
- *
51
- * The writes are batched: a task has no key to collide on and no count to
52
- * keep honest between rows, so a chunk is one commit.
53
- */
54
- export declare const crmTasksImportHandler: PluginApiHandler;
@@ -1,160 +0,0 @@
1
- import { _ as _extends } from "@swc/helpers/_/_extends";
2
- /**
3
- * @license
4
- * Copyright 2026 Aglyn LLC
5
- *
6
- * Licensed under the Apache License, Version 2.0 (the "License");
7
- * you may not use this file except in compliance with the License.
8
- * You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing, software
13
- * distributed under the License is distributed on an "AS IS" BASIS,
14
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- * See the License for the specific language governing permissions and
16
- * limitations under the License.
17
- */ /**
18
- * `POST /api/crm/tasks-import` — one chunk of a tasks file, written
19
- * (AGL-2662).
20
- *
21
- * The browser has already read the file and applied the operator's column
22
- * mapping; what arrives here is up to {@link TASK_IMPORT_CHUNK_SIZE} raw
23
- * rows in the vocabulary `crm-task-import.ts` defines. This route judges
24
- * each one through the same normalizer, resolves the assignee against the
25
- * org's roster, and writes the document in the shape the task-save route
26
- * writes — the optional ids absent rather than null, `dueAtMs` null when
27
- * unset so the ordered views keep the row.
28
- *
29
- * ## A stranger refuses the row
30
- *
31
- * The task-save route refuses an assignee who is not a member, because a
32
- * notification to a stranger's inbox is the one thing it must never do.
33
- * The import refuses the same way, per row, as `unknown-assignee`: the
34
- * roster is read once for the request, and an address it does not hold
35
- * sends the row back to the operator's skipped file. No notification is
36
- * sent for an imported task, so nothing here reaches an inbox at all.
37
- *
38
- * ## Every task is the site's
39
- *
40
- * The shared drawer names a site even at the organization level, so an
41
- * imported task carries that site's scope the way a task typed under it
42
- * does. The organization's own tasks — no site — are filed one at a time
43
- * from the organization hub, not from a file.
44
- *
45
- * Tasks are not CRM records for the band: no quota is asked.
46
- */ import { CRM_COLLECTIONS, crmTaskListFields } from "@aglyn/aglyn/server";
47
- import { TASK_IMPORT_CHUNK_SIZE, TASK_IMPORT_MAX_BODY_BYTES, normalizeTaskImportRow } from "../model/crm-task-import.js";
48
- import { firebaseAdmin } from "@aglyn/tenant-data-admin";
49
- import { FieldValue } from "firebase-admin/firestore";
50
- import { ownerDirectory, readImportRows, resolveImportContext } from "./import-context.js";
51
- /**
52
- * `POST crm/tasks-import` — `{ hostId, rows }` → a {@link TaskImportChunkResult}.
53
- *
54
- * The writes are batched: a task has no key to collide on and no count to
55
- * keep honest between rows, so a chunk is one commit.
56
- */ export const crmTasksImportHandler = async (req, res)=>{
57
- if (req.method !== 'POST') {
58
- res.setHeader('Allow', 'POST');
59
- return res.status(405).json({
60
- error: 'Method not allowed'
61
- });
62
- }
63
- const read = readImportRows(req, {
64
- maxBodyBytes: TASK_IMPORT_MAX_BODY_BYTES,
65
- chunkSize: TASK_IMPORT_CHUNK_SIZE
66
- });
67
- if ('error' in read) return res.status(read.status).json({
68
- error: read.error
69
- });
70
- try {
71
- const context = await resolveImportContext(req);
72
- if (context.ok === false) return res.status(context.status).json(context.body);
73
- const skipped = [];
74
- const dropped = {};
75
- const normalized = [];
76
- read.rows.forEach((raw, index)=>{
77
- const verdict = normalizeTaskImportRow(raw);
78
- if (verdict.ok === false) {
79
- skipped.push({
80
- index,
81
- title: verdict.input,
82
- reason: verdict.reason
83
- });
84
- return;
85
- }
86
- for (const entry of verdict.row.dropped){
87
- var _dropped_entry_field;
88
- dropped[entry.field] = ((_dropped_entry_field = dropped[entry.field]) != null ? _dropped_entry_field : 0) + 1;
89
- }
90
- normalized.push({
91
- index,
92
- row: verdict.row
93
- });
94
- });
95
- // The roster reader keys on `ownerEmail`; a task's word for it is assignee.
96
- const members = await ownerDirectory(context.orgId, normalized.map((entry)=>({
97
- ownerEmail: entry.row.assigneeEmail
98
- })));
99
- const firestore = firebaseAdmin.app().firestore();
100
- const tasks = firestore.collection('orgs').doc(context.orgId).collection(CRM_COLLECTIONS.tasks);
101
- const batch = firestore.batch();
102
- const nowMs = Date.now();
103
- let created = 0;
104
- for (const { index, row } of normalized){
105
- var _row_notes;
106
- let assigneeUid;
107
- if (row.assigneeEmail) {
108
- assigneeUid = members.get(row.assigneeEmail);
109
- if (!assigneeUid) {
110
- skipped.push({
111
- index,
112
- title: row.title,
113
- reason: 'unknown-assignee'
114
- });
115
- continue;
116
- }
117
- }
118
- batch.set(tasks.doc(), _extends({}, crmTaskListFields({
119
- title: row.title,
120
- visibleTo: context.scopeTokens
121
- }), {
122
- title: row.title,
123
- kind: row.kind,
124
- priority: row.priority,
125
- status: row.status,
126
- dueAtMs: row.dueAtMs,
127
- notes: (_row_notes = row.notes) != null ? _row_notes : '',
128
- completedAtMs: row.status === 'done' ? nowMs : null
129
- }, row.status === 'done' ? {
130
- completedByUid: context.uid
131
- } : {}, assigneeUid ? {
132
- assigneeUid
133
- } : {}, {
134
- visibleTo: context.scopeTokens,
135
- hostId: context.hostId,
136
- createdByUid: context.uid,
137
- createdAt: FieldValue.serverTimestamp(),
138
- updatedAt: FieldValue.serverTimestamp()
139
- }));
140
- created += 1;
141
- }
142
- if (created) await batch.commit();
143
- const result = {
144
- received: read.rows.length,
145
- created,
146
- merged: 0,
147
- skipped: skipped.sort((a, b)=>a.index - b.index),
148
- dropped,
149
- ownersUnresolved: []
150
- };
151
- return res.status(200).json(result);
152
- } catch (error) {
153
- console.error('crm/tasks-import failed', error);
154
- return res.status(500).json({
155
- error: 'The import could not continue.'
156
- });
157
- }
158
- };
159
-
160
- //# sourceMappingURL=tasks-import.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/tasks-import.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `POST /api/crm/tasks-import` — one chunk of a tasks file, written\n * (AGL-2662).\n *\n * The browser has already read the file and applied the operator's column\n * mapping; what arrives here is up to {@link TASK_IMPORT_CHUNK_SIZE} raw\n * rows in the vocabulary `crm-task-import.ts` defines. This route judges\n * each one through the same normalizer, resolves the assignee against the\n * org's roster, and writes the document in the shape the task-save route\n * writes — the optional ids absent rather than null, `dueAtMs` null when\n * unset so the ordered views keep the row.\n *\n * ## A stranger refuses the row\n *\n * The task-save route refuses an assignee who is not a member, because a\n * notification to a stranger's inbox is the one thing it must never do.\n * The import refuses the same way, per row, as `unknown-assignee`: the\n * roster is read once for the request, and an address it does not hold\n * sends the row back to the operator's skipped file. No notification is\n * sent for an imported task, so nothing here reaches an inbox at all.\n *\n * ## Every task is the site's\n *\n * The shared drawer names a site even at the organization level, so an\n * imported task carries that site's scope the way a task typed under it\n * does. The organization's own tasks — no site — are filed one at a time\n * from the organization hub, not from a file.\n *\n * Tasks are not CRM records for the band: no quota is asked.\n */\n\nimport { CRM_COLLECTIONS, crmTaskListFields, type PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n TASK_IMPORT_CHUNK_SIZE,\n TASK_IMPORT_MAX_BODY_BYTES,\n type TaskImportChunkResult,\n type TaskImportRawRow,\n type TaskImportRow,\n type TaskImportSkippedRow,\n normalizeTaskImportRow,\n} from '../model/crm-task-import'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { ownerDirectory, readImportRows, resolveImportContext } from './import-context'\n\n/**\n * `POST crm/tasks-import` — `{ hostId, rows }` → a {@link TaskImportChunkResult}.\n *\n * The writes are batched: a task has no key to collide on and no count to\n * keep honest between rows, so a chunk is one commit.\n */\nexport const crmTasksImportHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const read = readImportRows<TaskImportRawRow>(req, {\n maxBodyBytes: TASK_IMPORT_MAX_BODY_BYTES,\n chunkSize: TASK_IMPORT_CHUNK_SIZE,\n })\n if ('error' in read) return res.status(read.status).json({ error: read.error })\n try {\n const context = await resolveImportContext(req)\n if (context.ok === false) return res.status(context.status).json(context.body)\n\n const skipped: TaskImportSkippedRow[] = []\n const dropped: Record<string, number> = {}\n const normalized: { index: number; row: TaskImportRow }[] = []\n read.rows.forEach((raw, index) => {\n const verdict = normalizeTaskImportRow(raw)\n if (verdict.ok === false) {\n skipped.push({ index, title: verdict.input, reason: verdict.reason })\n return\n }\n for (const entry of verdict.row.dropped) {\n dropped[entry.field] = (dropped[entry.field] ?? 0) + 1\n }\n normalized.push({ index, row: verdict.row })\n })\n\n // The roster reader keys on `ownerEmail`; a task's word for it is assignee.\n const members = await ownerDirectory(\n context.orgId,\n normalized.map((entry) => ({ ownerEmail: entry.row.assigneeEmail })),\n )\n const firestore = firebaseAdmin.app().firestore()\n const tasks = firestore\n .collection('orgs')\n .doc(context.orgId)\n .collection(CRM_COLLECTIONS.tasks)\n const batch = firestore.batch()\n const nowMs = Date.now()\n let created = 0\n\n for (const { index, row } of normalized) {\n let assigneeUid: string | undefined\n if (row.assigneeEmail) {\n assigneeUid = members.get(row.assigneeEmail)\n if (!assigneeUid) {\n skipped.push({ index, title: row.title, reason: 'unknown-assignee' })\n continue\n }\n }\n batch.set(tasks.doc(), {\n // What the Tasks list searches by (AGL-3321).\n ...crmTaskListFields({ title: row.title, visibleTo: context.scopeTokens }),\n title: row.title,\n kind: row.kind,\n priority: row.priority,\n status: row.status,\n dueAtMs: row.dueAtMs,\n notes: row.notes ?? '',\n completedAtMs: row.status === 'done' ? nowMs : null,\n ...(row.status === 'done' ? { completedByUid: context.uid } : {}),\n ...(assigneeUid ? { assigneeUid } : {}),\n visibleTo: context.scopeTokens,\n hostId: context.hostId,\n createdByUid: context.uid,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n created += 1\n }\n if (created) await batch.commit()\n\n const result: TaskImportChunkResult = {\n received: read.rows.length,\n created,\n merged: 0,\n skipped: skipped.sort((a, b) => a.index - b.index),\n dropped,\n ownersUnresolved: [],\n }\n return res.status(200).json(result)\n } catch (error) {\n console.error('crm/tasks-import failed', error)\n return res.status(500).json({ error: 'The import could not continue.' })\n }\n}\n"],"names":["CRM_COLLECTIONS","crmTaskListFields","TASK_IMPORT_CHUNK_SIZE","TASK_IMPORT_MAX_BODY_BYTES","normalizeTaskImportRow","firebaseAdmin","FieldValue","ownerDirectory","readImportRows","resolveImportContext","crmTasksImportHandler","req","res","method","setHeader","status","json","error","read","maxBodyBytes","chunkSize","context","ok","body","skipped","dropped","normalized","rows","forEach","raw","index","verdict","push","title","input","reason","entry","row","field","members","orgId","map","ownerEmail","assigneeEmail","firestore","app","tasks","collection","doc","batch","nowMs","Date","now","created","assigneeUid","get","set","visibleTo","scopeTokens","kind","priority","dueAtMs","notes","completedAtMs","completedByUid","uid","hostId","createdByUid","createdAt","serverTimestamp","updatedAt","commit","result","received","length","merged","sort","a","b","ownersUnresolved","console"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GAED,SAASA,eAAe,EAAEC,iBAAiB,QAA+B,sBAAqB;AAC/F,SACEC,sBAAsB,EACtBC,0BAA0B,EAK1BC,sBAAsB,QACjB,8BAA0B;AACjC,SAASC,aAAa,QAAQ,2BAA0B;AACxD,SAASC,UAAU,QAAQ,2BAA0B;AACrD,SAASC,cAAc,EAAEC,cAAc,EAAEC,oBAAoB,QAAQ,sBAAkB;AAEvF;;;;;CAKC,GACD,OAAO,MAAMC,wBAA0C,OAAOC,KAAKC;IACjE,IAAID,IAAIE,MAAM,KAAK,QAAQ;QACzBD,IAAIE,SAAS,CAAC,SAAS;QACvB,OAAOF,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;IAC5D;IACA,MAAMC,OAAOV,eAAiCG,KAAK;QACjDQ,cAAchB;QACdiB,WAAWlB;IACb;IACA,IAAI,WAAWgB,MAAM,OAAON,IAAIG,MAAM,CAACG,KAAKH,MAAM,EAAEC,IAAI,CAAC;QAAEC,OAAOC,KAAKD,KAAK;IAAC;IAC7E,IAAI;QACF,MAAMI,UAAU,MAAMZ,qBAAqBE;QAC3C,IAAIU,QAAQC,EAAE,KAAK,OAAO,OAAOV,IAAIG,MAAM,CAACM,QAAQN,MAAM,EAAEC,IAAI,CAACK,QAAQE,IAAI;QAE7E,MAAMC,UAAkC,EAAE;QAC1C,MAAMC,UAAkC,CAAC;QACzC,MAAMC,aAAsD,EAAE;QAC9DR,KAAKS,IAAI,CAACC,OAAO,CAAC,CAACC,KAAKC;YACtB,MAAMC,UAAU3B,uBAAuByB;YACvC,IAAIE,QAAQT,EAAE,KAAK,OAAO;gBACxBE,QAAQQ,IAAI,CAAC;oBAAEF;oBAAOG,OAAOF,QAAQG,KAAK;oBAAEC,QAAQJ,QAAQI,MAAM;gBAAC;gBACnE;YACF;YACA,KAAK,MAAMC,SAASL,QAAQM,GAAG,CAACZ,OAAO,CAAE;oBACfA;gBAAxBA,OAAO,CAACW,MAAME,KAAK,CAAC,GAAG,EAACb,uBAAAA,OAAO,CAACW,MAAME,KAAK,CAAC,YAApBb,uBAAwB,KAAK;YACvD;YACAC,WAAWM,IAAI,CAAC;gBAAEF;gBAAOO,KAAKN,QAAQM,GAAG;YAAC;QAC5C;QAEA,4EAA4E;QAC5E,MAAME,UAAU,MAAMhC,eACpBc,QAAQmB,KAAK,EACbd,WAAWe,GAAG,CAAC,CAACL,QAAW,CAAA;gBAAEM,YAAYN,MAAMC,GAAG,CAACM,aAAa;YAAC,CAAA;QAEnE,MAAMC,YAAYvC,cAAcwC,GAAG,GAAGD,SAAS;QAC/C,MAAME,QAAQF,UACXG,UAAU,CAAC,QACXC,GAAG,CAAC3B,QAAQmB,KAAK,EACjBO,UAAU,CAAC/C,gBAAgB8C,KAAK;QACnC,MAAMG,QAAQL,UAAUK,KAAK;QAC7B,MAAMC,QAAQC,KAAKC,GAAG;QACtB,IAAIC,UAAU;QAEd,KAAK,MAAM,EAAEvB,KAAK,EAAEO,GAAG,EAAE,IAAIX,WAAY;gBAiB9BW;YAhBT,IAAIiB;YACJ,IAAIjB,IAAIM,aAAa,EAAE;gBACrBW,cAAcf,QAAQgB,GAAG,CAAClB,IAAIM,aAAa;gBAC3C,IAAI,CAACW,aAAa;oBAChB9B,QAAQQ,IAAI,CAAC;wBAAEF;wBAAOG,OAAOI,IAAIJ,KAAK;wBAAEE,QAAQ;oBAAmB;oBACnE;gBACF;YACF;YACAc,MAAMO,GAAG,CAACV,MAAME,GAAG,IAAI,aAElB/C,kBAAkB;gBAAEgC,OAAOI,IAAIJ,KAAK;gBAAEwB,WAAWpC,QAAQqC,WAAW;YAAC;gBACxEzB,OAAOI,IAAIJ,KAAK;gBAChB0B,MAAMtB,IAAIsB,IAAI;gBACdC,UAAUvB,IAAIuB,QAAQ;gBACtB7C,QAAQsB,IAAItB,MAAM;gBAClB8C,SAASxB,IAAIwB,OAAO;gBACpBC,KAAK,GAAEzB,aAAAA,IAAIyB,KAAK,YAATzB,aAAa;gBACpB0B,eAAe1B,IAAItB,MAAM,KAAK,SAASmC,QAAQ;eAC3Cb,IAAItB,MAAM,KAAK,SAAS;gBAAEiD,gBAAgB3C,QAAQ4C,GAAG;YAAC,IAAI,CAAC,GAC3DX,cAAc;gBAAEA;YAAY,IAAI,CAAC;gBACrCG,WAAWpC,QAAQqC,WAAW;gBAC9BQ,QAAQ7C,QAAQ6C,MAAM;gBACtBC,cAAc9C,QAAQ4C,GAAG;gBACzBG,WAAW9D,WAAW+D,eAAe;gBACrCC,WAAWhE,WAAW+D,eAAe;;YAEvChB,WAAW;QACb;QACA,IAAIA,SAAS,MAAMJ,MAAMsB,MAAM;QAE/B,MAAMC,SAAgC;YACpCC,UAAUvD,KAAKS,IAAI,CAAC+C,MAAM;YAC1BrB;YACAsB,QAAQ;YACRnD,SAASA,QAAQoD,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAE/C,KAAK,GAAGgD,EAAEhD,KAAK;YACjDL;YACAsD,kBAAkB,EAAE;QACtB;QACA,OAAOnE,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAACwD;IAC9B,EAAE,OAAOvD,OAAO;QACd+D,QAAQ/D,KAAK,CAAC,2BAA2BA;QACzC,OAAOL,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAiC;IACxE;AACF,EAAC"}