@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
@@ -21,7 +21,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
21
21
  * Kept out of the component so the translation can be tested without a
22
22
  * Firestore or a DOM, and so the contact card and the company card, which
23
23
  * open the same drawer with a link preselected, build a deal the same way.
24
- */ import { contactDisplayName, nameSearchKey, nameSearchToken } from "@aglyn/aglyn";
24
+ */ import { contactDisplayName, crmPicklistDefaultLabel, DEAL_NEXT_STEP_MAX, dealContactRolesOf, dealContactRolesWithPrimary, dealStageForecastCategory, isCrmForecastCategory, judgeCrmPicklistValue, nameSearchKey, nameSearchToken, readDealProbability } from "@aglyn/aglyn";
25
25
  import { amountInputValue, dateInputMs, dateInputValue, DEFAULT_DEAL_CURRENCY, openStages, parseAmountInput } from "./deal-board-model.js";
26
26
  export const DEAL_TITLE_MAX = 120;
27
27
  export const DEAL_NOTES_MAX = 4000;
@@ -47,11 +47,17 @@ export const DEAL_NOTES_MAX = 4000;
47
47
  contactName: '',
48
48
  companyId: '',
49
49
  companyName: '',
50
- notes: ''
50
+ notes: '',
51
+ type: '',
52
+ leadSource: '',
53
+ nextStep: '',
54
+ probability: '',
55
+ forecastCategory: '',
56
+ campaignId: ''
51
57
  }, defaults);
52
58
  }
53
59
  /** The form an existing deal opens as. */ export function dealFormFromDoc(deal) {
54
- var _deal_title, _deal_pipelineId, _deal_stageId, _deal_ownerUid, _deal_contactId, _deal_contactName, _deal_companyId, _deal_companyName, _deal_notes;
60
+ var _deal_title, _deal_pipelineId, _deal_stageId, _deal_ownerUid, _deal_contactId, _deal_contactName, _deal_companyId, _deal_companyName, _deal_notes, _deal_type, _deal_leadSource, _deal_nextStep, _deal_campaignId;
55
61
  return {
56
62
  title: String((_deal_title = deal.title) != null ? _deal_title : ''),
57
63
  pipelineId: String((_deal_pipelineId = deal.pipelineId) != null ? _deal_pipelineId : ''),
@@ -64,7 +70,13 @@ export const DEAL_NOTES_MAX = 4000;
64
70
  contactName: String((_deal_contactName = deal.contactName) != null ? _deal_contactName : ''),
65
71
  companyId: String((_deal_companyId = deal.companyId) != null ? _deal_companyId : ''),
66
72
  companyName: String((_deal_companyName = deal.companyName) != null ? _deal_companyName : ''),
67
- notes: String((_deal_notes = deal.notes) != null ? _deal_notes : '')
73
+ notes: String((_deal_notes = deal.notes) != null ? _deal_notes : ''),
74
+ type: String((_deal_type = deal.type) != null ? _deal_type : ''),
75
+ leadSource: String((_deal_leadSource = deal.leadSource) != null ? _deal_leadSource : ''),
76
+ nextStep: String((_deal_nextStep = deal.nextStep) != null ? _deal_nextStep : ''),
77
+ probability: typeof deal.probability === 'number' ? String(deal.probability) : '',
78
+ forecastCategory: isCrmForecastCategory(deal.forecastCategory) ? deal.forecastCategory : '',
79
+ campaignId: String((_deal_campaignId = deal.campaignId) != null ? _deal_campaignId : '')
68
80
  };
69
81
  }
70
82
  /**
@@ -84,11 +96,41 @@ export const DEAL_NOTES_MAX = 4000;
84
96
  if (values.expectedClose.trim() && dateInputMs(values.expectedClose) === null) {
85
97
  return 'The expected close has to be a date.';
86
98
  }
99
+ if (readDealProbability(values.probability) === undefined) {
100
+ return 'The probability has to be a whole number from 0 to 100.';
101
+ }
87
102
  if (mode === 'create' && (!values.pipelineId || !values.stageId)) {
88
103
  return 'Pick the stage the deal starts in.';
89
104
  }
90
105
  return null;
91
106
  }
107
+ /**
108
+ * The form's two picklist fields judged against the org's lists (AGL-3516),
109
+ * the way every server door judges them: an active value is stored as the
110
+ * list spells it, a blank clears, the deal's own current value is kept, and
111
+ * anything else is refused naming what the list allows. A NEW deal with no
112
+ * Type takes the list's default, as Salesforce's does; the lead source is
113
+ * never defaulted — it records where a deal came from.
114
+ */ export function judgeDealFormPicklists(values, lists, options) {
115
+ var _crmPicklistDefaultLabel, _type_value, _leadSource_value;
116
+ var _options_current, _options_current1;
117
+ const typed = values.type.trim() || (options.created ? (_crmPicklistDefaultLabel = crmPicklistDefaultLabel(lists.type)) != null ? _crmPicklistDefaultLabel : '' : '');
118
+ const type = judgeCrmPicklistValue('opportunityType', lists.type, typed, (_options_current = options.current) == null ? void 0 : _options_current.type);
119
+ if (type.ok === false) return type;
120
+ const leadSource = judgeCrmPicklistValue('leadSource', lists.leadSource, values.leadSource, (_options_current1 = options.current) == null ? void 0 : _options_current1.leadSource);
121
+ if (leadSource.ok === false) return leadSource;
122
+ return {
123
+ ok: true,
124
+ values: _extends({}, values, {
125
+ type: (_type_value = type.value) != null ? _type_value : '',
126
+ leadSource: (_leadSource_value = leadSource.value) != null ? _leadSource_value : ''
127
+ })
128
+ };
129
+ }
130
+ /** The forecast category a save stamps: the form's, else the stage's, else none. */ function forecastCategoryFor(values, stage) {
131
+ if (isCrmForecastCategory(values.forecastCategory)) return values.forecastCategory;
132
+ return stage ? dealStageForecastCategory(stage) : null;
133
+ }
92
134
  /**
93
135
  * The optional fields, as the values to SET and the keys to CLEAR.
94
136
  *
@@ -99,6 +141,7 @@ export const DEAL_NOTES_MAX = 4000;
99
141
  * either. On a create the cleared keys are simply not written; on an edit
100
142
  * the component turns them into field deletes.
101
143
  */ function optionalFields(values) {
144
+ var _readDealProbability;
102
145
  const set = {};
103
146
  const clear = [];
104
147
  const put = (key, value)=>{
@@ -113,6 +156,11 @@ export const DEAL_NOTES_MAX = 4000;
113
156
  put('companyId', values.companyId.trim());
114
157
  put('companyName', values.companyId.trim() ? values.companyName.trim() : '');
115
158
  put('notes', values.notes.trim().slice(0, DEAL_NOTES_MAX));
159
+ put('type', values.type.trim());
160
+ put('leadSource', values.leadSource.trim());
161
+ put('nextStep', values.nextStep.trim().replace(/\s+/g, ' ').slice(0, DEAL_NEXT_STEP_MAX));
162
+ put('probability', (_readDealProbability = readDealProbability(values.probability)) != null ? _readDealProbability : null);
163
+ put('campaignId', values.campaignId.trim());
116
164
  return {
117
165
  set,
118
166
  clear
@@ -124,15 +172,27 @@ export const DEAL_NOTES_MAX = 4000;
124
172
  */ export function dealDocumentFromForm(values, context) {
125
173
  const title = values.title.trim().slice(0, DEAL_TITLE_MAX);
126
174
  const { set } = optionalFields(values);
175
+ const forecastCategory = forecastCategoryFor(values, context.stage);
176
+ const contactId = values.contactId.trim();
127
177
  return _extends({
128
178
  title,
129
179
  titleLower: nameSearchKey(title),
130
180
  pipelineId: values.pipelineId,
131
181
  stageId: values.stageId,
132
- status: 'open',
182
+ status: 'open'
183
+ }, contactId ? {
184
+ contactRoles: [
185
+ {
186
+ contactId,
187
+ primary: true
188
+ }
189
+ ]
190
+ } : {}, {
133
191
  currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),
134
192
  stageChangedAtMs: context.nowMs
135
- }, set, {
193
+ }, forecastCategory ? {
194
+ forecastCategory
195
+ } : {}, set, {
136
196
  visibleTo: context.visibleTo,
137
197
  hostId: context.hostId,
138
198
  createdByUid: context.uid,
@@ -151,8 +211,20 @@ export const DEAL_NOTES_MAX = 4000;
151
211
  * form shows them read-only, and a patch that re-wrote the number it was
152
212
  * seeded with would overwrite a line added from another tab meanwhile.
153
213
  */ export function dealPatchFromForm(values, nowMs, options = {}) {
214
+ var _options_current_contactId;
154
215
  const title = values.title.trim().slice(0, DEAL_TITLE_MAX);
155
- const { set, clear } = optionalFields(values);
216
+ const optional = optionalFields(values);
217
+ const set = optional.set;
218
+ const { clear } = optional;
219
+ const contactId = values.contactId.trim();
220
+ if (options.current && contactId !== ((_options_current_contactId = options.current.contactId) != null ? _options_current_contactId : '')) {
221
+ set['contactRoles'] = dealContactRolesWithPrimary(dealContactRolesOf(options.current), contactId || null);
222
+ }
223
+ // The category picked, else the stage's again; with neither, the field
224
+ // goes and every reader derives it from the stage.
225
+ const forecastCategory = forecastCategoryFor(values, options.stage);
226
+ if (forecastCategory) set['forecastCategory'] = forecastCategory;
227
+ else clear.push('forecastCategory');
156
228
  if (options.amountDerived) {
157
229
  delete set['amountCents'];
158
230
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/deal-form-model.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 * The deal drawer's values and the documents they become (AGL-2598), as pure\n * functions — the drawer holds strings the way inputs do, and this module is\n * the one place those strings turn into the typed fields a `CrmDeal` stores.\n * Kept out of the component so the translation can be tested without a\n * Firestore or a DOM, and so the contact card and the company card, which\n * open the same drawer with a link preselected, build a deal the same way.\n */\n\nimport {\n contactDisplayName,\n type CrmDeal,\n nameSearchKey,\n nameSearchToken,\n} from '@aglyn/aglyn'\nimport {\n amountInputValue,\n dateInputMs,\n dateInputValue,\n DEFAULT_DEAL_CURRENCY,\n type DealDoc,\n openStages,\n parseAmountInput,\n type PipelineDoc,\n} from './deal-board-model'\n\n/** What the drawer edits — strings as the inputs hold them. */\nexport interface DealFormValues {\n title: string\n pipelineId: string\n stageId: string\n /** As typed: `1,250.00`; parsed at save. */\n amount: string\n currency: string\n /** `YYYY-MM-DD` from a date input, or empty. */\n expectedClose: string\n ownerUid: string\n contactId: string\n contactName: string\n companyId: string\n companyName: string\n notes: string\n}\n\nexport const DEAL_TITLE_MAX = 120\nexport const DEAL_NOTES_MAX = 4000\n\n/**\n * A blank form aimed at a pipeline's first open stage.\n *\n * `defaults` is how a contact's page or a company's page starts a deal\n * already linked to itself: the caller passes the ids and names it knows and\n * the drawer opens with those fields filled. Anything it does not pass stays\n * blank rather than being guessed.\n */\nexport function emptyDealForm(\n pipeline: Pick<PipelineDoc, '$id' | 'stages'> | null | undefined,\n defaults: Partial<DealFormValues> = {},\n): DealFormValues {\n const firstOpen = openStages(pipeline)[0]\n return {\n title: '',\n pipelineId: pipeline?.$id ?? '',\n stageId: firstOpen?.id ?? '',\n amount: '',\n currency: DEFAULT_DEAL_CURRENCY,\n expectedClose: '',\n ownerUid: '',\n contactId: '',\n contactName: '',\n companyId: '',\n companyName: '',\n notes: '',\n ...defaults,\n }\n}\n\n/** The form an existing deal opens as. */\nexport function dealFormFromDoc(deal: DealDoc): DealFormValues {\n return {\n title: String(deal.title ?? ''),\n pipelineId: String(deal.pipelineId ?? ''),\n stageId: String(deal.stageId ?? ''),\n amount: amountInputValue(deal.amountCents),\n currency: String(deal.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n expectedClose: dateInputValue(deal.expectedCloseAtMs),\n ownerUid: String(deal.ownerUid ?? ''),\n contactId: String(deal.contactId ?? ''),\n contactName: String(deal.contactName ?? ''),\n companyId: String(deal.companyId ?? ''),\n companyName: String(deal.companyName ?? ''),\n notes: String(deal.notes ?? ''),\n }\n}\n\n/**\n * Why the form cannot be saved, or `null` when it can.\n *\n * A title is the one thing a deal needs — a card with no title is a card\n * nobody can find on the board. The amount is optional but, if typed, has to\n * parse: a deal saved with an amount the merchant typed and the model dropped\n * would forecast at zero without saying so. The stage is only checked on a\n * new deal, because an existing deal's stage is moved by the stage route and\n * is not this form's to write.\n */\nexport function dealFormProblem(\n values: DealFormValues,\n mode: 'create' | 'edit',\n): string | null {\n if (!values.title.trim()) return 'A deal needs a title.'\n if (values.amount.trim() && parseAmountInput(values.amount) === null) {\n return 'The amount has to be a number of zero or more.'\n }\n if (values.expectedClose.trim() && dateInputMs(values.expectedClose) === null) {\n return 'The expected close has to be a date.'\n }\n if (mode === 'create' && (!values.pipelineId || !values.stageId)) {\n return 'Pick the stage the deal starts in.'\n }\n return null\n}\n\n/** What every deal write stamps besides the fields the form edits. */\nexport interface DealWriteContext {\n visibleTo: string[]\n hostId: string\n uid: string\n nowMs: number\n}\n\n/**\n * The optional fields, as the values to SET and the keys to CLEAR.\n *\n * Split rather than written as `null` because a deal's optional ids are\n * typed as absent-or-string, and the indexes on `contactId` and `companyId`\n * are what the contact and company pages query — a `null` there would be a\n * row those pages cannot see and a filter on \"no contact\" could not find\n * either. On a create the cleared keys are simply not written; on an edit\n * the component turns them into field deletes.\n */\nfunction optionalFields(values: DealFormValues): {\n set: Record<string, string | number>\n clear: string[]\n} {\n const set: Record<string, string | number> = {}\n const clear: string[] = []\n const put = (key: string, value: string | number | null) => {\n if (value === null || value === '') clear.push(key)\n else set[key] = value\n }\n put('amountCents', parseAmountInput(values.amount))\n put('expectedCloseAtMs', dateInputMs(values.expectedClose))\n put('ownerUid', values.ownerUid.trim())\n put('contactId', values.contactId.trim())\n put('contactName', values.contactId.trim() ? values.contactName.trim() : '')\n put('companyId', values.companyId.trim())\n put('companyName', values.companyId.trim() ? values.companyName.trim() : '')\n put('notes', values.notes.trim().slice(0, DEAL_NOTES_MAX))\n return { set, clear }\n}\n\n/**\n * The document a new deal is created as. Open, in the chosen stage, with\n * the stage clock started now so \"days in stage\" counts from creation.\n */\nexport function dealDocumentFromForm(\n values: DealFormValues,\n context: DealWriteContext,\n): Record<string, unknown> {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const { set } = optionalFields(values)\n return {\n title,\n titleLower: nameSearchKey(title),\n pipelineId: values.pipelineId,\n stageId: values.stageId,\n status: 'open' satisfies CrmDeal['status'],\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n stageChangedAtMs: context.nowMs,\n ...set,\n visibleTo: context.visibleTo,\n hostId: context.hostId,\n createdByUid: context.uid,\n createdAt: new Date(context.nowMs),\n updatedAt: new Date(context.nowMs),\n }\n}\n\n/**\n * The patch an edit writes. Never the stage, the status or the scope: the\n * stage goes through the server route so the automations hear it, and the\n * scope is changed only by an org-wide member on purpose.\n *\n * Nor the amount or the currency of a deal whose amount is DERIVED\n * (`amountDerived`, AGL-2620): with line items on the deal the amount is\n * their sum and the currency is theirs, both owned by the products card. The\n * form shows them read-only, and a patch that re-wrote the number it was\n * seeded with would overwrite a line added from another tab meanwhile.\n */\nexport function dealPatchFromForm(\n values: DealFormValues,\n nowMs: number,\n options: { amountDerived?: boolean } = {},\n): { set: Record<string, unknown>; clear: string[] } {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const { set, clear } = optionalFields(values)\n if (options.amountDerived) {\n delete set['amountCents']\n return {\n set: { title, titleLower: nameSearchKey(title), ...set, updatedAt: new Date(nowMs) },\n clear: clear.filter((key) => key !== 'amountCents'),\n }\n }\n return {\n set: {\n title,\n titleLower: nameSearchKey(title),\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n ...set,\n updatedAt: new Date(nowMs),\n },\n clear,\n }\n}\n\n/** A contact as the picker offers it. */\nexport interface ContactChoice {\n id: string\n name: string\n email: string\n}\n\n/**\n * The contacts a typed query matches, from a window of recent contact rows,\n * in the list's own grammar.\n *\n * In memory rather than by query because the contacts collection has no\n * composite index over `visibleTo` with `nameLower` or `email`, and a\n * scoped list can carry only one array clause — so the picker reads the\n * newest rows the viewer may see (bounded, ordered) and matches here. The\n * match is what the list's search does: the normalized query as a prefix of\n * the email, or as a prefix of any word of the name (`nameTokens` holds\n * every word prefix a write produced; `nameLower` covers rows written before\n * the tokens existed). The holder's own name for the person wins over the\n * canonical one, as everywhere else.\n */\nexport function contactChoicesFor(\n queryText: string,\n rows: readonly Record<string, unknown>[],\n /**\n * The holder each row is named through: one group for every row under a\n * site, or a resolver per row at the organization level (AGL-2630), where\n * each contact reads through its own primary holder.\n */\n groupId: string | ((row: Record<string, unknown>) => string),\n max = 8,\n): ContactChoice[] {\n const key = nameSearchKey(queryText)\n const token = nameSearchToken(queryText)\n const choices: ContactChoice[] = []\n for (const row of rows) {\n const email = String(row['email'] ?? '').toLowerCase()\n const name = contactDisplayName(\n row,\n typeof groupId === 'function' ? groupId(row) : groupId,\n )\n const nameLower = nameSearchKey(name)\n const tokens = Array.isArray(row['nameTokens'])\n ? (row['nameTokens'] as unknown[]).map(String)\n : []\n const matches =\n !key ||\n email.startsWith(key) ||\n nameLower.startsWith(key) ||\n nameLower.includes(` ${key}`) ||\n (token ? tokens.includes(token) : false)\n if (!matches) continue\n choices.push({ id: String(row['$id'] ?? ''), name, email })\n if (choices.length >= max) break\n }\n return choices\n}\n"],"names":["contactDisplayName","nameSearchKey","nameSearchToken","amountInputValue","dateInputMs","dateInputValue","DEFAULT_DEAL_CURRENCY","openStages","parseAmountInput","DEAL_TITLE_MAX","DEAL_NOTES_MAX","emptyDealForm","pipeline","defaults","firstOpen","title","pipelineId","$id","stageId","id","amount","currency","expectedClose","ownerUid","contactId","contactName","companyId","companyName","notes","dealFormFromDoc","deal","String","amountCents","toLowerCase","expectedCloseAtMs","dealFormProblem","values","mode","trim","optionalFields","set","clear","put","key","value","push","slice","dealDocumentFromForm","context","titleLower","status","stageChangedAtMs","nowMs","visibleTo","hostId","createdByUid","uid","createdAt","Date","updatedAt","dealPatchFromForm","options","amountDerived","filter","contactChoicesFor","queryText","rows","groupId","max","token","choices","row","email","name","nameLower","tokens","Array","isArray","map","matches","startsWith","includes","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/deal-form-model.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 * The deal drawer's values and the documents they become (AGL-2598), as pure\n * functions — the drawer holds strings the way inputs do, and this module is\n * the one place those strings turn into the typed fields a `CrmDeal` stores.\n * Kept out of the component so the translation can be tested without a\n * Firestore or a DOM, and so the contact card and the company card, which\n * open the same drawer with a link preselected, build a deal the same way.\n */\n\nimport {\n contactDisplayName,\n type CrmDeal,\n type CrmDealStage,\n type CrmForecastCategory,\n type CrmPicklist,\n crmPicklistDefaultLabel,\n DEAL_NEXT_STEP_MAX,\n dealContactRolesOf,\n dealContactRolesWithPrimary,\n dealStageForecastCategory,\n isCrmForecastCategory,\n judgeCrmPicklistValue,\n nameSearchKey,\n nameSearchToken,\n readDealProbability,\n} from '@aglyn/aglyn'\nimport {\n amountInputValue,\n dateInputMs,\n dateInputValue,\n DEFAULT_DEAL_CURRENCY,\n type DealDoc,\n openStages,\n parseAmountInput,\n type PipelineDoc,\n} from './deal-board-model'\n\n/** What the drawer edits — strings as the inputs hold them. */\nexport interface DealFormValues {\n title: string\n pipelineId: string\n stageId: string\n /** As typed: `1,250.00`; parsed at save. */\n amount: string\n currency: string\n /** `YYYY-MM-DD` from a date input, or empty. */\n expectedClose: string\n ownerUid: string\n contactId: string\n contactName: string\n companyId: string\n companyName: string\n notes: string\n /*\n * Salesforce's Opportunity fields (AGL-3516).\n */\n /** The `opportunityType` label, or empty. */\n type: string\n /** The `leadSource` label, or empty. */\n leadSource: string\n nextStep: string\n /** The override as typed, `35`; empty means the stage's. */\n probability: string\n /** Empty means the stage's — what a save stamps when nobody picked one. */\n forecastCategory: '' | CrmForecastCategory\n /** One of the org's campaigns, or empty. */\n campaignId: string\n}\n\nexport const DEAL_TITLE_MAX = 120\nexport const DEAL_NOTES_MAX = 4000\n\n/**\n * A blank form aimed at a pipeline's first open stage.\n *\n * `defaults` is how a contact's page or a company's page starts a deal\n * already linked to itself: the caller passes the ids and names it knows and\n * the drawer opens with those fields filled. Anything it does not pass stays\n * blank rather than being guessed.\n */\nexport function emptyDealForm(\n pipeline: Pick<PipelineDoc, '$id' | 'stages'> | null | undefined,\n defaults: Partial<DealFormValues> = {},\n): DealFormValues {\n const firstOpen = openStages(pipeline)[0]\n return {\n title: '',\n pipelineId: pipeline?.$id ?? '',\n stageId: firstOpen?.id ?? '',\n amount: '',\n currency: DEFAULT_DEAL_CURRENCY,\n expectedClose: '',\n ownerUid: '',\n contactId: '',\n contactName: '',\n companyId: '',\n companyName: '',\n notes: '',\n type: '',\n leadSource: '',\n nextStep: '',\n probability: '',\n forecastCategory: '',\n campaignId: '',\n ...defaults,\n }\n}\n\n/** The form an existing deal opens as. */\nexport function dealFormFromDoc(deal: DealDoc): DealFormValues {\n return {\n title: String(deal.title ?? ''),\n pipelineId: String(deal.pipelineId ?? ''),\n stageId: String(deal.stageId ?? ''),\n amount: amountInputValue(deal.amountCents),\n currency: String(deal.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n expectedClose: dateInputValue(deal.expectedCloseAtMs),\n ownerUid: String(deal.ownerUid ?? ''),\n contactId: String(deal.contactId ?? ''),\n contactName: String(deal.contactName ?? ''),\n companyId: String(deal.companyId ?? ''),\n companyName: String(deal.companyName ?? ''),\n notes: String(deal.notes ?? ''),\n type: String(deal.type ?? ''),\n leadSource: String(deal.leadSource ?? ''),\n nextStep: String(deal.nextStep ?? ''),\n probability: typeof deal.probability === 'number' ? String(deal.probability) : '',\n forecastCategory: isCrmForecastCategory(deal.forecastCategory) ? deal.forecastCategory : '',\n campaignId: String(deal.campaignId ?? ''),\n }\n}\n\n/**\n * Why the form cannot be saved, or `null` when it can.\n *\n * A title is the one thing a deal needs — a card with no title is a card\n * nobody can find on the board. The amount is optional but, if typed, has to\n * parse: a deal saved with an amount the merchant typed and the model dropped\n * would forecast at zero without saying so. The stage is only checked on a\n * new deal, because an existing deal's stage is moved by the stage route and\n * is not this form's to write.\n */\nexport function dealFormProblem(\n values: DealFormValues,\n mode: 'create' | 'edit',\n): string | null {\n if (!values.title.trim()) return 'A deal needs a title.'\n if (values.amount.trim() && parseAmountInput(values.amount) === null) {\n return 'The amount has to be a number of zero or more.'\n }\n if (values.expectedClose.trim() && dateInputMs(values.expectedClose) === null) {\n return 'The expected close has to be a date.'\n }\n if (readDealProbability(values.probability) === undefined) {\n return 'The probability has to be a whole number from 0 to 100.'\n }\n if (mode === 'create' && (!values.pipelineId || !values.stageId)) {\n return 'Pick the stage the deal starts in.'\n }\n return null\n}\n\n/** What every deal write stamps besides the fields the form edits. */\nexport interface DealWriteContext {\n visibleTo: string[]\n hostId: string\n uid: string\n nowMs: number\n /**\n * The stage the deal is in, whose forecast category a form left on\n * \"from the stage\" stamps (AGL-3516). Absent, the field is left unset and\n * every reader derives it from the stage.\n */\n stage?: Pick<CrmDealStage, 'kind' | 'forecastCategory'> | null\n}\n\n/**\n * The form's two picklist fields judged against the org's lists (AGL-3516),\n * the way every server door judges them: an active value is stored as the\n * list spells it, a blank clears, the deal's own current value is kept, and\n * anything else is refused naming what the list allows. A NEW deal with no\n * Type takes the list's default, as Salesforce's does; the lead source is\n * never defaulted — it records where a deal came from.\n */\nexport function judgeDealFormPicklists(\n values: DealFormValues,\n lists: { type: CrmPicklist; leadSource: CrmPicklist },\n options: { current?: Partial<Pick<CrmDeal, 'type' | 'leadSource'>> | null; created: boolean },\n): { ok: true; values: DealFormValues } | { ok: false; error: string } {\n const typed = values.type.trim() || (options.created ? (crmPicklistDefaultLabel(lists.type) ?? '') : '')\n const type = judgeCrmPicklistValue('opportunityType', lists.type, typed, options.current?.type)\n if (type.ok === false) return type\n const leadSource = judgeCrmPicklistValue(\n 'leadSource',\n lists.leadSource,\n values.leadSource,\n options.current?.leadSource,\n )\n if (leadSource.ok === false) return leadSource\n return {\n ok: true,\n values: { ...values, type: type.value ?? '', leadSource: leadSource.value ?? '' },\n }\n}\n\n/** The forecast category a save stamps: the form's, else the stage's, else none. */\nfunction forecastCategoryFor(\n values: Pick<DealFormValues, 'forecastCategory'>,\n stage: DealWriteContext['stage'],\n): CrmForecastCategory | null {\n if (isCrmForecastCategory(values.forecastCategory)) return values.forecastCategory\n return stage ? dealStageForecastCategory(stage) : null\n}\n\n/**\n * The optional fields, as the values to SET and the keys to CLEAR.\n *\n * Split rather than written as `null` because a deal's optional ids are\n * typed as absent-or-string, and the indexes on `contactId` and `companyId`\n * are what the contact and company pages query — a `null` there would be a\n * row those pages cannot see and a filter on \"no contact\" could not find\n * either. On a create the cleared keys are simply not written; on an edit\n * the component turns them into field deletes.\n */\nfunction optionalFields(values: DealFormValues): {\n set: Record<string, string | number>\n clear: string[]\n} {\n const set: Record<string, string | number> = {}\n const clear: string[] = []\n const put = (key: string, value: string | number | null) => {\n if (value === null || value === '') clear.push(key)\n else set[key] = value\n }\n put('amountCents', parseAmountInput(values.amount))\n put('expectedCloseAtMs', dateInputMs(values.expectedClose))\n put('ownerUid', values.ownerUid.trim())\n put('contactId', values.contactId.trim())\n put('contactName', values.contactId.trim() ? values.contactName.trim() : '')\n put('companyId', values.companyId.trim())\n put('companyName', values.companyId.trim() ? values.companyName.trim() : '')\n put('notes', values.notes.trim().slice(0, DEAL_NOTES_MAX))\n put('type', values.type.trim())\n put('leadSource', values.leadSource.trim())\n put('nextStep', values.nextStep.trim().replace(/\\s+/g, ' ').slice(0, DEAL_NEXT_STEP_MAX))\n put('probability', readDealProbability(values.probability) ?? null)\n put('campaignId', values.campaignId.trim())\n return { set, clear }\n}\n\n/**\n * The document a new deal is created as. Open, in the chosen stage, with\n * the stage clock started now so \"days in stage\" counts from creation.\n */\nexport function dealDocumentFromForm(\n values: DealFormValues,\n context: DealWriteContext,\n): Record<string, unknown> {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const { set } = optionalFields(values)\n const forecastCategory = forecastCategoryFor(values, context.stage)\n const contactId = values.contactId.trim()\n return {\n title,\n titleLower: nameSearchKey(title),\n pipelineId: values.pipelineId,\n stageId: values.stageId,\n status: 'open' satisfies CrmDeal['status'],\n // The contact picked is the deal's Primary contact role (AGL-3521).\n ...(contactId ? { contactRoles: [{ contactId, primary: true }] } : {}),\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n stageChangedAtMs: context.nowMs,\n ...(forecastCategory ? { forecastCategory } : {}),\n ...set,\n visibleTo: context.visibleTo,\n hostId: context.hostId,\n createdByUid: context.uid,\n createdAt: new Date(context.nowMs),\n updatedAt: new Date(context.nowMs),\n }\n}\n\n/**\n * The patch an edit writes. Never the stage, the status or the scope: the\n * stage goes through the server route so the automations hear it, and the\n * scope is changed only by an org-wide member on purpose.\n *\n * Nor the amount or the currency of a deal whose amount is DERIVED\n * (`amountDerived`, AGL-2620): with line items on the deal the amount is\n * their sum and the currency is theirs, both owned by the products card. The\n * form shows them read-only, and a patch that re-wrote the number it was\n * seeded with would overwrite a line added from another tab meanwhile.\n */\nexport function dealPatchFromForm(\n values: DealFormValues,\n nowMs: number,\n options: {\n amountDerived?: boolean\n stage?: DealWriteContext['stage']\n /**\n * The deal as stored, whose contact roles a changed contact moves\n * (AGL-3521): the contact picked becomes the Primary — added with no\n * role when the deal did not name them — and a cleared contact leaves\n * the deal with no Primary. An unchanged contact writes no roles, so a\n * role edited on the deal's page meanwhile is not overwritten.\n */\n current?: Pick<CrmDeal, 'contactId' | 'contactRoles'> | null\n } = {},\n): { set: Record<string, unknown>; clear: string[] } {\n const title = values.title.trim().slice(0, DEAL_TITLE_MAX)\n const optional = optionalFields(values)\n const set: Record<string, unknown> = optional.set\n const { clear } = optional\n const contactId = values.contactId.trim()\n if (options.current && contactId !== (options.current.contactId ?? '')) {\n set['contactRoles'] = dealContactRolesWithPrimary(dealContactRolesOf(options.current), contactId || null)\n }\n // The category picked, else the stage's again; with neither, the field\n // goes and every reader derives it from the stage.\n const forecastCategory = forecastCategoryFor(values, options.stage)\n if (forecastCategory) set['forecastCategory'] = forecastCategory\n else clear.push('forecastCategory')\n if (options.amountDerived) {\n delete set['amountCents']\n return {\n set: { title, titleLower: nameSearchKey(title), ...set, updatedAt: new Date(nowMs) },\n clear: clear.filter((key) => key !== 'amountCents'),\n }\n }\n return {\n set: {\n title,\n titleLower: nameSearchKey(title),\n currency: String(values.currency || DEFAULT_DEAL_CURRENCY).toLowerCase(),\n ...set,\n updatedAt: new Date(nowMs),\n },\n clear,\n }\n}\n\n/** A contact as the picker offers it. */\nexport interface ContactChoice {\n id: string\n name: string\n email: string\n}\n\n/**\n * The contacts a typed query matches, from a window of recent contact rows,\n * in the list's own grammar.\n *\n * In memory rather than by query because the contacts collection has no\n * composite index over `visibleTo` with `nameLower` or `email`, and a\n * scoped list can carry only one array clause — so the picker reads the\n * newest rows the viewer may see (bounded, ordered) and matches here. The\n * match is what the list's search does: the normalized query as a prefix of\n * the email, or as a prefix of any word of the name (`nameTokens` holds\n * every word prefix a write produced; `nameLower` covers rows written before\n * the tokens existed). The holder's own name for the person wins over the\n * canonical one, as everywhere else.\n */\nexport function contactChoicesFor(\n queryText: string,\n rows: readonly Record<string, unknown>[],\n /**\n * The holder each row is named through: one group for every row under a\n * site, or a resolver per row at the organization level (AGL-2630), where\n * each contact reads through its own primary holder.\n */\n groupId: string | ((row: Record<string, unknown>) => string),\n max = 8,\n): ContactChoice[] {\n const key = nameSearchKey(queryText)\n const token = nameSearchToken(queryText)\n const choices: ContactChoice[] = []\n for (const row of rows) {\n const email = String(row['email'] ?? '').toLowerCase()\n const name = contactDisplayName(\n row,\n typeof groupId === 'function' ? groupId(row) : groupId,\n )\n const nameLower = nameSearchKey(name)\n const tokens = Array.isArray(row['nameTokens'])\n ? (row['nameTokens'] as unknown[]).map(String)\n : []\n const matches =\n !key ||\n email.startsWith(key) ||\n nameLower.startsWith(key) ||\n nameLower.includes(` ${key}`) ||\n (token ? tokens.includes(token) : false)\n if (!matches) continue\n choices.push({ id: String(row['$id'] ?? ''), name, email })\n if (choices.length >= max) break\n }\n return choices\n}\n"],"names":["contactDisplayName","crmPicklistDefaultLabel","DEAL_NEXT_STEP_MAX","dealContactRolesOf","dealContactRolesWithPrimary","dealStageForecastCategory","isCrmForecastCategory","judgeCrmPicklistValue","nameSearchKey","nameSearchToken","readDealProbability","amountInputValue","dateInputMs","dateInputValue","DEFAULT_DEAL_CURRENCY","openStages","parseAmountInput","DEAL_TITLE_MAX","DEAL_NOTES_MAX","emptyDealForm","pipeline","defaults","firstOpen","title","pipelineId","$id","stageId","id","amount","currency","expectedClose","ownerUid","contactId","contactName","companyId","companyName","notes","type","leadSource","nextStep","probability","forecastCategory","campaignId","dealFormFromDoc","deal","String","amountCents","toLowerCase","expectedCloseAtMs","dealFormProblem","values","mode","trim","undefined","judgeDealFormPicklists","lists","options","typed","created","current","ok","value","forecastCategoryFor","stage","optionalFields","set","clear","put","key","push","slice","replace","dealDocumentFromForm","context","titleLower","status","contactRoles","primary","stageChangedAtMs","nowMs","visibleTo","hostId","createdByUid","uid","createdAt","Date","updatedAt","dealPatchFromForm","optional","amountDerived","filter","contactChoicesFor","queryText","rows","groupId","max","token","choices","row","email","name","nameLower","tokens","Array","isArray","map","matches","startsWith","includes","length"],"mappings":"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"}
@@ -0,0 +1,23 @@
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
+ * Hand the browser a file to save.
19
+ *
20
+ * It touches `URL.createObjectURL` and `document`, so it belongs to the
21
+ * browser and never to a module a server route imports.
22
+ */
23
+ export declare function downloadTextFile(name: string, mime: string, text: string): void;
@@ -0,0 +1,34 @@
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
+ * Hand the browser a file to save.
18
+ *
19
+ * It touches `URL.createObjectURL` and `document`, so it belongs to the
20
+ * browser and never to a module a server route imports.
21
+ */ export function downloadTextFile(name, mime, text) {
22
+ const url = URL.createObjectURL(new Blob([
23
+ text
24
+ ], {
25
+ type: mime
26
+ }));
27
+ const anchor = document.createElement('a');
28
+ anchor.href = url;
29
+ anchor.download = name;
30
+ anchor.click();
31
+ URL.revokeObjectURL(url);
32
+ }
33
+
34
+ //# sourceMappingURL=download-text-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/download-text-file.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 * Hand the browser a file to save.\n *\n * It touches `URL.createObjectURL` and `document`, so it belongs to the\n * browser and never to a module a server route imports.\n */\nexport function downloadTextFile(name: string, mime: string, text: string): void {\n const url = URL.createObjectURL(new Blob([text], { type: mime }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = name\n anchor.click()\n URL.revokeObjectURL(url)\n}\n"],"names":["downloadTextFile","name","mime","text","url","URL","createObjectURL","Blob","type","anchor","document","createElement","href","download","click","revokeObjectURL"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;CAKC,GACD,OAAO,SAASA,iBAAiBC,IAAY,EAAEC,IAAY,EAAEC,IAAY;IACvE,MAAMC,MAAMC,IAAIC,eAAe,CAAC,IAAIC,KAAK;QAACJ;KAAK,EAAE;QAAEK,MAAMN;IAAK;IAC9D,MAAMO,SAASC,SAASC,aAAa,CAAC;IACtCF,OAAOG,IAAI,GAAGR;IACdK,OAAOI,QAAQ,GAAGZ;IAClBQ,OAAOK,KAAK;IACZT,IAAIU,eAAe,CAACX;AACtB"}
@@ -14,11 +14,11 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type EmailStateStatus, type CrmLeadStatus } from '@aglyn/aglyn';
17
+ import { type EmailStateStatus, type CrmLeadStatus, type CrmPicklist } from '@aglyn/aglyn';
18
18
  import type { CrmViewFilterClause } from '@aglyn/aglyn';
19
19
  import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter';
20
20
  import { type ListGridFilterCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
21
- import type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
21
+ import { type ListQueryDeclaration, type ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
22
22
  import { type CrmClauseAsked } from './crm-list-query';
23
23
  /**
24
24
  * What the Leads section's `Show` control offers (AGL-2608): the two
@@ -49,6 +49,20 @@ export declare const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string>;
49
49
  export declare const LEAD_SOURCE_FILTER_NONE = "\0none";
50
50
  /** The fields the grid's panel offers on Leads. */
51
51
  export declare const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[];
52
+ /**
53
+ * The lead picklist columns the list filters by (AGL-3513), each with the
54
+ * list its choices come from: a choice's value is the KEY the query
55
+ * compares, its caption the label.
56
+ */
57
+ export declare const LEAD_PICKLIST_FILTERS: readonly [{
58
+ readonly column: "industry";
59
+ readonly picklistId: "industry";
60
+ readonly header: "Industry";
61
+ }, {
62
+ readonly column: "rating";
63
+ readonly picklistId: "rating";
64
+ readonly header: "Rating";
65
+ }];
52
66
  /** What each filter field reads as on a chip. */
53
67
  export declare const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>>;
54
68
  /** The choices for Status: Open (new, nurturing or working) and each status on its own. */
@@ -61,6 +75,21 @@ export declare const LEAD_EMAIL_FILTER_OPTIONS: {
61
75
  value: "none" | "ok" | "bounced" | "complained" | "blocked" | "undeliverable" | "unsubscribed" | "do_not_contact" | "problem";
62
76
  label: string;
63
77
  }[];
78
+ /** The choices for Lead source direction: each group of the Lead source picklist. */
79
+ export declare const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS: {
80
+ value: import("./lead-source-direction").LeadSourceDirection;
81
+ label: string;
82
+ }[];
83
+ /**
84
+ * The Status choices as an org reads them (AGL-3512): still one choice per
85
+ * MEANING — the query asks `status` — each named by the org's values of that
86
+ * meaning, so a list that keeps "Contacted" and "Meeting set" as Working
87
+ * reads "Working: Contacted, Meeting set".
88
+ */
89
+ export declare function leadStatusFilterOptions(picklist: CrmPicklist): Array<{
90
+ value: string;
91
+ label: string;
92
+ }>;
64
93
  /**
65
94
  * The clauses the grid shows for a view's stored ones. A stored list with
66
95
  * no status clause is Open, which the list opened on before views existed,
@@ -105,6 +134,11 @@ export interface LeadQueryReader {
105
134
  scopeTokens: readonly string[] | null;
106
135
  /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */
107
136
  foldsScope: boolean;
137
+ /**
138
+ * The org's Lead source list, which a direction is expanded through;
139
+ * the standard values alone while it has not been read.
140
+ */
141
+ leadSources?: CrmPicklist;
108
142
  }
109
143
  /**
110
144
  * One grid clause as the clause the Leads query asks — see the block above.
@@ -14,10 +14,12 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, CRM_LEAD_STATUS_LABELS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_SCOPED_CAMPAIGNS_FIELD, CRM_LEAD_STATUSES, crmLeadSourceKey } from "@aglyn/aglyn";
17
+ */ import { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, CRM_LEAD_STATUS_LABELS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_SCOPED_CAMPAIGNS_FIELD, CRM_LEAD_STATUSES, crmLeadSourceKey, crmPicklistKey } from "@aglyn/aglyn";
18
18
  import { SCOPED_SEARCH_JOIN } from "@aglyn/aglyn/app-utils/name-search";
19
19
  import { listSelectCodec } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
20
+ import { LIST_QUERY_DISJUNCTIONS } from "@aglyn/shared-ui-jsx/const/list-query-plan";
20
21
  import { CRM_LIST_SEARCH, crmAnyOf, crmClauseValues, crmSelectField } from "./crm-list-query.js";
22
+ import { isLeadSourceDirection, LEAD_SOURCE_DIRECTION_LABELS, LEAD_SOURCE_DIRECTIONS, leadSourceLabelsOfDirection, STANDARD_LEAD_SOURCES } from "./lead-source-direction.js";
21
23
  export const LEAD_FILTERS = [
22
24
  'open',
23
25
  'new',
@@ -63,6 +65,10 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
63
65
  * emailState `equals <filter>`
64
66
  * campaignIds `contains <campaign id>`
65
67
  * leadSource `equals <label>`, or `isEmpty` for the leads holding none
68
+ * leadSourceDirection `equals inbound|outbound` (AGL-3511): every lead
69
+ * whose lead source is a value of that group
70
+ * industry, rating `equals <key>` (AGL-3513): a value of the org's
71
+ * Industry or Rating list, by the key the query compares
66
72
  *
67
73
  * Every one is asked of the Leads query (AGL-3321) through the field a
68
74
  * writer stores for it — see {@link leadQueryClause}.
@@ -103,6 +109,15 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
103
109
  'isAnyOf'
104
110
  ]
105
111
  },
112
+ {
113
+ column: 'leadSourceDirection',
114
+ kind: 'exact',
115
+ path: 'leadSourceDirection',
116
+ operators: [
117
+ 'equals',
118
+ 'isAnyOf'
119
+ ]
120
+ },
106
121
  {
107
122
  column: 'campaignIds',
108
123
  kind: 'exact',
@@ -110,6 +125,41 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
110
125
  operators: [
111
126
  'equals'
112
127
  ]
128
+ },
129
+ // Salesforce's Industry and Rating (AGL-3513), the lists companies keep.
130
+ {
131
+ column: 'industry',
132
+ kind: 'exact',
133
+ path: 'industry',
134
+ operators: [
135
+ 'equals',
136
+ 'isAnyOf'
137
+ ]
138
+ },
139
+ {
140
+ column: 'rating',
141
+ kind: 'exact',
142
+ path: 'rating',
143
+ operators: [
144
+ 'equals',
145
+ 'isAnyOf'
146
+ ]
147
+ }
148
+ ];
149
+ /**
150
+ * The lead picklist columns the list filters by (AGL-3513), each with the
151
+ * list its choices come from: a choice's value is the KEY the query
152
+ * compares, its caption the label.
153
+ */ export const LEAD_PICKLIST_FILTERS = [
154
+ {
155
+ column: 'industry',
156
+ picklistId: 'industry',
157
+ header: 'Industry'
158
+ },
159
+ {
160
+ column: 'rating',
161
+ picklistId: 'rating',
162
+ header: 'Rating'
113
163
  }
114
164
  ];
115
165
  /** What each filter field reads as on a chip. */ export const LEAD_LIST_FILTER_HEADERS = {
@@ -117,7 +167,10 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
117
167
  emailState: 'Email',
118
168
  ownerUid: 'Owner',
119
169
  leadSource: 'Lead source',
120
- campaignIds: 'Campaign'
170
+ leadSourceDirection: 'Lead source direction',
171
+ campaignIds: 'Campaign',
172
+ industry: 'Industry',
173
+ rating: 'Rating'
121
174
  };
122
175
  /** The choices for Status: Open (new, nurturing or working) and each status on its own. */ export const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter((option)=>option !== 'all').map((option)=>({
123
176
  value: option,
@@ -127,6 +180,29 @@ export const LEAD_EMAIL_FILTER_LABELS = _extends({
127
180
  value: option,
128
181
  label: LEAD_EMAIL_FILTER_LABELS[option]
129
182
  }));
183
+ /** The choices for Lead source direction: each group of the Lead source picklist. */ export const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS = LEAD_SOURCE_DIRECTIONS.map((direction)=>({
184
+ value: direction,
185
+ label: LEAD_SOURCE_DIRECTION_LABELS[direction]
186
+ }));
187
+ /**
188
+ * The Status choices as an org reads them (AGL-3512): still one choice per
189
+ * MEANING — the query asks `status` — each named by the org's values of that
190
+ * meaning, so a list that keeps "Contacted" and "Meeting set" as Working
191
+ * reads "Working: Contacted, Meeting set".
192
+ */ export function leadStatusFilterOptions(picklist) {
193
+ const named = (status)=>{
194
+ const labels = picklist.values.filter((value)=>value.meaning === status && value.active).map((value)=>value.label);
195
+ const meaning = picklistMeaningName(status);
196
+ if (!labels.length) return meaning;
197
+ if (labels.length === 1 && labels[0] === meaning) return meaning;
198
+ return `${meaning}: ${labels.join(', ')}`;
199
+ };
200
+ return LEAD_FILTERS.filter((option)=>option !== 'all').map((option)=>({
201
+ value: option,
202
+ label: option === 'open' ? 'Open (new, nurturing or working)' : named(option)
203
+ }));
204
+ }
205
+ const picklistMeaningName = (status)=>CRM_LEAD_STATUS_LABELS[status];
130
206
  const STATUS_OPEN = {
131
207
  field: 'status',
132
208
  op: 'equals',
@@ -214,7 +290,15 @@ const STATUS_ALL = {
214
290
  * emailed" is every verdict but `ok`
215
291
  * leadSource `leadSourceKey`, the label as the picklist compares it, or
216
292
  * `null` for none
293
+ * leadSourceDirection `leadSourceKey in [...]` over the key of every
294
+ * value the org's list files under that group, read when
295
+ * the clause is asked — a direction is never stored on a
296
+ * lead, so regrouping a value moves its leads with it. One
297
+ * `in` holds thirty keys, and a group holding more is
298
+ * refused by name rather than half asked
217
299
  * ownerUid as stored
300
+ * industry, rating `industryKey`, `ratingKey` (AGL-3513), each label
301
+ * as the picklist compares it
218
302
  * campaignIds `array-contains` on the lead's own campaigns; under a site,
219
303
  * `scopedCampaignIds` — the campaign behind each of the
220
304
  * site's scope tokens — which stands in the scope clause's
@@ -256,6 +340,9 @@ const STATUS_ALL = {
256
340
  'contains'
257
341
  ]
258
342
  },
343
+ // Industry and Rating (AGL-3513), by the key each writer stores.
344
+ crmSelectField('industryKey'),
345
+ crmSelectField('ratingKey'),
259
346
  {
260
347
  // A campaign under a site: the lead's campaigns behind its scope.
261
348
  column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,
@@ -344,8 +431,42 @@ const ORG_READER = {
344
431
  const keys = values.map((value)=>crmLeadSourceKey(value)).filter((key)=>Boolean(key));
345
432
  return crmAnyOf('leadSourceKey', keys);
346
433
  }
434
+ case 'leadSourceDirection':
435
+ {
436
+ var _reader_leadSources;
437
+ const picklist = (_reader_leadSources = reader.leadSources) != null ? _reader_leadSources : STANDARD_LEAD_SOURCES;
438
+ const keys = [];
439
+ for (const value of values){
440
+ if (!isLeadSourceDirection(value)) return {
441
+ refused: `${value} is not a lead source direction`
442
+ };
443
+ for (const label of leadSourceLabelsOfDirection(picklist, value)){
444
+ const key = crmLeadSourceKey(label);
445
+ if (key) keys.push(key);
446
+ }
447
+ }
448
+ const unique = [
449
+ ...new Set(keys)
450
+ ];
451
+ if (!unique.length) return {
452
+ refused: 'no lead source is in that direction yet'
453
+ };
454
+ if (unique.length > LIST_QUERY_DISJUNCTIONS) {
455
+ return {
456
+ refused: `more than ${LIST_QUERY_DISJUNCTIONS} lead sources are in that direction — filter by Lead source instead`
457
+ };
458
+ }
459
+ return crmAnyOf('leadSourceKey', unique);
460
+ }
347
461
  case 'ownerUid':
348
462
  return crmAnyOf('ownerUid', values);
463
+ case 'industry':
464
+ case 'rating':
465
+ {
466
+ // A choice is the key; a label typed into a stored view keys the same way.
467
+ const keys = values.map((value)=>crmPicklistKey(value)).filter((key)=>Boolean(key));
468
+ return crmAnyOf(clause.field === 'industry' ? 'industryKey' : 'ratingKey', keys);
469
+ }
349
470
  case 'campaignIds':
350
471
  {
351
472
  if (values.length !== 1) return {