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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/README.md +2 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/activity-list.js +12 -5
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/companies-bulk-bar.d.ts +1 -4
  6. package/src/lib/components/companies-bulk-bar.js +7 -20
  7. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  8. package/src/lib/components/companies-section.d.ts +3 -3
  9. package/src/lib/components/companies-section.js +149 -46
  10. package/src/lib/components/companies-section.js.map +1 -1
  11. package/src/lib/components/company-edit-drawer.js +281 -20
  12. package/src/lib/components/company-edit-drawer.js.map +1 -1
  13. package/src/lib/components/company-properties-card.js +99 -8
  14. package/src/lib/components/company-properties-card.js.map +1 -1
  15. package/src/lib/components/contact-detail-page.js +3 -1
  16. package/src/lib/components/contact-detail-page.js.map +1 -1
  17. package/src/lib/components/contact-merge-dialog.js +33 -10
  18. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  19. package/src/lib/components/contact-properties-card.d.ts +0 -69
  20. package/src/lib/components/contact-properties-card.js +350 -60
  21. package/src/lib/components/contact-properties-card.js.map +1 -1
  22. package/src/lib/components/contact-reports-to-field.d.ts +46 -0
  23. package/src/lib/components/contact-reports-to-field.js +67 -0
  24. package/src/lib/components/contact-reports-to-field.js.map +1 -0
  25. package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
  26. package/src/lib/components/contacts-bulk-bar.js +7 -19
  27. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  28. package/src/lib/components/contacts-section.js +46 -33
  29. package/src/lib/components/contacts-section.js.map +1 -1
  30. package/src/lib/components/crm-call-actions.d.ts +10 -0
  31. package/src/lib/components/crm-call-actions.js +26 -9
  32. package/src/lib/components/crm-call-actions.js.map +1 -1
  33. package/src/lib/components/crm-record-picker.d.ts +15 -0
  34. package/src/lib/components/crm-record-picker.js +16 -10
  35. package/src/lib/components/crm-record-picker.js.map +1 -1
  36. package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
  37. package/src/lib/components/crm-transfer-buttons.js +167 -0
  38. package/src/lib/components/crm-transfer-buttons.js.map +1 -0
  39. package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
  40. package/src/lib/components/deal-contact-roles-card.js +368 -0
  41. package/src/lib/components/deal-contact-roles-card.js.map +1 -0
  42. package/src/lib/components/deal-detail-page.d.ts +3 -2
  43. package/src/lib/components/deal-detail-page.js +25 -6
  44. package/src/lib/components/deal-detail-page.js.map +1 -1
  45. package/src/lib/components/deal-edit-drawer.d.ts +10 -0
  46. package/src/lib/components/deal-edit-drawer.js +219 -10
  47. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  48. package/src/lib/components/deal-properties-card.d.ts +7 -0
  49. package/src/lib/components/deal-properties-card.js +34 -3
  50. package/src/lib/components/deal-properties-card.js.map +1 -1
  51. package/src/lib/components/deal-stage-card.d.ts +4 -0
  52. package/src/lib/components/deal-stage-card.js +10 -2
  53. package/src/lib/components/deal-stage-card.js.map +1 -1
  54. package/src/lib/components/deals-bulk-bar.d.ts +0 -3
  55. package/src/lib/components/deals-bulk-bar.js +7 -20
  56. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  57. package/src/lib/components/deals-section.d.ts +3 -3
  58. package/src/lib/components/deals-section.js +123 -63
  59. package/src/lib/components/deals-section.js.map +1 -1
  60. package/src/lib/components/fields-section.js +31 -17
  61. package/src/lib/components/fields-section.js.map +1 -1
  62. package/src/lib/components/lead-convert-dialog.d.ts +4 -0
  63. package/src/lib/components/lead-convert-dialog.js +23 -1
  64. package/src/lib/components/lead-convert-dialog.js.map +1 -1
  65. package/src/lib/components/lead-detail-page.js +8 -2
  66. package/src/lib/components/lead-detail-page.js.map +1 -1
  67. package/src/lib/components/lead-profile-fields.d.ts +71 -0
  68. package/src/lib/components/lead-profile-fields.js +386 -0
  69. package/src/lib/components/lead-profile-fields.js.map +1 -0
  70. package/src/lib/components/lead-properties-card.d.ts +2 -1
  71. package/src/lib/components/lead-properties-card.js +85 -176
  72. package/src/lib/components/lead-properties-card.js.map +1 -1
  73. package/src/lib/components/lead-source-select.d.ts +4 -23
  74. package/src/lib/components/lead-source-select.js +9 -73
  75. package/src/lib/components/lead-source-select.js.map +1 -1
  76. package/src/lib/components/lead-status-chip.d.ts +9 -3
  77. package/src/lib/components/lead-status-chip.js +8 -4
  78. package/src/lib/components/lead-status-chip.js.map +1 -1
  79. package/src/lib/components/lead-status-options.d.ts +20 -0
  80. package/src/lib/components/lead-status-options.js +51 -0
  81. package/src/lib/components/lead-status-options.js.map +1 -0
  82. package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
  83. package/src/lib/components/lead-unqualify-dialog.js +50 -18
  84. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  85. package/src/lib/components/leads-bulk-bar.d.ts +0 -3
  86. package/src/lib/components/leads-bulk-bar.js +29 -29
  87. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  88. package/src/lib/components/leads-section.js +120 -68
  89. package/src/lib/components/leads-section.js.map +1 -1
  90. package/src/lib/components/linked-deals-card.d.ts +4 -0
  91. package/src/lib/components/linked-deals-card.js +47 -3
  92. package/src/lib/components/linked-deals-card.js.map +1 -1
  93. package/src/lib/components/log-activity-dialog.js +43 -3
  94. package/src/lib/components/log-activity-dialog.js.map +1 -1
  95. package/src/lib/components/new-contact-drawer.d.ts +8 -0
  96. package/src/lib/components/new-contact-drawer.js +108 -6
  97. package/src/lib/components/new-contact-drawer.js.map +1 -1
  98. package/src/lib/components/new-lead-drawer.d.ts +9 -2
  99. package/src/lib/components/new-lead-drawer.js +88 -121
  100. package/src/lib/components/new-lead-drawer.js.map +1 -1
  101. package/src/lib/components/picklist-select.d.ts +36 -0
  102. package/src/lib/components/picklist-select.js +114 -0
  103. package/src/lib/components/picklist-select.js.map +1 -0
  104. package/src/lib/components/picklist-values-card.d.ts +34 -0
  105. package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
  106. package/src/lib/components/picklist-values-card.js.map +1 -0
  107. package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
  108. package/src/lib/components/pipeline-stages-dialog.js +26 -4
  109. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  110. package/src/lib/components/pipelines-dialog.js +15 -6
  111. package/src/lib/components/pipelines-dialog.js.map +1 -1
  112. package/src/lib/components/recent-activity-feed.js +20 -5
  113. package/src/lib/components/recent-activity-feed.js.map +1 -1
  114. package/src/lib/components/recipes-card.js +4 -2
  115. package/src/lib/components/recipes-card.js.map +1 -1
  116. package/src/lib/components/record-tasks-card.js +15 -8
  117. package/src/lib/components/record-tasks-card.js.map +1 -1
  118. package/src/lib/components/reports/forecast-card.d.ts +6 -0
  119. package/src/lib/components/reports/forecast-card.js +101 -3
  120. package/src/lib/components/reports/forecast-card.js.map +1 -1
  121. package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
  122. package/src/lib/components/reports/lead-funnel-card.js +9 -5
  123. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  124. package/src/lib/components/reports/lead-source-card.d.ts +23 -0
  125. package/src/lib/components/reports/lead-source-card.js +318 -0
  126. package/src/lib/components/reports/lead-source-card.js.map +1 -0
  127. package/src/lib/components/reports/report-export.js +1 -1
  128. package/src/lib/components/reports/report-export.js.map +1 -1
  129. package/src/lib/components/reports-section.js +8 -0
  130. package/src/lib/components/reports-section.js.map +1 -1
  131. package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
  132. package/src/lib/components/sharing-rule-drawer.js +108 -19
  133. package/src/lib/components/sharing-rule-drawer.js.map +1 -1
  134. package/src/lib/components/sharing-rules-card.js +11 -3
  135. package/src/lib/components/sharing-rules-card.js.map +1 -1
  136. package/src/lib/components/task-cells.d.ts +9 -1
  137. package/src/lib/components/task-cells.js +9 -4
  138. package/src/lib/components/task-cells.js.map +1 -1
  139. package/src/lib/components/task-edit-drawer.js +111 -43
  140. package/src/lib/components/task-edit-drawer.js.map +1 -1
  141. package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
  142. package/src/lib/components/tasks-bulk-bar.js +7 -20
  143. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
  144. package/src/lib/components/tasks-calendar.d.ts +3 -0
  145. package/src/lib/components/tasks-calendar.js +8 -1
  146. package/src/lib/components/tasks-calendar.js.map +1 -1
  147. package/src/lib/components/tasks-section.d.ts +3 -3
  148. package/src/lib/components/tasks-section.js +58 -43
  149. package/src/lib/components/tasks-section.js.map +1 -1
  150. package/src/lib/constants/api-routes.d.ts +2 -9
  151. package/src/lib/constants/api-routes.js +1 -7
  152. package/src/lib/constants/api-routes.js.map +1 -1
  153. package/src/lib/constants/bundle-common.d.ts +6 -0
  154. package/src/lib/constants/bundle-common.js +11 -0
  155. package/src/lib/constants/bundle-common.js.map +1 -1
  156. package/src/lib/constants/company-filters.d.ts +14 -0
  157. package/src/lib/constants/company-filters.js +51 -1
  158. package/src/lib/constants/company-filters.js.map +1 -1
  159. package/src/lib/constants/contact-filters.d.ts +2 -0
  160. package/src/lib/constants/contact-filters.js +18 -1
  161. package/src/lib/constants/contact-filters.js.map +1 -1
  162. package/src/lib/constants/deal-filters.d.ts +22 -1
  163. package/src/lib/constants/deal-filters.js +67 -4
  164. package/src/lib/constants/deal-filters.js.map +1 -1
  165. package/src/lib/declarations.console-server.d.ts +3 -0
  166. package/src/lib/declarations.console-server.js +35 -0
  167. package/src/lib/declarations.console-server.js.map +1 -1
  168. package/src/lib/declarations.d.ts +26 -0
  169. package/src/lib/declarations.js +35 -0
  170. package/src/lib/declarations.js.map +1 -0
  171. package/src/lib/declarations.server.d.ts +11 -0
  172. package/src/lib/declarations.server.js +92 -1
  173. package/src/lib/declarations.server.js.map +1 -1
  174. package/src/lib/hooks/use-company-picklists.d.ts +15 -0
  175. package/src/lib/hooks/use-company-picklists.js +55 -0
  176. package/src/lib/hooks/use-company-picklists.js.map +1 -0
  177. package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
  178. package/src/lib/hooks/use-crm-picklist.js +53 -0
  179. package/src/lib/hooks/use-crm-picklist.js.map +1 -0
  180. package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
  181. package/src/lib/hooks/use-crm-record-names.js +45 -19
  182. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  183. package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
  184. package/src/lib/hooks/use-crm-task-picklists.js +45 -0
  185. package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
  186. package/src/lib/hooks/use-deals.d.ts +14 -0
  187. package/src/lib/hooks/use-deals.js +29 -0
  188. package/src/lib/hooks/use-deals.js.map +1 -1
  189. package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
  190. package/src/lib/hooks/use-lead-picklists.js +46 -0
  191. package/src/lib/hooks/use-lead-picklists.js.map +1 -0
  192. package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
  193. package/src/lib/hooks/use-lead-source-picklist.js +12 -22
  194. package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
  195. package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
  196. package/src/lib/hooks/use-lead-status-picklist.js +40 -0
  197. package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
  198. package/src/lib/model/companies.d.ts +32 -3
  199. package/src/lib/model/companies.js +133 -16
  200. package/src/lib/model/companies.js.map +1 -1
  201. package/src/lib/model/contact-merge.d.ts +27 -3
  202. package/src/lib/model/contact-merge.js +81 -7
  203. package/src/lib/model/contact-merge.js.map +1 -1
  204. package/src/lib/model/contact-record.d.ts +17 -0
  205. package/src/lib/model/contact-record.js +15 -1
  206. package/src/lib/model/contact-record.js.map +1 -1
  207. package/src/lib/model/contact-update.d.ts +31 -2
  208. package/src/lib/model/contact-update.js +14 -0
  209. package/src/lib/model/contact-update.js.map +1 -1
  210. package/src/lib/model/crm-list-query.d.ts +5 -0
  211. package/src/lib/model/crm-list-query.js +8 -0
  212. package/src/lib/model/crm-list-query.js.map +1 -1
  213. package/src/lib/model/crm-recipes.d.ts +40 -0
  214. package/src/lib/model/crm-recipes.js +244 -0
  215. package/src/lib/model/crm-recipes.js.map +1 -0
  216. package/src/lib/model/crm-record-counts.d.ts +36 -0
  217. package/src/lib/model/crm-record-counts.js +54 -0
  218. package/src/lib/model/crm-record-counts.js.map +1 -0
  219. package/src/lib/model/crm-record-lists.d.ts +23 -0
  220. package/src/lib/model/crm-record-lists.js +161 -0
  221. package/src/lib/model/crm-record-lists.js.map +1 -0
  222. package/src/lib/model/crm-reports.d.ts +73 -3
  223. package/src/lib/model/crm-reports.js +120 -1
  224. package/src/lib/model/crm-reports.js.map +1 -1
  225. package/src/lib/model/crm-sharing.d.ts +21 -3
  226. package/src/lib/model/crm-sharing.js +27 -6
  227. package/src/lib/model/crm-sharing.js.map +1 -1
  228. package/src/lib/model/deal-board-model.d.ts +8 -2
  229. package/src/lib/model/deal-board-model.js +15 -2
  230. package/src/lib/model/deal-board-model.js.map +1 -1
  231. package/src/lib/model/deal-contact-roles.d.ts +45 -0
  232. package/src/lib/model/deal-contact-roles.js +73 -0
  233. package/src/lib/model/deal-contact-roles.js.map +1 -0
  234. package/src/lib/model/deal-form-model.d.ts +56 -0
  235. package/src/lib/model/deal-form-model.js +79 -7
  236. package/src/lib/model/deal-form-model.js.map +1 -1
  237. package/src/lib/model/download-text-file.d.ts +23 -0
  238. package/src/lib/model/download-text-file.js +34 -0
  239. package/src/lib/model/download-text-file.js.map +1 -0
  240. package/src/lib/model/lead-filters.d.ts +36 -2
  241. package/src/lib/model/lead-filters.js +123 -2
  242. package/src/lib/model/lead-filters.js.map +1 -1
  243. package/src/lib/model/lead-source-direction.d.ts +38 -0
  244. package/src/lib/model/lead-source-direction.js +52 -0
  245. package/src/lib/model/lead-source-direction.js.map +1 -0
  246. package/src/lib/model/picklist-values.d.ts +119 -0
  247. package/src/lib/model/picklist-values.js +256 -0
  248. package/src/lib/model/picklist-values.js.map +1 -0
  249. package/src/lib/model/record-facts-disclosed.d.ts +81 -0
  250. package/src/lib/model/record-facts-disclosed.js +281 -0
  251. package/src/lib/model/record-facts-disclosed.js.map +1 -0
  252. package/src/lib/model/record-facts.d.ts +215 -34
  253. package/src/lib/model/record-facts.js +307 -128
  254. package/src/lib/model/record-facts.js.map +1 -1
  255. package/src/lib/model/task-picklists.d.ts +52 -0
  256. package/src/lib/model/task-picklists.js +83 -0
  257. package/src/lib/model/task-picklists.js.map +1 -0
  258. package/src/lib/model/task-routes.d.ts +13 -0
  259. package/src/lib/model/task-routes.js +23 -4
  260. package/src/lib/model/task-routes.js.map +1 -1
  261. package/src/lib/plan-entitlements.js +6 -4
  262. package/src/lib/plan-entitlements.js.map +1 -1
  263. package/src/lib/plugin.js +17 -0
  264. package/src/lib/plugin.js.map +1 -1
  265. package/src/lib/server/api-v1/contacts.js +220 -14
  266. package/src/lib/server/api-v1/contacts.js.map +1 -1
  267. package/src/lib/server/api-v1/crm-activities.js +14 -1
  268. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  269. package/src/lib/server/api-v1/crm-companies.js +123 -14
  270. package/src/lib/server/api-v1/crm-companies.js.map +1 -1
  271. package/src/lib/server/api-v1/crm-deals.js +291 -26
  272. package/src/lib/server/api-v1/crm-deals.js.map +1 -1
  273. package/src/lib/server/api-v1/crm-leads.js +144 -38
  274. package/src/lib/server/api-v1/crm-leads.js.map +1 -1
  275. package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
  276. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  277. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  278. package/src/lib/server/api-v1/crm-tasks.js +75 -33
  279. package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
  280. package/src/lib/server/api-v1/openapi.d.ts +0 -11
  281. package/src/lib/server/api-v1/openapi.js +171 -16
  282. package/src/lib/server/api-v1/openapi.js.map +1 -1
  283. package/src/lib/server/assign-contact-owner.d.ts +162 -0
  284. package/src/lib/server/assign-contact-owner.js +340 -0
  285. package/src/lib/server/assign-contact-owner.js.map +1 -0
  286. package/src/lib/server/associate-company-by-domain.d.ts +70 -0
  287. package/src/lib/server/associate-company-by-domain.js +153 -0
  288. package/src/lib/server/associate-company-by-domain.js.map +1 -0
  289. package/src/lib/server/automation-steps.d.ts +139 -0
  290. package/src/lib/server/automation-steps.js +472 -0
  291. package/src/lib/server/automation-steps.js.map +1 -0
  292. package/src/lib/server/capture-contact.js +65 -21
  293. package/src/lib/server/capture-contact.js.map +1 -1
  294. package/src/lib/server/capture-host-contact.d.ts +104 -0
  295. package/src/lib/server/capture-host-contact.js +151 -0
  296. package/src/lib/server/capture-host-contact.js.map +1 -0
  297. package/src/lib/server/company-children.d.ts +25 -0
  298. package/src/lib/server/company-children.js +42 -0
  299. package/src/lib/server/company-children.js.map +1 -0
  300. package/src/lib/server/company-delete.d.ts +18 -0
  301. package/src/lib/server/company-delete.js +44 -21
  302. package/src/lib/server/company-delete.js.map +1 -1
  303. package/src/lib/server/contact-email-history.js +21 -19
  304. package/src/lib/server/contact-email-history.js.map +1 -1
  305. package/src/lib/server/contact-remove.js +27 -2
  306. package/src/lib/server/contact-remove.js.map +1 -1
  307. package/src/lib/server/contact-reports-to.d.ts +61 -0
  308. package/src/lib/server/contact-reports-to.js +114 -0
  309. package/src/lib/server/contact-reports-to.js.map +1 -0
  310. package/src/lib/server/contact-update.d.ts +42 -1
  311. package/src/lib/server/contact-update.js +204 -23
  312. package/src/lib/server/contact-update.js.map +1 -1
  313. package/src/lib/server/convert-host-lead.d.ts +46 -1
  314. package/src/lib/server/convert-host-lead.js +173 -15
  315. package/src/lib/server/convert-host-lead.js.map +1 -1
  316. package/src/lib/server/convert-open-lead.js +12 -5
  317. package/src/lib/server/convert-open-lead.js.map +1 -1
  318. package/src/lib/server/crm-record-indexes.d.ts +45 -0
  319. package/src/lib/server/crm-record-indexes.js +78 -0
  320. package/src/lib/server/crm-record-indexes.js.map +1 -0
  321. package/src/lib/server/crm-sharing.d.ts +9 -1
  322. package/src/lib/server/crm-sharing.js +23 -3
  323. package/src/lib/server/crm-sharing.js.map +1 -1
  324. package/src/lib/server/deal-contact-roles.d.ts +27 -0
  325. package/src/lib/server/deal-contact-roles.js +89 -0
  326. package/src/lib/server/deal-contact-roles.js.map +1 -0
  327. package/src/lib/server/lead-convert.d.ts +5 -0
  328. package/src/lib/server/lead-convert.js +19 -3
  329. package/src/lib/server/lead-convert.js.map +1 -1
  330. package/src/lib/server/lead-create.d.ts +17 -2
  331. package/src/lib/server/lead-create.js +46 -14
  332. package/src/lib/server/lead-create.js.map +1 -1
  333. package/src/lib/server/lead-nurturing.d.ts +6 -0
  334. package/src/lib/server/lead-nurturing.js +57 -20
  335. package/src/lib/server/lead-nurturing.js.map +1 -1
  336. package/src/lib/server/lead-picklists.d.ts +35 -0
  337. package/src/lib/server/lead-picklists.js +52 -0
  338. package/src/lib/server/lead-picklists.js.map +1 -0
  339. package/src/lib/server/lead-source-picklist.d.ts +1 -13
  340. package/src/lib/server/lead-source-picklist.js +6 -33
  341. package/src/lib/server/lead-source-picklist.js.map +1 -1
  342. package/src/lib/server/merge-contacts.js +22 -4
  343. package/src/lib/server/merge-contacts.js.map +1 -1
  344. package/src/lib/server/person-eraser.d.ts +53 -0
  345. package/src/lib/server/person-eraser.js +149 -0
  346. package/src/lib/server/person-eraser.js.map +1 -0
  347. package/src/lib/server/person-records.d.ts +69 -0
  348. package/src/lib/server/person-records.js +317 -0
  349. package/src/lib/server/person-records.js.map +1 -0
  350. package/src/lib/server/person-refund.d.ts +97 -0
  351. package/src/lib/server/person-refund.js +221 -0
  352. package/src/lib/server/person-refund.js.map +1 -0
  353. package/src/lib/server/picklist-values.d.ts +21 -0
  354. package/src/lib/server/picklist-values.js +308 -0
  355. package/src/lib/server/picklist-values.js.map +1 -0
  356. package/src/lib/server/read-picklist.d.ts +44 -0
  357. package/src/lib/server/read-picklist.js +65 -0
  358. package/src/lib/server/read-picklist.js.map +1 -0
  359. package/src/lib/server/recipe-routes.js +15 -7
  360. package/src/lib/server/recipe-routes.js.map +1 -1
  361. package/src/lib/server/record-email-state.js +7 -1
  362. package/src/lib/server/record-email-state.js.map +1 -1
  363. package/src/lib/server/record-facts.d.ts +3 -0
  364. package/src/lib/server/record-facts.js +233 -31
  365. package/src/lib/server/record-facts.js.map +1 -1
  366. package/src/lib/server/record-origin.d.ts +27 -0
  367. package/src/lib/server/record-origin.js +104 -0
  368. package/src/lib/server/record-origin.js.map +1 -0
  369. package/src/lib/server/record-timeline.js +15 -2
  370. package/src/lib/server/record-timeline.js.map +1 -1
  371. package/src/lib/server/suite-gate.js +4 -3
  372. package/src/lib/server/suite-gate.js.map +1 -1
  373. package/src/lib/server/task-routes.js +30 -16
  374. package/src/lib/server/task-routes.js.map +1 -1
  375. package/src/lib/server-deal-stage.d.ts +5 -1
  376. package/src/lib/server-deal-stage.js +10 -5
  377. package/src/lib/server-deal-stage.js.map +1 -1
  378. package/src/lib/server.d.ts +8 -3
  379. package/src/lib/server.js +47 -43
  380. package/src/lib/server.js.map +1 -1
  381. package/src/lib/testing/memory-firestore.d.ts +55 -0
  382. package/src/lib/testing/memory-firestore.js +335 -0
  383. package/src/lib/testing/memory-firestore.js.map +1 -0
  384. package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
  385. package/src/lib/testing/stand-in-email-send-index.js +49 -0
  386. package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
  387. package/src/lib/transfer/activities.d.ts +30 -0
  388. package/src/lib/transfer/activities.js +132 -0
  389. package/src/lib/transfer/activities.js.map +1 -0
  390. package/src/lib/transfer/aliases.d.ts +22 -0
  391. package/src/lib/transfer/aliases.js +1342 -0
  392. package/src/lib/transfer/aliases.js.map +1 -0
  393. package/src/lib/transfer/common.d.ts +222 -0
  394. package/src/lib/transfer/common.js +832 -0
  395. package/src/lib/transfer/common.js.map +1 -0
  396. package/src/lib/transfer/companies.d.ts +43 -0
  397. package/src/lib/transfer/companies.js +549 -0
  398. package/src/lib/transfer/companies.js.map +1 -0
  399. package/src/lib/transfer/contacts.d.ts +31 -0
  400. package/src/lib/transfer/contacts.js +727 -0
  401. package/src/lib/transfer/contacts.js.map +1 -0
  402. package/src/lib/transfer/deals.d.ts +66 -0
  403. package/src/lib/transfer/deals.js +698 -0
  404. package/src/lib/transfer/deals.js.map +1 -0
  405. package/src/lib/transfer/email-templates-package.d.ts +40 -0
  406. package/src/lib/transfer/email-templates-package.js +76 -0
  407. package/src/lib/transfer/email-templates-package.js.map +1 -0
  408. package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
  409. package/src/lib/transfer/email-templates-package.server.js +176 -0
  410. package/src/lib/transfer/email-templates-package.server.js.map +1 -0
  411. package/src/lib/transfer/fields.d.ts +102 -0
  412. package/src/lib/transfer/fields.js +1755 -0
  413. package/src/lib/transfer/fields.js.map +1 -0
  414. package/src/lib/transfer/leads.d.ts +30 -0
  415. package/src/lib/transfer/leads.js +582 -0
  416. package/src/lib/transfer/leads.js.map +1 -0
  417. package/src/lib/transfer/pipelines.d.ts +21 -0
  418. package/src/lib/transfer/pipelines.js +172 -0
  419. package/src/lib/transfer/pipelines.js.map +1 -0
  420. package/src/lib/transfer/register.d.ts +47 -0
  421. package/src/lib/transfer/register.js +277 -0
  422. package/src/lib/transfer/register.js.map +1 -0
  423. package/src/lib/transfer/tasks.d.ts +30 -0
  424. package/src/lib/transfer/tasks.js +418 -0
  425. package/src/lib/transfer/tasks.js.map +1 -0
  426. package/src/lib/transfer/ui.d.ts +20 -0
  427. package/src/lib/transfer/ui.js +42 -0
  428. package/src/lib/transfer/ui.js.map +1 -0
  429. package/src/lib/components/company-import-drawer.d.ts +0 -42
  430. package/src/lib/components/company-import-drawer.js +0 -99
  431. package/src/lib/components/company-import-drawer.js.map +0 -1
  432. package/src/lib/components/contact-import-drawer.d.ts +0 -46
  433. package/src/lib/components/contact-import-drawer.js +0 -149
  434. package/src/lib/components/contact-import-drawer.js.map +0 -1
  435. package/src/lib/components/crm-export-all-button.d.ts +0 -39
  436. package/src/lib/components/crm-export-all-button.js +0 -98
  437. package/src/lib/components/crm-export-all-button.js.map +0 -1
  438. package/src/lib/components/csv-import-drawer.d.ts +0 -139
  439. package/src/lib/components/csv-import-drawer.js +0 -757
  440. package/src/lib/components/csv-import-drawer.js.map +0 -1
  441. package/src/lib/components/deal-import-drawer.d.ts +0 -24
  442. package/src/lib/components/deal-import-drawer.js +0 -83
  443. package/src/lib/components/deal-import-drawer.js.map +0 -1
  444. package/src/lib/components/lead-import-drawer.d.ts +0 -27
  445. package/src/lib/components/lead-import-drawer.js +0 -118
  446. package/src/lib/components/lead-import-drawer.js.map +0 -1
  447. package/src/lib/components/lead-source-values-card.d.ts +0 -27
  448. package/src/lib/components/lead-source-values-card.js.map +0 -1
  449. package/src/lib/components/task-import-drawer.d.ts +0 -24
  450. package/src/lib/components/task-import-drawer.js +0 -82
  451. package/src/lib/components/task-import-drawer.js.map +0 -1
  452. package/src/lib/model/companies-csv.d.ts +0 -36
  453. package/src/lib/model/companies-csv.js +0 -38
  454. package/src/lib/model/companies-csv.js.map +0 -1
  455. package/src/lib/model/contacts-csv.d.ts +0 -54
  456. package/src/lib/model/contacts-csv.js +0 -67
  457. package/src/lib/model/contacts-csv.js.map +0 -1
  458. package/src/lib/model/crm-company-import.d.ts +0 -154
  459. package/src/lib/model/crm-company-import.js +0 -331
  460. package/src/lib/model/crm-company-import.js.map +0 -1
  461. package/src/lib/model/crm-csv.d.ts +0 -239
  462. package/src/lib/model/crm-csv.js +0 -467
  463. package/src/lib/model/crm-csv.js.map +0 -1
  464. package/src/lib/model/crm-deal-import.d.ts +0 -112
  465. package/src/lib/model/crm-deal-import.js +0 -263
  466. package/src/lib/model/crm-deal-import.js.map +0 -1
  467. package/src/lib/model/crm-import.d.ts +0 -250
  468. package/src/lib/model/crm-import.js +0 -467
  469. package/src/lib/model/crm-import.js.map +0 -1
  470. package/src/lib/model/crm-lead-import.d.ts +0 -138
  471. package/src/lib/model/crm-lead-import.js +0 -446
  472. package/src/lib/model/crm-lead-import.js.map +0 -1
  473. package/src/lib/model/crm-task-import.d.ts +0 -94
  474. package/src/lib/model/crm-task-import.js +0 -275
  475. package/src/lib/model/crm-task-import.js.map +0 -1
  476. package/src/lib/model/deals-csv.d.ts +0 -32
  477. package/src/lib/model/deals-csv.js +0 -36
  478. package/src/lib/model/deals-csv.js.map +0 -1
  479. package/src/lib/model/import-column-shapes.d.ts +0 -36
  480. package/src/lib/model/import-column-shapes.js +0 -76
  481. package/src/lib/model/import-column-shapes.js.map +0 -1
  482. package/src/lib/model/lead-source-values.d.ts +0 -82
  483. package/src/lib/model/lead-source-values.js +0 -149
  484. package/src/lib/model/lead-source-values.js.map +0 -1
  485. package/src/lib/model/leads-csv.d.ts +0 -40
  486. package/src/lib/model/leads-csv.js +0 -40
  487. package/src/lib/model/leads-csv.js.map +0 -1
  488. package/src/lib/model/tasks-csv.d.ts +0 -28
  489. package/src/lib/model/tasks-csv.js +0 -28
  490. package/src/lib/model/tasks-csv.js.map +0 -1
  491. package/src/lib/server/companies-import.d.ts +0 -57
  492. package/src/lib/server/companies-import.js +0 -242
  493. package/src/lib/server/companies-import.js.map +0 -1
  494. package/src/lib/server/contacts-import.d.ts +0 -109
  495. package/src/lib/server/contacts-import.js +0 -393
  496. package/src/lib/server/contacts-import.js.map +0 -1
  497. package/src/lib/server/deals-import.d.ts +0 -76
  498. package/src/lib/server/deals-import.js +0 -248
  499. package/src/lib/server/deals-import.js.map +0 -1
  500. package/src/lib/server/export-route.d.ts +0 -60
  501. package/src/lib/server/export-route.js +0 -447
  502. package/src/lib/server/export-route.js.map +0 -1
  503. package/src/lib/server/import-context.d.ts +0 -87
  504. package/src/lib/server/import-context.js +0 -182
  505. package/src/lib/server/import-context.js.map +0 -1
  506. package/src/lib/server/lead-source-values.d.ts +0 -50
  507. package/src/lib/server/lead-source-values.js +0 -191
  508. package/src/lib/server/lead-source-values.js.map +0 -1
  509. package/src/lib/server/leads-import.d.ts +0 -84
  510. package/src/lib/server/leads-import.js +0 -367
  511. package/src/lib/server/leads-import.js.map +0 -1
  512. package/src/lib/server/tasks-import.d.ts +0 -54
  513. package/src/lib/server/tasks-import.js +0 -160
  514. package/src/lib/server/tasks-import.js.map +0 -1
@@ -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 {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EMAIL_STATE_LABELS,\n EMAIL_STATE_STATUSES,\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n CRM_LEAD_STATUSES,\n type EmailStateStatus,\n type CrmLeadStatus,\n crmLeadSourceKey,\n} from '@aglyn/aglyn'\nimport type { CrmViewFilterClause } from '@aglyn/aglyn'\nimport { SCOPED_SEARCH_JOIN } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { type ListGridFilterCodec, listSelectCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n crmSelectField,\n} from './crm-list-query'\n\n/**\n * What the Leads section's `Show` control offers (AGL-2608): the two\n * aggregate views and each status on its own. `open` is the default — the\n * list opens on the work — asked of the query as\n * `status in [new, nurturing, working]`,\n * which every lead answers because every lead writer stores a status, `new`\n * for one nobody has touched (`crmLeadListFields`, AGL-3321).\n */\nexport type LeadFilter = 'open' | 'all' | CrmLeadStatus\n\nexport const LEAD_FILTERS: readonly LeadFilter[] = [\n 'open',\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n 'all',\n]\n\nexport const LEAD_FILTER_LABELS: Record<LeadFilter, string> = {\n open: 'Open',\n all: 'All',\n new: CRM_LEAD_STATUS_LABELS.new,\n nurturing: CRM_LEAD_STATUS_LABELS.nurturing,\n working: CRM_LEAD_STATUS_LABELS.working,\n qualified: CRM_LEAD_STATUS_LABELS.qualified,\n unqualified: CRM_LEAD_STATUS_LABELS.unqualified,\n}\n\n/**\n * The Leads section's `Email` control (AGL-3245): every lead, the ones a\n * member must not email — any verdict but `ok` — the ones nothing has been\n * said about, or one verdict on its own. Beside `Show`, not inside it: a\n * bounce does not move a lead out of Open, and a queue of bounced leads is\n * a queue of people to reach some other way.\n */\nexport type LeadEmailFilter = 'any' | 'problem' | 'none' | EmailStateStatus\n\nexport const LEAD_EMAIL_FILTERS: readonly LeadEmailFilter[] = [\n 'any',\n 'problem',\n ...EMAIL_STATE_STATUSES,\n 'none',\n]\n\nexport const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string> = {\n any: 'Any',\n problem: 'Cannot be emailed',\n none: 'Nothing known',\n ...EMAIL_STATE_LABELS,\n}\n\n/**\n * The `Lead source` control's \"no lead source\" choice (AGL-3298). Held in\n * the saved view as an `isEmpty` clause, never as this string; it is only\n * the select's own value for the choice.\n */\nexport const LEAD_SOURCE_FILTER_NONE = '\\u0000none'\n\n/*==========================================\n * THE LEADS GRID'S FILTERS (AGL-3313).\n *\n * The Show, Email, Campaign and Lead source dropdowns became columns of the\n * grid's own Filters panel. Their clauses keep the shape the dropdowns\n * stored, so every saved view of leads reads as it did:\n *\n * status `equals <filter>`; none at all is Open, `all` is every lead\n * emailState `equals <filter>`\n * campaignIds `contains <campaign id>`\n * leadSource `equals <label>`, or `isEmpty` for the leads holding none\n *\n * Every one is asked of the Leads query (AGL-3321) through the field a\n * writer stores for it — see {@link leadQueryClause}.\n *=========================================*/\n\n/** The fields the grid's panel offers on Leads. */\nexport const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals', 'isAnyOf'] },\n { column: 'emailState', kind: 'exact', path: 'emailState', operators: ['equals', 'isAnyOf'] },\n { column: 'ownerUid', kind: 'exact', path: 'ownerUid', operators: ['equals', 'isAnyOf'] },\n { column: 'leadSource', kind: 'exact', path: 'leadSource', operators: ['equals', 'isAnyOf'] },\n { column: 'campaignIds', kind: 'exact', path: 'campaignIds', operators: ['equals'] },\n]\n\n/** What each filter field reads as on a chip. */\nexport const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n emailState: 'Email',\n ownerUid: 'Owner',\n leadSource: 'Lead source',\n campaignIds: 'Campaign',\n}\n\n/** The choices for Status: Open (new, nurturing or working) and each status on its own. */\nexport const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter(\n (option) => option !== 'all',\n).map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : LEAD_FILTER_LABELS[option],\n}))\n\n/** The choices for Email: every verdict, plus the two aggregates. */\nexport const LEAD_EMAIL_FILTER_OPTIONS = LEAD_EMAIL_FILTERS.filter(\n (option) => option !== 'any',\n).map((option) => ({ value: option, label: LEAD_EMAIL_FILTER_LABELS[option] }))\n\nconst STATUS_OPEN: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'open' }\nconst STATUS_ALL: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'all' }\n\n/**\n * The clauses the grid shows for a view's stored ones. A stored list with\n * no status clause is Open, which the list opened on before views existed,\n * so it is shown as the clause it means; `status equals all` is every lead\n * and so no clause at all.\n */\nexport function leadClausesForGrid(\n stored: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = stored.find((clause) => clause.field === 'status')\n if (!status) return [STATUS_OPEN, ...stored]\n if (status.op === 'equals' && status.value === 'all') {\n return stored.filter((clause) => clause.field !== 'status')\n }\n return [...stored]\n}\n\n/** The inverse of {@link leadClausesForGrid}: what the view stores. */\nexport function leadClausesToStore(\n shown: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = shown.find((clause) => clause.field === 'status')\n if (!status) return [...shown, STATUS_ALL]\n if (status.op === 'equals' && status.value === 'open') {\n return shown.filter((clause) => clause.field !== 'status')\n }\n return [...shown]\n}\n\n/** Campaign clauses were stored as `contains`; the panel's select says `is`. */\nexport const LEAD_CAMPAIGN_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'contains' || clause.op === 'equals'\n ? { operator: 'is', value: clause.value }\n : null,\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n return clause && clause.op === 'equals' ? { ...clause, op: 'contains' } : null\n },\n}\n\n/** \"No lead source\" is an `isEmpty` clause, shown as a choice of the select. */\nexport const LEAD_SOURCE_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'isEmpty'\n ? { operator: 'is', value: LEAD_SOURCE_FILTER_NONE }\n : listSelectCodec.toItem(clause),\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n if (clause?.op === 'equals' && clause.value === LEAD_SOURCE_FILTER_NONE) {\n return { field: clause.field, op: 'isEmpty', value: '' }\n }\n return clause\n },\n}\n\n/** The Leads fields whose stored clauses translate their own way. */\nexport const LEAD_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>> = {\n campaignIds: LEAD_CAMPAIGN_CODEC,\n leadSource: LEAD_SOURCE_CODEC,\n}\n\n/*==========================================\n * THE LEADS QUERY (AGL-3321).\n *\n * `orgs/{orgId}/leads`, newest seen first, under a site narrowed by the\n * `visibleTo` scope clause. Each grid clause is asked through a field every\n * lead writer stores (`crmLeadListFields` in `@aglyn/aglyn`):\n *\n * status `status`, stored `new` on a lead nobody has touched, so\n * Open is `status in [new, nurturing, working]`\n * emailState `emailStatus`, the verdict's status or `none`; \"Cannot be\n * emailed\" is every verdict but `ok`\n * leadSource `leadSourceKey`, the label as the picklist compares it, or\n * `null` for none\n * ownerUid as stored\n * campaignIds `array-contains` on the lead's own campaigns; under a site,\n * `scopedCampaignIds` — the campaign behind each of the\n * site's scope tokens — which stands in the scope clause's\n * place, so the one array clause answers both\n *\n * and the search box reads `searchTokens` — word prefixes of the name,\n * address, company, title and tags. Every equality rides one\n * `(field, lastSeenAtMs DESC)` composite; the campaign and the search are\n * array clauses, so the two do not stand together (nor under a site, where\n * the scope clause is the query's one array clause, save the search, which\n * folds into it for a reader who may drop it).\n *=========================================*/\n\n/** The one order the Leads list takes: newest seen first. */\nexport const LEAD_LIST_SORTS: readonly ListQuerySort[] = [{ path: 'lastSeenAtMs', direction: 'desc' }]\n\n/** The fields the Leads query asks — each a stored field, as named. */\nexport const LEAD_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n crmSelectField('emailStatus'),\n crmSelectField('ownerUid'),\n {\n column: 'leadSourceKey',\n kind: 'exact',\n path: 'leadSourceKey',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty'],\n },\n {\n column: 'campaignIds',\n kind: 'exact',\n path: 'campaignIds',\n tokensPath: 'campaignIds',\n operators: ['contains'],\n },\n {\n // A campaign under a site: the lead's campaigns behind its scope.\n column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n kind: 'exact',\n path: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n tokensPath: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n operators: ['isAnyOf'],\n },\n]\n\nexport const LEAD_LIST_DECLARATION: ListQueryDeclaration = {\n fields: LEAD_QUERY_FIELDS,\n sorts: LEAD_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the address, beside the scope clause — the `(visibleTo, email)`\n * composite the Inbox's leads list reads too.\n */\nexport const LEAD_PREFIX_SEARCH = {\n field: {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['startsWith'],\n } satisfies ListFilterField,\n notice: 'Search matches the start of a lead’s address for access limited to specific sites.',\n}\n\n/** Whether an asked clause stands in for the scope clause: a campaign behind the site's scope. */\nexport const leadClauseImpliesScope = (clause: ListFilterRequest): boolean =>\n clause.field === CRM_LEAD_SCOPED_CAMPAIGNS_FIELD\n\n/** The verdicts a member must not email to: every one but `ok`. */\nconst EMAIL_PROBLEMS = EMAIL_STATE_STATUSES.filter((status) => status !== 'ok')\n\n/** Who is asking, for the one clause whose shape depends on it: the Campaign. */\nexport interface LeadQueryReader {\n /** The site's scope tokens, or `null` at the organization level. */\n scopeTokens: readonly string[] | null\n /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */\n foldsScope: boolean\n}\n\nconst ORG_READER: LeadQueryReader = { scopeTokens: null, foldsScope: true }\n\n/**\n * One grid clause as the clause the Leads query asks — see the block above.\n * A value the list does not know is refused by name rather than dropped, so\n * a stored view that names a retired choice says so.\n */\nexport function leadQueryClause(\n clause: CrmViewFilterClause | ListFilterRequest,\n reader: LeadQueryReader = ORG_READER,\n): CrmClauseAsked {\n const values = crmClauseValues(clause)\n switch (clause.field) {\n case 'status': {\n const statuses: string[] = []\n for (const value of values) {\n if (value === 'open') statuses.push(...CRM_LEAD_OPEN_STATUSES)\n else if ((CRM_LEAD_STATUSES as readonly string[]).includes(value)) statuses.push(value)\n else return { refused: `${value} is not a lead status` }\n }\n return crmAnyOf('status', statuses)\n }\n case 'emailState': {\n const keys: string[] = []\n for (const value of values) {\n if (value === 'problem') keys.push(...EMAIL_PROBLEMS)\n else if (value === 'none') keys.push('none')\n else if ((EMAIL_STATE_STATUSES as readonly string[]).includes(value)) keys.push(value)\n else return { refused: `${value} is not an email verdict` }\n }\n return crmAnyOf('emailStatus', keys)\n }\n case 'leadSource': {\n if (clause.op === 'isEmpty' || (values.length === 1 && values[0] === LEAD_SOURCE_FILTER_NONE)) {\n return { field: 'leadSourceKey', op: 'isEmpty', value: '' }\n }\n if (values.includes(LEAD_SOURCE_FILTER_NONE)) {\n return { refused: '\"No lead source\" cannot be picked beside a value' }\n }\n const keys = values.map((value) => crmLeadSourceKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf('leadSourceKey', keys)\n }\n case 'ownerUid':\n return crmAnyOf('ownerUid', values)\n case 'campaignIds': {\n if (values.length !== 1) return { refused: 'pick one campaign' }\n if (!reader.scopeTokens) return { field: 'campaignIds', op: 'contains', value: values[0] }\n if (!reader.foldsScope) {\n return {\n refused:\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it',\n }\n }\n return {\n field: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n op: 'isAnyOf',\n value: reader.scopeTokens.map((token) => `${token}${SCOPED_SEARCH_JOIN}${values[0]}`).join(','),\n }\n }\n default:\n return { refused: 'this list does not filter by that' }\n }\n}\n\n/**\n * The query shape the Leads list can send beyond its own declaration, for\n * the index pin (AGL-3321): a collaborator's address prefix beside the\n * equalities they may set.\n */\nexport function leadIndexShapes(): ListQueryDeclaration[] {\n const equalities = LEAD_QUERY_FIELDS.filter((field) => !field.tokensPath)\n return [{ fields: [LEAD_PREFIX_SEARCH.field, ...equalities], sorts: [] }]\n}\n"],"names":["EMAIL_STATE_LABELS","EMAIL_STATE_STATUSES","CRM_LEAD_STATUS_LABELS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","CRM_LEAD_STATUSES","crmLeadSourceKey","SCOPED_SEARCH_JOIN","listSelectCodec","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","crmSelectField","LEAD_FILTERS","LEAD_FILTER_LABELS","open","all","new","nurturing","working","qualified","unqualified","LEAD_EMAIL_FILTERS","LEAD_EMAIL_FILTER_LABELS","any","problem","none","LEAD_SOURCE_FILTER_NONE","LEAD_LIST_FILTER_FIELDS","column","kind","path","operators","LEAD_LIST_FILTER_HEADERS","status","emailState","ownerUid","leadSource","campaignIds","LEAD_STATUS_FILTER_OPTIONS","filter","option","map","value","label","LEAD_EMAIL_FILTER_OPTIONS","STATUS_OPEN","field","op","STATUS_ALL","leadClausesForGrid","stored","find","clause","leadClausesToStore","shown","LEAD_CAMPAIGN_CODEC","toItem","operator","toClause","item","LEAD_SOURCE_CODEC","LEAD_FILTER_CODECS","LEAD_LIST_SORTS","direction","LEAD_QUERY_FIELDS","presence","tokensPath","LEAD_LIST_DECLARATION","fields","sorts","search","LEAD_PREFIX_SEARCH","lowerPath","notice","leadClauseImpliesScope","EMAIL_PROBLEMS","ORG_READER","scopeTokens","foldsScope","leadQueryClause","reader","values","statuses","push","includes","refused","keys","length","key","Boolean","token","join","leadIndexShapes","equalities"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,kBAAkB,EAClBC,oBAAoB,EACpBC,sBAAsB,EACtBC,sBAAsB,EACtBC,+BAA+B,EAC/BC,iBAAiB,EAGjBC,gBAAgB,QACX,eAAc;AAErB,SAASC,kBAAkB,QAAQ,qCAAoC;AAEvE,SAAmCC,eAAe,QAAQ,8CAA6C;AAEvG,SACEC,eAAe,EAEfC,QAAQ,EACRC,eAAe,EACfC,cAAc,QACT,sBAAkB;AAYzB,OAAO,MAAMC,eAAsC;IACjD;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,OAAO,MAAMC,qBAAiD;IAC5DC,MAAM;IACNC,KAAK;IACLC,KAAKf,uBAAuBe,GAAG;IAC/BC,WAAWhB,uBAAuBgB,SAAS;IAC3CC,SAASjB,uBAAuBiB,OAAO;IACvCC,WAAWlB,uBAAuBkB,SAAS;IAC3CC,aAAanB,uBAAuBmB,WAAW;AACjD,EAAC;AAWD,OAAO,MAAMC,qBAAiD;IAC5D;IACA;OACGrB;IACH;CACD,CAAA;AAED,OAAO,MAAMsB,2BAA4D;IACvEC,KAAK;IACLC,SAAS;IACTC,MAAM;GACH1B,oBACJ;AAED;;;;CAIC,GACD,OAAO,MAAM2B,0BAA0B,aAAY;AAEnD;;;;;;;;;;;;;;2CAc2C,GAE3C,iDAAiD,GACjD,OAAO,MAAMC,0BAAsD;IACjE;QAAEC,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAUC,WAAW;YAAC;YAAU;SAAU;IAAC;IACpF;QAAEH,QAAQ;QAAcC,MAAM;QAASC,MAAM;QAAcC,WAAW;YAAC;YAAU;SAAU;IAAC;IAC5F;QAAEH,QAAQ;QAAYC,MAAM;QAASC,MAAM;QAAYC,WAAW;YAAC;YAAU;SAAU;IAAC;IACxF;QAAEH,QAAQ;QAAcC,MAAM;QAASC,MAAM;QAAcC,WAAW;YAAC;YAAU;SAAU;IAAC;IAC5F;QAAEH,QAAQ;QAAeC,MAAM;QAASC,MAAM;QAAeC,WAAW;YAAC;SAAS;IAAC;CACpF,CAAA;AAED,+CAA+C,GAC/C,OAAO,MAAMC,2BAA6D;IACxEC,QAAQ;IACRC,YAAY;IACZC,UAAU;IACVC,YAAY;IACZC,aAAa;AACf,EAAC;AAED,yFAAyF,GACzF,OAAO,MAAMC,6BAA6B1B,aAAa2B,MAAM,CAC3D,CAACC,SAAWA,WAAW,OACvBC,GAAG,CAAC,CAACD,SAAY,CAAA;QACjBE,OAAOF;QACPG,OAAOH,WAAW,SAAS,qCAAqC3B,kBAAkB,CAAC2B,OAAO;IAC5F,CAAA,GAAG;AAEH,mEAAmE,GACnE,OAAO,MAAMI,4BAA4BvB,mBAAmBkB,MAAM,CAChE,CAACC,SAAWA,WAAW,OACvBC,GAAG,CAAC,CAACD,SAAY,CAAA;QAAEE,OAAOF;QAAQG,OAAOrB,wBAAwB,CAACkB,OAAO;IAAC,CAAA,GAAG;AAE/E,MAAMK,cAAmC;IAAEC,OAAO;IAAUC,IAAI;IAAUL,OAAO;AAAO;AACxF,MAAMM,aAAkC;IAAEF,OAAO;IAAUC,IAAI;IAAUL,OAAO;AAAM;AAEtF;;;;;CAKC,GACD,OAAO,SAASO,mBACdC,MAAsC;IAEtC,MAAMjB,SAASiB,OAAOC,IAAI,CAAC,CAACC,SAAWA,OAAON,KAAK,KAAK;IACxD,IAAI,CAACb,QAAQ,OAAO;QAACY;WAAgBK;KAAO;IAC5C,IAAIjB,OAAOc,EAAE,KAAK,YAAYd,OAAOS,KAAK,KAAK,OAAO;QACpD,OAAOQ,OAAOX,MAAM,CAAC,CAACa,SAAWA,OAAON,KAAK,KAAK;IACpD;IACA,OAAO;WAAII;KAAO;AACpB;AAEA,qEAAqE,GACrE,OAAO,SAASG,mBACdC,KAAqC;IAErC,MAAMrB,SAASqB,MAAMH,IAAI,CAAC,CAACC,SAAWA,OAAON,KAAK,KAAK;IACvD,IAAI,CAACb,QAAQ,OAAO;WAAIqB;QAAON;KAAW;IAC1C,IAAIf,OAAOc,EAAE,KAAK,YAAYd,OAAOS,KAAK,KAAK,QAAQ;QACrD,OAAOY,MAAMf,MAAM,CAAC,CAACa,SAAWA,OAAON,KAAK,KAAK;IACnD;IACA,OAAO;WAAIQ;KAAM;AACnB;AAEA,8EAA8E,GAC9E,OAAO,MAAMC,sBAA2C;IACtDC,QAAQ,CAACJ,SACPA,OAAOL,EAAE,KAAK,cAAcK,OAAOL,EAAE,KAAK,WACtC;YAAEU,UAAU;YAAMf,OAAOU,OAAOV,KAAK;QAAC,IACtC;IACNgB,UAAU,CAACC;QACT,MAAMP,SAAS7C,gBAAgBmD,QAAQ,CAACC;QACxC,OAAOP,UAAUA,OAAOL,EAAE,KAAK,WAAW,aAAKK;YAAQL,IAAI;aAAe;IAC5E;AACF,EAAC;AAED,8EAA8E,GAC9E,OAAO,MAAMa,oBAAyC;IACpDJ,QAAQ,CAACJ,SACPA,OAAOL,EAAE,KAAK,YACV;YAAEU,UAAU;YAAMf,OAAOhB;QAAwB,IACjDnB,gBAAgBiD,MAAM,CAACJ;IAC7BM,UAAU,CAACC;QACT,MAAMP,SAAS7C,gBAAgBmD,QAAQ,CAACC;QACxC,IAAIP,CAAAA,0BAAAA,OAAQL,EAAE,MAAK,YAAYK,OAAOV,KAAK,KAAKhB,yBAAyB;YACvE,OAAO;gBAAEoB,OAAOM,OAAON,KAAK;gBAAEC,IAAI;gBAAWL,OAAO;YAAG;QACzD;QACA,OAAOU;IACT;AACF,EAAC;AAED,mEAAmE,GACnE,OAAO,MAAMS,qBAAoE;IAC/ExB,aAAakB;IACbnB,YAAYwB;AACd,EAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;2CAyB2C,GAE3C,2DAA2D,GAC3D,OAAO,MAAME,kBAA4C;IAAC;QAAEhC,MAAM;QAAgBiC,WAAW;IAAO;CAAE,CAAA;AAEtG,qEAAqE,GACrE,OAAO,MAAMC,oBAAgD;IAC3DrD,eAAe;IACfA,eAAe;IACfA,eAAe;IACf;QACEiB,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNmC,UAAU;QACVlC,WAAW;YAAC;YAAU;YAAW;SAAU;IAC7C;IACA;QACEH,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNoC,YAAY;QACZnC,WAAW;YAAC;SAAW;IACzB;IACA;QACE,kEAAkE;QAClEH,QAAQzB;QACR0B,MAAM;QACNC,MAAM3B;QACN+D,YAAY/D;QACZ4B,WAAW;YAAC;SAAU;IACxB;CACD,CAAA;AAED,OAAO,MAAMoC,wBAA8C;IACzDC,QAAQJ;IACRK,OAAOP;IACPQ,QAAQ9D;AACV,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAM+D,qBAAqB;IAChCzB,OAAO;QACLlB,QAAQ;QACRC,MAAM;QACNC,MAAM;QACN0C,WAAW;QACXP,UAAU;QACVlC,WAAW;YAAC;SAAa;IAC3B;IACA0C,QAAQ;AACV,EAAC;AAED,gGAAgG,GAChG,OAAO,MAAMC,yBAAyB,CAACtB,SACrCA,OAAON,KAAK,KAAK3C,gCAA+B;AAElD,iEAAiE,GACjE,MAAMwE,iBAAiB3E,qBAAqBuC,MAAM,CAAC,CAACN,SAAWA,WAAW;AAU1E,MAAM2C,aAA8B;IAAEC,aAAa;IAAMC,YAAY;AAAK;AAE1E;;;;CAIC,GACD,OAAO,SAASC,gBACd3B,MAA+C,EAC/C4B,SAA0BJ,UAAU;IAEpC,MAAMK,SAASvE,gBAAgB0C;IAC/B,OAAQA,OAAON,KAAK;QAClB,KAAK;YAAU;gBACb,MAAMoC,WAAqB,EAAE;gBAC7B,KAAK,MAAMxC,SAASuC,OAAQ;oBAC1B,IAAIvC,UAAU,QAAQwC,SAASC,IAAI,IAAIjF;yBAClC,IAAI,AAACE,kBAAwCgF,QAAQ,CAAC1C,QAAQwC,SAASC,IAAI,CAACzC;yBAC5E,OAAO;wBAAE2C,SAAS,GAAG3C,MAAM,qBAAqB,CAAC;oBAAC;gBACzD;gBACA,OAAOjC,SAAS,UAAUyE;YAC5B;QACA,KAAK;YAAc;gBACjB,MAAMI,OAAiB,EAAE;gBACzB,KAAK,MAAM5C,SAASuC,OAAQ;oBAC1B,IAAIvC,UAAU,WAAW4C,KAAKH,IAAI,IAAIR;yBACjC,IAAIjC,UAAU,QAAQ4C,KAAKH,IAAI,CAAC;yBAChC,IAAI,AAACnF,qBAA2CoF,QAAQ,CAAC1C,QAAQ4C,KAAKH,IAAI,CAACzC;yBAC3E,OAAO;wBAAE2C,SAAS,GAAG3C,MAAM,wBAAwB,CAAC;oBAAC;gBAC5D;gBACA,OAAOjC,SAAS,eAAe6E;YACjC;QACA,KAAK;YAAc;gBACjB,IAAIlC,OAAOL,EAAE,KAAK,aAAckC,OAAOM,MAAM,KAAK,KAAKN,MAAM,CAAC,EAAE,KAAKvD,yBAA0B;oBAC7F,OAAO;wBAAEoB,OAAO;wBAAiBC,IAAI;wBAAWL,OAAO;oBAAG;gBAC5D;gBACA,IAAIuC,OAAOG,QAAQ,CAAC1D,0BAA0B;oBAC5C,OAAO;wBAAE2D,SAAS;oBAAmD;gBACvE;gBACA,MAAMC,OAAOL,OAAOxC,GAAG,CAAC,CAACC,QAAUrC,iBAAiBqC,QAAQH,MAAM,CAAC,CAACiD,MAAuBC,QAAQD;gBACnG,OAAO/E,SAAS,iBAAiB6E;YACnC;QACA,KAAK;YACH,OAAO7E,SAAS,YAAYwE;QAC9B,KAAK;YAAe;gBAClB,IAAIA,OAAOM,MAAM,KAAK,GAAG,OAAO;oBAAEF,SAAS;gBAAoB;gBAC/D,IAAI,CAACL,OAAOH,WAAW,EAAE,OAAO;oBAAE/B,OAAO;oBAAeC,IAAI;oBAAYL,OAAOuC,MAAM,CAAC,EAAE;gBAAC;gBACzF,IAAI,CAACD,OAAOF,UAAU,EAAE;oBACtB,OAAO;wBACLO,SACE;oBACJ;gBACF;gBACA,OAAO;oBACLvC,OAAO3C;oBACP4C,IAAI;oBACJL,OAAOsC,OAAOH,WAAW,CAACpC,GAAG,CAAC,CAACiD,QAAU,GAAGA,QAAQpF,qBAAqB2E,MAAM,CAAC,EAAE,EAAE,EAAEU,IAAI,CAAC;gBAC7F;YACF;QACA;YACE,OAAO;gBAAEN,SAAS;YAAoC;IAC1D;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASO;IACd,MAAMC,aAAa7B,kBAAkBzB,MAAM,CAAC,CAACO,QAAU,CAACA,MAAMoB,UAAU;IACxE,OAAO;QAAC;YAAEE,QAAQ;gBAACG,mBAAmBzB,KAAK;mBAAK+C;aAAW;YAAExB,OAAO,EAAE;QAAC;KAAE;AAC3E"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EMAIL_STATE_LABELS,\n EMAIL_STATE_STATUSES,\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n CRM_LEAD_STATUSES,\n type EmailStateStatus,\n type CrmLeadStatus,\n type CrmPicklist,\n crmLeadSourceKey,\n crmPicklistKey,\n} from '@aglyn/aglyn'\nimport type { CrmViewFilterClause } from '@aglyn/aglyn'\nimport { SCOPED_SEARCH_JOIN } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { type ListGridFilterCodec, listSelectCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n LIST_QUERY_DISJUNCTIONS,\n type ListQueryDeclaration,\n type ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n crmSelectField,\n} from './crm-list-query'\nimport {\n isLeadSourceDirection,\n LEAD_SOURCE_DIRECTION_LABELS,\n LEAD_SOURCE_DIRECTIONS,\n leadSourceLabelsOfDirection,\n STANDARD_LEAD_SOURCES,\n} from './lead-source-direction'\n\n/**\n * What the Leads section's `Show` control offers (AGL-2608): the two\n * aggregate views and each status on its own. `open` is the default — the\n * list opens on the work — asked of the query as\n * `status in [new, nurturing, working]`,\n * which every lead answers because every lead writer stores a status, `new`\n * for one nobody has touched (`crmLeadListFields`, AGL-3321).\n */\nexport type LeadFilter = 'open' | 'all' | CrmLeadStatus\n\nexport const LEAD_FILTERS: readonly LeadFilter[] = [\n 'open',\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n 'all',\n]\n\nexport const LEAD_FILTER_LABELS: Record<LeadFilter, string> = {\n open: 'Open',\n all: 'All',\n new: CRM_LEAD_STATUS_LABELS.new,\n nurturing: CRM_LEAD_STATUS_LABELS.nurturing,\n working: CRM_LEAD_STATUS_LABELS.working,\n qualified: CRM_LEAD_STATUS_LABELS.qualified,\n unqualified: CRM_LEAD_STATUS_LABELS.unqualified,\n}\n\n/**\n * The Leads section's `Email` control (AGL-3245): every lead, the ones a\n * member must not email — any verdict but `ok` — the ones nothing has been\n * said about, or one verdict on its own. Beside `Show`, not inside it: a\n * bounce does not move a lead out of Open, and a queue of bounced leads is\n * a queue of people to reach some other way.\n */\nexport type LeadEmailFilter = 'any' | 'problem' | 'none' | EmailStateStatus\n\nexport const LEAD_EMAIL_FILTERS: readonly LeadEmailFilter[] = [\n 'any',\n 'problem',\n ...EMAIL_STATE_STATUSES,\n 'none',\n]\n\nexport const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string> = {\n any: 'Any',\n problem: 'Cannot be emailed',\n none: 'Nothing known',\n ...EMAIL_STATE_LABELS,\n}\n\n/**\n * The `Lead source` control's \"no lead source\" choice (AGL-3298). Held in\n * the saved view as an `isEmpty` clause, never as this string; it is only\n * the select's own value for the choice.\n */\nexport const LEAD_SOURCE_FILTER_NONE = '\\u0000none'\n\n/*==========================================\n * THE LEADS GRID'S FILTERS (AGL-3313).\n *\n * The Show, Email, Campaign and Lead source dropdowns became columns of the\n * grid's own Filters panel. Their clauses keep the shape the dropdowns\n * stored, so every saved view of leads reads as it did:\n *\n * status `equals <filter>`; none at all is Open, `all` is every lead\n * emailState `equals <filter>`\n * campaignIds `contains <campaign id>`\n * leadSource `equals <label>`, or `isEmpty` for the leads holding none\n * leadSourceDirection `equals inbound|outbound` (AGL-3511): every lead\n * whose lead source is a value of that group\n * industry, rating `equals <key>` (AGL-3513): a value of the org's\n * Industry or Rating list, by the key the query compares\n *\n * Every one is asked of the Leads query (AGL-3321) through the field a\n * writer stores for it — see {@link leadQueryClause}.\n *=========================================*/\n\n/** The fields the grid's panel offers on Leads. */\nexport const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals', 'isAnyOf'] },\n { column: 'emailState', kind: 'exact', path: 'emailState', operators: ['equals', 'isAnyOf'] },\n { column: 'ownerUid', kind: 'exact', path: 'ownerUid', operators: ['equals', 'isAnyOf'] },\n { column: 'leadSource', kind: 'exact', path: 'leadSource', operators: ['equals', 'isAnyOf'] },\n {\n column: 'leadSourceDirection',\n kind: 'exact',\n path: 'leadSourceDirection',\n operators: ['equals', 'isAnyOf'],\n },\n { column: 'campaignIds', kind: 'exact', path: 'campaignIds', operators: ['equals'] },\n // Salesforce's Industry and Rating (AGL-3513), the lists companies keep.\n { column: 'industry', kind: 'exact', path: 'industry', operators: ['equals', 'isAnyOf'] },\n { column: 'rating', kind: 'exact', path: 'rating', operators: ['equals', 'isAnyOf'] },\n]\n\n/**\n * The lead picklist columns the list filters by (AGL-3513), each with the\n * list its choices come from: a choice's value is the KEY the query\n * compares, its caption the label.\n */\nexport const LEAD_PICKLIST_FILTERS = [\n { column: 'industry', picklistId: 'industry', header: 'Industry' },\n { column: 'rating', picklistId: 'rating', header: 'Rating' },\n] as const\n\n/** What each filter field reads as on a chip. */\nexport const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n emailState: 'Email',\n ownerUid: 'Owner',\n leadSource: 'Lead source',\n leadSourceDirection: 'Lead source direction',\n campaignIds: 'Campaign',\n industry: 'Industry',\n rating: 'Rating',\n}\n\n/** The choices for Status: Open (new, nurturing or working) and each status on its own. */\nexport const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter(\n (option) => option !== 'all',\n).map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : LEAD_FILTER_LABELS[option],\n}))\n\n/** The choices for Email: every verdict, plus the two aggregates. */\nexport const LEAD_EMAIL_FILTER_OPTIONS = LEAD_EMAIL_FILTERS.filter(\n (option) => option !== 'any',\n).map((option) => ({ value: option, label: LEAD_EMAIL_FILTER_LABELS[option] }))\n\n/** The choices for Lead source direction: each group of the Lead source picklist. */\nexport const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS = LEAD_SOURCE_DIRECTIONS.map((direction) => ({\n value: direction,\n label: LEAD_SOURCE_DIRECTION_LABELS[direction],\n}))\n\n/**\n * The Status choices as an org reads them (AGL-3512): still one choice per\n * MEANING — the query asks `status` — each named by the org's values of that\n * meaning, so a list that keeps \"Contacted\" and \"Meeting set\" as Working\n * reads \"Working: Contacted, Meeting set\".\n */\nexport function leadStatusFilterOptions(\n picklist: CrmPicklist,\n): Array<{ value: string; label: string }> {\n const named = (status: CrmLeadStatus): string => {\n const labels = picklist.values\n .filter((value) => value.meaning === status && value.active)\n .map((value) => value.label)\n const meaning = picklistMeaningName(status)\n if (!labels.length) return meaning\n if (labels.length === 1 && labels[0] === meaning) return meaning\n return `${meaning}: ${labels.join(', ')}`\n }\n return LEAD_FILTERS.filter((option) => option !== 'all').map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : named(option),\n }))\n}\n\nconst picklistMeaningName = (status: CrmLeadStatus): string => CRM_LEAD_STATUS_LABELS[status]\n\nconst STATUS_OPEN: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'open' }\nconst STATUS_ALL: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'all' }\n\n/**\n * The clauses the grid shows for a view's stored ones. A stored list with\n * no status clause is Open, which the list opened on before views existed,\n * so it is shown as the clause it means; `status equals all` is every lead\n * and so no clause at all.\n */\nexport function leadClausesForGrid(\n stored: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = stored.find((clause) => clause.field === 'status')\n if (!status) return [STATUS_OPEN, ...stored]\n if (status.op === 'equals' && status.value === 'all') {\n return stored.filter((clause) => clause.field !== 'status')\n }\n return [...stored]\n}\n\n/** The inverse of {@link leadClausesForGrid}: what the view stores. */\nexport function leadClausesToStore(\n shown: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = shown.find((clause) => clause.field === 'status')\n if (!status) return [...shown, STATUS_ALL]\n if (status.op === 'equals' && status.value === 'open') {\n return shown.filter((clause) => clause.field !== 'status')\n }\n return [...shown]\n}\n\n/** Campaign clauses were stored as `contains`; the panel's select says `is`. */\nexport const LEAD_CAMPAIGN_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'contains' || clause.op === 'equals'\n ? { operator: 'is', value: clause.value }\n : null,\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n return clause && clause.op === 'equals' ? { ...clause, op: 'contains' } : null\n },\n}\n\n/** \"No lead source\" is an `isEmpty` clause, shown as a choice of the select. */\nexport const LEAD_SOURCE_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'isEmpty'\n ? { operator: 'is', value: LEAD_SOURCE_FILTER_NONE }\n : listSelectCodec.toItem(clause),\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n if (clause?.op === 'equals' && clause.value === LEAD_SOURCE_FILTER_NONE) {\n return { field: clause.field, op: 'isEmpty', value: '' }\n }\n return clause\n },\n}\n\n/** The Leads fields whose stored clauses translate their own way. */\nexport const LEAD_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>> = {\n campaignIds: LEAD_CAMPAIGN_CODEC,\n leadSource: LEAD_SOURCE_CODEC,\n}\n\n/*==========================================\n * THE LEADS QUERY (AGL-3321).\n *\n * `orgs/{orgId}/leads`, newest seen first, under a site narrowed by the\n * `visibleTo` scope clause. Each grid clause is asked through a field every\n * lead writer stores (`crmLeadListFields` in `@aglyn/aglyn`):\n *\n * status `status`, stored `new` on a lead nobody has touched, so\n * Open is `status in [new, nurturing, working]`\n * emailState `emailStatus`, the verdict's status or `none`; \"Cannot be\n * emailed\" is every verdict but `ok`\n * leadSource `leadSourceKey`, the label as the picklist compares it, or\n * `null` for none\n * leadSourceDirection `leadSourceKey in [...]` over the key of every\n * value the org's list files under that group, read when\n * the clause is asked — a direction is never stored on a\n * lead, so regrouping a value moves its leads with it. One\n * `in` holds thirty keys, and a group holding more is\n * refused by name rather than half asked\n * ownerUid as stored\n * industry, rating `industryKey`, `ratingKey` (AGL-3513), each label\n * as the picklist compares it\n * campaignIds `array-contains` on the lead's own campaigns; under a site,\n * `scopedCampaignIds` — the campaign behind each of the\n * site's scope tokens — which stands in the scope clause's\n * place, so the one array clause answers both\n *\n * and the search box reads `searchTokens` — word prefixes of the name,\n * address, company, title and tags. Every equality rides one\n * `(field, lastSeenAtMs DESC)` composite; the campaign and the search are\n * array clauses, so the two do not stand together (nor under a site, where\n * the scope clause is the query's one array clause, save the search, which\n * folds into it for a reader who may drop it).\n *=========================================*/\n\n/** The one order the Leads list takes: newest seen first. */\nexport const LEAD_LIST_SORTS: readonly ListQuerySort[] = [{ path: 'lastSeenAtMs', direction: 'desc' }]\n\n/** The fields the Leads query asks — each a stored field, as named. */\nexport const LEAD_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n crmSelectField('emailStatus'),\n crmSelectField('ownerUid'),\n {\n column: 'leadSourceKey',\n kind: 'exact',\n path: 'leadSourceKey',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty'],\n },\n {\n column: 'campaignIds',\n kind: 'exact',\n path: 'campaignIds',\n tokensPath: 'campaignIds',\n operators: ['contains'],\n },\n // Industry and Rating (AGL-3513), by the key each writer stores.\n crmSelectField('industryKey'),\n crmSelectField('ratingKey'),\n {\n // A campaign under a site: the lead's campaigns behind its scope.\n column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n kind: 'exact',\n path: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n tokensPath: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n operators: ['isAnyOf'],\n },\n]\n\nexport const LEAD_LIST_DECLARATION: ListQueryDeclaration = {\n fields: LEAD_QUERY_FIELDS,\n sorts: LEAD_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the address, beside the scope clause — the `(visibleTo, email)`\n * composite the Inbox's leads list reads too.\n */\nexport const LEAD_PREFIX_SEARCH = {\n field: {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['startsWith'],\n } satisfies ListFilterField,\n notice: 'Search matches the start of a lead’s address for access limited to specific sites.',\n}\n\n/** Whether an asked clause stands in for the scope clause: a campaign behind the site's scope. */\nexport const leadClauseImpliesScope = (clause: ListFilterRequest): boolean =>\n clause.field === CRM_LEAD_SCOPED_CAMPAIGNS_FIELD\n\n/** The verdicts a member must not email to: every one but `ok`. */\nconst EMAIL_PROBLEMS = EMAIL_STATE_STATUSES.filter((status) => status !== 'ok')\n\n/** Who is asking, for the one clause whose shape depends on it: the Campaign. */\nexport interface LeadQueryReader {\n /** The site's scope tokens, or `null` at the organization level. */\n scopeTokens: readonly string[] | null\n /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */\n foldsScope: boolean\n /**\n * The org's Lead source list, which a direction is expanded through;\n * the standard values alone while it has not been read.\n */\n leadSources?: CrmPicklist\n}\n\nconst ORG_READER: LeadQueryReader = { scopeTokens: null, foldsScope: true }\n\n/**\n * One grid clause as the clause the Leads query asks — see the block above.\n * A value the list does not know is refused by name rather than dropped, so\n * a stored view that names a retired choice says so.\n */\nexport function leadQueryClause(\n clause: CrmViewFilterClause | ListFilterRequest,\n reader: LeadQueryReader = ORG_READER,\n): CrmClauseAsked {\n const values = crmClauseValues(clause)\n switch (clause.field) {\n case 'status': {\n const statuses: string[] = []\n for (const value of values) {\n if (value === 'open') statuses.push(...CRM_LEAD_OPEN_STATUSES)\n else if ((CRM_LEAD_STATUSES as readonly string[]).includes(value)) statuses.push(value)\n else return { refused: `${value} is not a lead status` }\n }\n return crmAnyOf('status', statuses)\n }\n case 'emailState': {\n const keys: string[] = []\n for (const value of values) {\n if (value === 'problem') keys.push(...EMAIL_PROBLEMS)\n else if (value === 'none') keys.push('none')\n else if ((EMAIL_STATE_STATUSES as readonly string[]).includes(value)) keys.push(value)\n else return { refused: `${value} is not an email verdict` }\n }\n return crmAnyOf('emailStatus', keys)\n }\n case 'leadSource': {\n if (clause.op === 'isEmpty' || (values.length === 1 && values[0] === LEAD_SOURCE_FILTER_NONE)) {\n return { field: 'leadSourceKey', op: 'isEmpty', value: '' }\n }\n if (values.includes(LEAD_SOURCE_FILTER_NONE)) {\n return { refused: '\"No lead source\" cannot be picked beside a value' }\n }\n const keys = values.map((value) => crmLeadSourceKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf('leadSourceKey', keys)\n }\n case 'leadSourceDirection': {\n const picklist = reader.leadSources ?? STANDARD_LEAD_SOURCES\n const keys: string[] = []\n for (const value of values) {\n if (!isLeadSourceDirection(value)) return { refused: `${value} is not a lead source direction` }\n for (const label of leadSourceLabelsOfDirection(picklist, value)) {\n const key = crmLeadSourceKey(label)\n if (key) keys.push(key)\n }\n }\n const unique = [...new Set(keys)]\n if (!unique.length) return { refused: 'no lead source is in that direction yet' }\n if (unique.length > LIST_QUERY_DISJUNCTIONS) {\n return {\n refused: `more than ${LIST_QUERY_DISJUNCTIONS} lead sources are in that direction — filter by Lead source instead`,\n }\n }\n return crmAnyOf('leadSourceKey', unique)\n }\n case 'ownerUid':\n return crmAnyOf('ownerUid', values)\n case 'industry':\n case 'rating': {\n // A choice is the key; a label typed into a stored view keys the same way.\n const keys = values.map((value) => crmPicklistKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf(clause.field === 'industry' ? 'industryKey' : 'ratingKey', keys)\n }\n case 'campaignIds': {\n if (values.length !== 1) return { refused: 'pick one campaign' }\n if (!reader.scopeTokens) return { field: 'campaignIds', op: 'contains', value: values[0] }\n if (!reader.foldsScope) {\n return {\n refused:\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it',\n }\n }\n return {\n field: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n op: 'isAnyOf',\n value: reader.scopeTokens.map((token) => `${token}${SCOPED_SEARCH_JOIN}${values[0]}`).join(','),\n }\n }\n default:\n return { refused: 'this list does not filter by that' }\n }\n}\n\n/**\n * The query shape the Leads list can send beyond its own declaration, for\n * the index pin (AGL-3321): a collaborator's address prefix beside the\n * equalities they may set.\n */\nexport function leadIndexShapes(): ListQueryDeclaration[] {\n const equalities = LEAD_QUERY_FIELDS.filter((field) => !field.tokensPath)\n return [{ fields: [LEAD_PREFIX_SEARCH.field, ...equalities], sorts: [] }]\n}\n"],"names":["EMAIL_STATE_LABELS","EMAIL_STATE_STATUSES","CRM_LEAD_STATUS_LABELS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","CRM_LEAD_STATUSES","crmLeadSourceKey","crmPicklistKey","SCOPED_SEARCH_JOIN","listSelectCodec","LIST_QUERY_DISJUNCTIONS","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","crmSelectField","isLeadSourceDirection","LEAD_SOURCE_DIRECTION_LABELS","LEAD_SOURCE_DIRECTIONS","leadSourceLabelsOfDirection","STANDARD_LEAD_SOURCES","LEAD_FILTERS","LEAD_FILTER_LABELS","open","all","new","nurturing","working","qualified","unqualified","LEAD_EMAIL_FILTERS","LEAD_EMAIL_FILTER_LABELS","any","problem","none","LEAD_SOURCE_FILTER_NONE","LEAD_LIST_FILTER_FIELDS","column","kind","path","operators","LEAD_PICKLIST_FILTERS","picklistId","header","LEAD_LIST_FILTER_HEADERS","status","emailState","ownerUid","leadSource","leadSourceDirection","campaignIds","industry","rating","LEAD_STATUS_FILTER_OPTIONS","filter","option","map","value","label","LEAD_EMAIL_FILTER_OPTIONS","LEAD_SOURCE_DIRECTION_FILTER_OPTIONS","direction","leadStatusFilterOptions","picklist","named","labels","values","meaning","active","picklistMeaningName","length","join","STATUS_OPEN","field","op","STATUS_ALL","leadClausesForGrid","stored","find","clause","leadClausesToStore","shown","LEAD_CAMPAIGN_CODEC","toItem","operator","toClause","item","LEAD_SOURCE_CODEC","LEAD_FILTER_CODECS","LEAD_LIST_SORTS","LEAD_QUERY_FIELDS","presence","tokensPath","LEAD_LIST_DECLARATION","fields","sorts","search","LEAD_PREFIX_SEARCH","lowerPath","notice","leadClauseImpliesScope","EMAIL_PROBLEMS","ORG_READER","scopeTokens","foldsScope","leadQueryClause","reader","statuses","push","includes","refused","keys","key","Boolean","leadSources","unique","Set","token","leadIndexShapes","equalities"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type CrmPicklist } from '@aglyn/aglyn/app-utils/crm';
18
+ /** The directions a lead source can have: the Lead source picklist's groups. */
19
+ export type LeadSourceDirection = 'inbound' | 'outbound';
20
+ export declare const LEAD_SOURCE_DIRECTIONS: readonly LeadSourceDirection[];
21
+ /** How each direction reads, from the picklist's own group labels. */
22
+ export declare const LEAD_SOURCE_DIRECTION_LABELS: Readonly<Record<LeadSourceDirection, string>>;
23
+ /** A lead with no lead source, or one in no group. */
24
+ export declare const LEAD_SOURCE_UNSPECIFIED_LABEL = "Unspecified";
25
+ export declare function isLeadSourceDirection(value: unknown): value is LeadSourceDirection;
26
+ /**
27
+ * The direction a lead source label has in `picklist` — `null` for no label,
28
+ * a value in no group, or a label the list does not hold.
29
+ */
30
+ export declare function leadSourceDirectionOf(picklist: CrmPicklist, label: unknown): LeadSourceDirection | null;
31
+ /**
32
+ * Every label in `picklist` whose value is in `direction` — the inactive
33
+ * ones too, since a record keeps a value after it is deactivated and a
34
+ * direction asked of the records must still find it.
35
+ */
36
+ export declare function leadSourceLabelsOfDirection(picklist: CrmPicklist, direction: LeadSourceDirection): string[];
37
+ /** The standard values alone — the answer before an organization's own list is read. */
38
+ export declare const STANDARD_LEAD_SOURCES: CrmPicklist;