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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. package/README.md +2 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/activity-list.js +12 -5
  4. package/src/lib/components/activity-list.js.map +1 -1
  5. package/src/lib/components/companies-bulk-bar.d.ts +1 -4
  6. package/src/lib/components/companies-bulk-bar.js +7 -20
  7. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  8. package/src/lib/components/companies-section.d.ts +3 -3
  9. package/src/lib/components/companies-section.js +149 -46
  10. package/src/lib/components/companies-section.js.map +1 -1
  11. package/src/lib/components/company-edit-drawer.js +281 -20
  12. package/src/lib/components/company-edit-drawer.js.map +1 -1
  13. package/src/lib/components/company-properties-card.js +99 -8
  14. package/src/lib/components/company-properties-card.js.map +1 -1
  15. package/src/lib/components/contact-detail-page.js +3 -1
  16. package/src/lib/components/contact-detail-page.js.map +1 -1
  17. package/src/lib/components/contact-merge-dialog.js +33 -10
  18. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  19. package/src/lib/components/contact-properties-card.d.ts +0 -69
  20. package/src/lib/components/contact-properties-card.js +350 -60
  21. package/src/lib/components/contact-properties-card.js.map +1 -1
  22. package/src/lib/components/contact-reports-to-field.d.ts +46 -0
  23. package/src/lib/components/contact-reports-to-field.js +67 -0
  24. package/src/lib/components/contact-reports-to-field.js.map +1 -0
  25. package/src/lib/components/contacts-bulk-bar.d.ts +1 -8
  26. package/src/lib/components/contacts-bulk-bar.js +7 -19
  27. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  28. package/src/lib/components/contacts-section.js +46 -33
  29. package/src/lib/components/contacts-section.js.map +1 -1
  30. package/src/lib/components/crm-call-actions.d.ts +10 -0
  31. package/src/lib/components/crm-call-actions.js +26 -9
  32. package/src/lib/components/crm-call-actions.js.map +1 -1
  33. package/src/lib/components/crm-next-activity-column.js +1 -1
  34. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  35. package/src/lib/components/crm-record-picker.d.ts +15 -0
  36. package/src/lib/components/crm-record-picker.js +16 -10
  37. package/src/lib/components/crm-record-picker.js.map +1 -1
  38. package/src/lib/components/crm-transfer-buttons.d.ts +54 -0
  39. package/src/lib/components/crm-transfer-buttons.js +167 -0
  40. package/src/lib/components/crm-transfer-buttons.js.map +1 -0
  41. package/src/lib/components/deal-contact-roles-card.d.ts +46 -0
  42. package/src/lib/components/deal-contact-roles-card.js +368 -0
  43. package/src/lib/components/deal-contact-roles-card.js.map +1 -0
  44. package/src/lib/components/deal-detail-page.d.ts +3 -2
  45. package/src/lib/components/deal-detail-page.js +25 -6
  46. package/src/lib/components/deal-detail-page.js.map +1 -1
  47. package/src/lib/components/deal-edit-drawer.d.ts +10 -0
  48. package/src/lib/components/deal-edit-drawer.js +219 -10
  49. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  50. package/src/lib/components/deal-properties-card.d.ts +7 -0
  51. package/src/lib/components/deal-properties-card.js +34 -3
  52. package/src/lib/components/deal-properties-card.js.map +1 -1
  53. package/src/lib/components/deal-stage-card.d.ts +4 -0
  54. package/src/lib/components/deal-stage-card.js +10 -2
  55. package/src/lib/components/deal-stage-card.js.map +1 -1
  56. package/src/lib/components/deals-bulk-bar.d.ts +0 -3
  57. package/src/lib/components/deals-bulk-bar.js +7 -20
  58. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  59. package/src/lib/components/deals-section.d.ts +3 -3
  60. package/src/lib/components/deals-section.js +123 -63
  61. package/src/lib/components/deals-section.js.map +1 -1
  62. package/src/lib/components/fields-section.js +31 -17
  63. package/src/lib/components/fields-section.js.map +1 -1
  64. package/src/lib/components/lead-convert-dialog.d.ts +4 -0
  65. package/src/lib/components/lead-convert-dialog.js +23 -1
  66. package/src/lib/components/lead-convert-dialog.js.map +1 -1
  67. package/src/lib/components/lead-detail-page.js +8 -2
  68. package/src/lib/components/lead-detail-page.js.map +1 -1
  69. package/src/lib/components/lead-profile-fields.d.ts +71 -0
  70. package/src/lib/components/lead-profile-fields.js +386 -0
  71. package/src/lib/components/lead-profile-fields.js.map +1 -0
  72. package/src/lib/components/lead-properties-card.d.ts +2 -1
  73. package/src/lib/components/lead-properties-card.js +85 -176
  74. package/src/lib/components/lead-properties-card.js.map +1 -1
  75. package/src/lib/components/lead-source-select.d.ts +4 -23
  76. package/src/lib/components/lead-source-select.js +9 -73
  77. package/src/lib/components/lead-source-select.js.map +1 -1
  78. package/src/lib/components/lead-status-chip.d.ts +9 -3
  79. package/src/lib/components/lead-status-chip.js +8 -4
  80. package/src/lib/components/lead-status-chip.js.map +1 -1
  81. package/src/lib/components/lead-status-options.d.ts +20 -0
  82. package/src/lib/components/lead-status-options.js +51 -0
  83. package/src/lib/components/lead-status-options.js.map +1 -0
  84. package/src/lib/components/lead-unqualify-dialog.d.ts +5 -0
  85. package/src/lib/components/lead-unqualify-dialog.js +50 -18
  86. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  87. package/src/lib/components/leads-bulk-bar.d.ts +0 -3
  88. package/src/lib/components/leads-bulk-bar.js +29 -29
  89. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  90. package/src/lib/components/leads-section.js +120 -68
  91. package/src/lib/components/leads-section.js.map +1 -1
  92. package/src/lib/components/linked-deals-card.d.ts +4 -0
  93. package/src/lib/components/linked-deals-card.js +47 -3
  94. package/src/lib/components/linked-deals-card.js.map +1 -1
  95. package/src/lib/components/log-activity-dialog.js +43 -3
  96. package/src/lib/components/log-activity-dialog.js.map +1 -1
  97. package/src/lib/components/new-contact-drawer.d.ts +8 -0
  98. package/src/lib/components/new-contact-drawer.js +108 -6
  99. package/src/lib/components/new-contact-drawer.js.map +1 -1
  100. package/src/lib/components/new-lead-drawer.d.ts +9 -2
  101. package/src/lib/components/new-lead-drawer.js +88 -121
  102. package/src/lib/components/new-lead-drawer.js.map +1 -1
  103. package/src/lib/components/picklist-select.d.ts +36 -0
  104. package/src/lib/components/picklist-select.js +114 -0
  105. package/src/lib/components/picklist-select.js.map +1 -0
  106. package/src/lib/components/picklist-values-card.d.ts +34 -0
  107. package/src/lib/components/{lead-source-values-card.js → picklist-values-card.js} +241 -58
  108. package/src/lib/components/picklist-values-card.js.map +1 -0
  109. package/src/lib/components/pipeline-stages-dialog.d.ts +7 -0
  110. package/src/lib/components/pipeline-stages-dialog.js +26 -4
  111. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  112. package/src/lib/components/pipelines-dialog.js +15 -6
  113. package/src/lib/components/pipelines-dialog.js.map +1 -1
  114. package/src/lib/components/recent-activity-feed.js +20 -5
  115. package/src/lib/components/recent-activity-feed.js.map +1 -1
  116. package/src/lib/components/recipes-card.js +4 -2
  117. package/src/lib/components/recipes-card.js.map +1 -1
  118. package/src/lib/components/record-tasks-card.js +15 -8
  119. package/src/lib/components/record-tasks-card.js.map +1 -1
  120. package/src/lib/components/reports/forecast-card.d.ts +6 -0
  121. package/src/lib/components/reports/forecast-card.js +101 -3
  122. package/src/lib/components/reports/forecast-card.js.map +1 -1
  123. package/src/lib/components/reports/lead-funnel-card.d.ts +12 -0
  124. package/src/lib/components/reports/lead-funnel-card.js +9 -5
  125. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  126. package/src/lib/components/reports/lead-source-card.d.ts +23 -0
  127. package/src/lib/components/reports/lead-source-card.js +318 -0
  128. package/src/lib/components/reports/lead-source-card.js.map +1 -0
  129. package/src/lib/components/reports/pipeline-card.js +2 -1
  130. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  131. package/src/lib/components/reports/report-export.js +1 -1
  132. package/src/lib/components/reports/report-export.js.map +1 -1
  133. package/src/lib/components/reports-section.js +8 -0
  134. package/src/lib/components/reports-section.js.map +1 -1
  135. package/src/lib/components/sharing-rule-drawer.d.ts +5 -0
  136. package/src/lib/components/sharing-rule-drawer.js +108 -19
  137. package/src/lib/components/sharing-rule-drawer.js.map +1 -1
  138. package/src/lib/components/sharing-rules-card.js +11 -3
  139. package/src/lib/components/sharing-rules-card.js.map +1 -1
  140. package/src/lib/components/task-cells.d.ts +9 -1
  141. package/src/lib/components/task-cells.js +9 -4
  142. package/src/lib/components/task-cells.js.map +1 -1
  143. package/src/lib/components/task-edit-drawer.js +111 -43
  144. package/src/lib/components/task-edit-drawer.js.map +1 -1
  145. package/src/lib/components/tasks-bulk-bar.d.ts +0 -3
  146. package/src/lib/components/tasks-bulk-bar.js +7 -20
  147. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
  148. package/src/lib/components/tasks-calendar.d.ts +3 -0
  149. package/src/lib/components/tasks-calendar.js +8 -1
  150. package/src/lib/components/tasks-calendar.js.map +1 -1
  151. package/src/lib/components/tasks-section.d.ts +3 -3
  152. package/src/lib/components/tasks-section.js +58 -43
  153. package/src/lib/components/tasks-section.js.map +1 -1
  154. package/src/lib/constants/api-routes.d.ts +2 -9
  155. package/src/lib/constants/api-routes.js +1 -7
  156. package/src/lib/constants/api-routes.js.map +1 -1
  157. package/src/lib/constants/bundle-common.d.ts +6 -0
  158. package/src/lib/constants/bundle-common.js +11 -0
  159. package/src/lib/constants/bundle-common.js.map +1 -1
  160. package/src/lib/constants/company-filters.d.ts +14 -0
  161. package/src/lib/constants/company-filters.js +51 -1
  162. package/src/lib/constants/company-filters.js.map +1 -1
  163. package/src/lib/constants/contact-filters.d.ts +2 -0
  164. package/src/lib/constants/contact-filters.js +18 -1
  165. package/src/lib/constants/contact-filters.js.map +1 -1
  166. package/src/lib/constants/deal-filters.d.ts +22 -1
  167. package/src/lib/constants/deal-filters.js +67 -4
  168. package/src/lib/constants/deal-filters.js.map +1 -1
  169. package/src/lib/declarations.console-server.d.ts +3 -0
  170. package/src/lib/declarations.console-server.js +35 -0
  171. package/src/lib/declarations.console-server.js.map +1 -1
  172. package/src/lib/declarations.d.ts +26 -0
  173. package/src/lib/declarations.js +35 -0
  174. package/src/lib/declarations.js.map +1 -0
  175. package/src/lib/declarations.server.d.ts +11 -0
  176. package/src/lib/declarations.server.js +97 -1
  177. package/src/lib/declarations.server.js.map +1 -1
  178. package/src/lib/hooks/use-company-picklists.d.ts +15 -0
  179. package/src/lib/hooks/use-company-picklists.js +55 -0
  180. package/src/lib/hooks/use-company-picklists.js.map +1 -0
  181. package/src/lib/hooks/use-crm-picklist.d.ts +25 -0
  182. package/src/lib/hooks/use-crm-picklist.js +53 -0
  183. package/src/lib/hooks/use-crm-picklist.js.map +1 -0
  184. package/src/lib/hooks/use-crm-record-names.d.ts +16 -0
  185. package/src/lib/hooks/use-crm-record-names.js +45 -19
  186. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  187. package/src/lib/hooks/use-crm-task-picklists.d.ts +11 -0
  188. package/src/lib/hooks/use-crm-task-picklists.js +45 -0
  189. package/src/lib/hooks/use-crm-task-picklists.js.map +1 -0
  190. package/src/lib/hooks/use-deals.d.ts +14 -0
  191. package/src/lib/hooks/use-deals.js +29 -0
  192. package/src/lib/hooks/use-deals.js.map +1 -1
  193. package/src/lib/hooks/use-lead-picklists.d.ts +14 -0
  194. package/src/lib/hooks/use-lead-picklists.js +46 -0
  195. package/src/lib/hooks/use-lead-picklists.js.map +1 -0
  196. package/src/lib/hooks/use-lead-source-picklist.d.ts +5 -19
  197. package/src/lib/hooks/use-lead-source-picklist.js +12 -22
  198. package/src/lib/hooks/use-lead-source-picklist.js.map +1 -1
  199. package/src/lib/hooks/use-lead-status-picklist.d.ts +9 -0
  200. package/src/lib/hooks/use-lead-status-picklist.js +40 -0
  201. package/src/lib/hooks/use-lead-status-picklist.js.map +1 -0
  202. package/src/lib/model/companies.d.ts +32 -3
  203. package/src/lib/model/companies.js +133 -16
  204. package/src/lib/model/companies.js.map +1 -1
  205. package/src/lib/model/contact-merge.d.ts +27 -3
  206. package/src/lib/model/contact-merge.js +81 -7
  207. package/src/lib/model/contact-merge.js.map +1 -1
  208. package/src/lib/model/contact-record.d.ts +17 -0
  209. package/src/lib/model/contact-record.js +17 -2
  210. package/src/lib/model/contact-record.js.map +1 -1
  211. package/src/lib/model/contact-update.d.ts +31 -2
  212. package/src/lib/model/contact-update.js +14 -0
  213. package/src/lib/model/contact-update.js.map +1 -1
  214. package/src/lib/model/crm-list-query.d.ts +5 -0
  215. package/src/lib/model/crm-list-query.js +8 -0
  216. package/src/lib/model/crm-list-query.js.map +1 -1
  217. package/src/lib/model/crm-next-activity.d.ts +107 -0
  218. package/src/lib/model/crm-next-activity.js +140 -0
  219. package/src/lib/model/crm-next-activity.js.map +1 -0
  220. package/src/lib/model/crm-recipes.d.ts +40 -0
  221. package/src/lib/model/crm-recipes.js +259 -0
  222. package/src/lib/model/crm-recipes.js.map +1 -0
  223. package/src/lib/model/crm-record-counts.d.ts +36 -0
  224. package/src/lib/model/crm-record-counts.js +54 -0
  225. package/src/lib/model/crm-record-counts.js.map +1 -0
  226. package/src/lib/model/crm-record-lists.d.ts +23 -0
  227. package/src/lib/model/crm-record-lists.js +161 -0
  228. package/src/lib/model/crm-record-lists.js.map +1 -0
  229. package/src/lib/model/crm-reports.d.ts +73 -3
  230. package/src/lib/model/crm-reports.js +120 -1
  231. package/src/lib/model/crm-reports.js.map +1 -1
  232. package/src/lib/model/crm-sharing.d.ts +21 -3
  233. package/src/lib/model/crm-sharing.js +27 -6
  234. package/src/lib/model/crm-sharing.js.map +1 -1
  235. package/src/lib/model/crm-task-reminders.d.ts +152 -0
  236. package/src/lib/model/crm-task-reminders.js +161 -0
  237. package/src/lib/model/crm-task-reminders.js.map +1 -0
  238. package/src/lib/model/deal-board-model.d.ts +8 -2
  239. package/src/lib/model/deal-board-model.js +15 -2
  240. package/src/lib/model/deal-board-model.js.map +1 -1
  241. package/src/lib/model/deal-contact-roles.d.ts +45 -0
  242. package/src/lib/model/deal-contact-roles.js +73 -0
  243. package/src/lib/model/deal-contact-roles.js.map +1 -0
  244. package/src/lib/model/deal-form-model.d.ts +56 -0
  245. package/src/lib/model/deal-form-model.js +79 -7
  246. package/src/lib/model/deal-form-model.js.map +1 -1
  247. package/src/lib/model/download-text-file.d.ts +23 -0
  248. package/src/lib/model/download-text-file.js +34 -0
  249. package/src/lib/model/download-text-file.js.map +1 -0
  250. package/src/lib/model/lead-filters.d.ts +36 -2
  251. package/src/lib/model/lead-filters.js +123 -2
  252. package/src/lib/model/lead-filters.js.map +1 -1
  253. package/src/lib/model/lead-source-direction.d.ts +38 -0
  254. package/src/lib/model/lead-source-direction.js +52 -0
  255. package/src/lib/model/lead-source-direction.js.map +1 -0
  256. package/src/lib/model/next-activity.d.ts +1 -1
  257. package/src/lib/model/next-activity.js.map +1 -1
  258. package/src/lib/model/picklist-values.d.ts +119 -0
  259. package/src/lib/model/picklist-values.js +256 -0
  260. package/src/lib/model/picklist-values.js.map +1 -0
  261. package/src/lib/model/record-facts-disclosed.d.ts +81 -0
  262. package/src/lib/model/record-facts-disclosed.js +281 -0
  263. package/src/lib/model/record-facts-disclosed.js.map +1 -0
  264. package/src/lib/model/record-facts.d.ts +215 -34
  265. package/src/lib/model/record-facts.js +307 -128
  266. package/src/lib/model/record-facts.js.map +1 -1
  267. package/src/lib/model/task-picklists.d.ts +52 -0
  268. package/src/lib/model/task-picklists.js +83 -0
  269. package/src/lib/model/task-picklists.js.map +1 -0
  270. package/src/lib/model/task-routes.d.ts +13 -0
  271. package/src/lib/model/task-routes.js +23 -4
  272. package/src/lib/model/task-routes.js.map +1 -1
  273. package/src/lib/plan-entitlements.js +6 -4
  274. package/src/lib/plan-entitlements.js.map +1 -1
  275. package/src/lib/plugin.js +17 -0
  276. package/src/lib/plugin.js.map +1 -1
  277. package/src/lib/server/api-v1/contacts.js +220 -14
  278. package/src/lib/server/api-v1/contacts.js.map +1 -1
  279. package/src/lib/server/api-v1/crm-activities.js +14 -1
  280. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  281. package/src/lib/server/api-v1/crm-companies.js +123 -14
  282. package/src/lib/server/api-v1/crm-companies.js.map +1 -1
  283. package/src/lib/server/api-v1/crm-deals.js +291 -26
  284. package/src/lib/server/api-v1/crm-deals.js.map +1 -1
  285. package/src/lib/server/api-v1/crm-leads.js +144 -38
  286. package/src/lib/server/api-v1/crm-leads.js.map +1 -1
  287. package/src/lib/server/api-v1/crm-pipelines.d.ts +6 -0
  288. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  289. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  290. package/src/lib/server/api-v1/crm-tasks.js +78 -34
  291. package/src/lib/server/api-v1/crm-tasks.js.map +1 -1
  292. package/src/lib/server/api-v1/openapi.d.ts +0 -11
  293. package/src/lib/server/api-v1/openapi.js +171 -16
  294. package/src/lib/server/api-v1/openapi.js.map +1 -1
  295. package/src/lib/server/assign-contact-owner.d.ts +188 -0
  296. package/src/lib/server/assign-contact-owner.js +446 -0
  297. package/src/lib/server/assign-contact-owner.js.map +1 -0
  298. package/src/lib/server/associate-company-by-domain.d.ts +70 -0
  299. package/src/lib/server/associate-company-by-domain.js +153 -0
  300. package/src/lib/server/associate-company-by-domain.js.map +1 -0
  301. package/src/lib/server/automation-steps.d.ts +140 -0
  302. package/src/lib/server/automation-steps.js +669 -0
  303. package/src/lib/server/automation-steps.js.map +1 -0
  304. package/src/lib/server/capture-contact.d.ts +17 -0
  305. package/src/lib/server/capture-contact.js +95 -26
  306. package/src/lib/server/capture-contact.js.map +1 -1
  307. package/src/lib/server/capture-host-contact.d.ts +104 -0
  308. package/src/lib/server/capture-host-contact.js +151 -0
  309. package/src/lib/server/capture-host-contact.js.map +1 -0
  310. package/src/lib/server/company-children.d.ts +25 -0
  311. package/src/lib/server/company-children.js +42 -0
  312. package/src/lib/server/company-children.js.map +1 -0
  313. package/src/lib/server/company-delete.d.ts +18 -0
  314. package/src/lib/server/company-delete.js +44 -21
  315. package/src/lib/server/company-delete.js.map +1 -1
  316. package/src/lib/server/contact-email-history.js +21 -19
  317. package/src/lib/server/contact-email-history.js.map +1 -1
  318. package/src/lib/server/contact-remove.js +27 -2
  319. package/src/lib/server/contact-remove.js.map +1 -1
  320. package/src/lib/server/contact-reports-to.d.ts +61 -0
  321. package/src/lib/server/contact-reports-to.js +114 -0
  322. package/src/lib/server/contact-reports-to.js.map +1 -0
  323. package/src/lib/server/contact-update.d.ts +42 -1
  324. package/src/lib/server/contact-update.js +204 -23
  325. package/src/lib/server/contact-update.js.map +1 -1
  326. package/src/lib/server/convert-host-lead.d.ts +46 -1
  327. package/src/lib/server/convert-host-lead.js +173 -15
  328. package/src/lib/server/convert-host-lead.js.map +1 -1
  329. package/src/lib/server/convert-open-lead.js +12 -5
  330. package/src/lib/server/convert-open-lead.js.map +1 -1
  331. package/src/lib/server/crm-email-activity.d.ts +118 -0
  332. package/src/lib/server/crm-email-activity.js +148 -0
  333. package/src/lib/server/crm-email-activity.js.map +1 -0
  334. package/src/lib/server/crm-inbound-email.js +1 -1
  335. package/src/lib/server/crm-inbound-email.js.map +1 -1
  336. package/src/lib/server/crm-next-activity.d.ts +74 -0
  337. package/src/lib/server/crm-next-activity.js +194 -0
  338. package/src/lib/server/crm-next-activity.js.map +1 -0
  339. package/src/lib/server/crm-record-indexes.d.ts +45 -0
  340. package/src/lib/server/crm-record-indexes.js +78 -0
  341. package/src/lib/server/crm-record-indexes.js.map +1 -0
  342. package/src/lib/server/crm-sharing.d.ts +9 -1
  343. package/src/lib/server/crm-sharing.js +23 -3
  344. package/src/lib/server/crm-sharing.js.map +1 -1
  345. package/src/lib/server/deal-contact-roles.d.ts +27 -0
  346. package/src/lib/server/deal-contact-roles.js +89 -0
  347. package/src/lib/server/deal-contact-roles.js.map +1 -0
  348. package/src/lib/server/email-send.js +2 -1
  349. package/src/lib/server/email-send.js.map +1 -1
  350. package/src/lib/server/lead-convert.d.ts +5 -0
  351. package/src/lib/server/lead-convert.js +19 -3
  352. package/src/lib/server/lead-convert.js.map +1 -1
  353. package/src/lib/server/lead-create.d.ts +17 -2
  354. package/src/lib/server/lead-create.js +46 -14
  355. package/src/lib/server/lead-create.js.map +1 -1
  356. package/src/lib/server/lead-nurturing.d.ts +6 -0
  357. package/src/lib/server/lead-nurturing.js +57 -20
  358. package/src/lib/server/lead-nurturing.js.map +1 -1
  359. package/src/lib/server/lead-picklists.d.ts +35 -0
  360. package/src/lib/server/lead-picklists.js +52 -0
  361. package/src/lib/server/lead-picklists.js.map +1 -0
  362. package/src/lib/server/lead-source-picklist.d.ts +1 -13
  363. package/src/lib/server/lead-source-picklist.js +6 -33
  364. package/src/lib/server/lead-source-picklist.js.map +1 -1
  365. package/src/lib/server/merge-contacts.js +23 -5
  366. package/src/lib/server/merge-contacts.js.map +1 -1
  367. package/src/lib/server/next-activity-routes.js +2 -1
  368. package/src/lib/server/next-activity-routes.js.map +1 -1
  369. package/src/lib/server/person-eraser.d.ts +53 -0
  370. package/src/lib/server/person-eraser.js +149 -0
  371. package/src/lib/server/person-eraser.js.map +1 -0
  372. package/src/lib/server/person-records.d.ts +69 -0
  373. package/src/lib/server/person-records.js +317 -0
  374. package/src/lib/server/person-records.js.map +1 -0
  375. package/src/lib/server/person-refund.d.ts +97 -0
  376. package/src/lib/server/person-refund.js +221 -0
  377. package/src/lib/server/person-refund.js.map +1 -0
  378. package/src/lib/server/picklist-values.d.ts +21 -0
  379. package/src/lib/server/picklist-values.js +308 -0
  380. package/src/lib/server/picklist-values.js.map +1 -0
  381. package/src/lib/server/read-picklist.d.ts +44 -0
  382. package/src/lib/server/read-picklist.js +65 -0
  383. package/src/lib/server/read-picklist.js.map +1 -0
  384. package/src/lib/server/recipe-routes.js +22 -8
  385. package/src/lib/server/recipe-routes.js.map +1 -1
  386. package/src/lib/server/record-email-state.js +7 -1
  387. package/src/lib/server/record-email-state.js.map +1 -1
  388. package/src/lib/server/record-facts.d.ts +3 -0
  389. package/src/lib/server/record-facts.js +233 -31
  390. package/src/lib/server/record-facts.js.map +1 -1
  391. package/src/lib/server/record-origin.d.ts +27 -0
  392. package/src/lib/server/record-origin.js +104 -0
  393. package/src/lib/server/record-origin.js.map +1 -0
  394. package/src/lib/server/record-timeline.js +40 -3
  395. package/src/lib/server/record-timeline.js.map +1 -1
  396. package/src/lib/server/suite-gate.js +4 -3
  397. package/src/lib/server/suite-gate.js.map +1 -1
  398. package/src/lib/server/task-reminders-route.js +2 -1
  399. package/src/lib/server/task-reminders-route.js.map +1 -1
  400. package/src/lib/server/task-routes.js +33 -17
  401. package/src/lib/server/task-routes.js.map +1 -1
  402. package/src/lib/server-deal-stage.d.ts +5 -1
  403. package/src/lib/server-deal-stage.js +10 -5
  404. package/src/lib/server-deal-stage.js.map +1 -1
  405. package/src/lib/server.d.ts +8 -3
  406. package/src/lib/server.js +47 -43
  407. package/src/lib/server.js.map +1 -1
  408. package/src/lib/testing/memory-firestore.d.ts +55 -0
  409. package/src/lib/testing/memory-firestore.js +335 -0
  410. package/src/lib/testing/memory-firestore.js.map +1 -0
  411. package/src/lib/testing/stand-in-email-send-index.d.ts +25 -0
  412. package/src/lib/testing/stand-in-email-send-index.js +49 -0
  413. package/src/lib/testing/stand-in-email-send-index.js.map +1 -0
  414. package/src/lib/transfer/activities.d.ts +30 -0
  415. package/src/lib/transfer/activities.js +132 -0
  416. package/src/lib/transfer/activities.js.map +1 -0
  417. package/src/lib/transfer/aliases.d.ts +22 -0
  418. package/src/lib/transfer/aliases.js +1342 -0
  419. package/src/lib/transfer/aliases.js.map +1 -0
  420. package/src/lib/transfer/common.d.ts +222 -0
  421. package/src/lib/transfer/common.js +832 -0
  422. package/src/lib/transfer/common.js.map +1 -0
  423. package/src/lib/transfer/companies.d.ts +43 -0
  424. package/src/lib/transfer/companies.js +549 -0
  425. package/src/lib/transfer/companies.js.map +1 -0
  426. package/src/lib/transfer/contacts.d.ts +31 -0
  427. package/src/lib/transfer/contacts.js +727 -0
  428. package/src/lib/transfer/contacts.js.map +1 -0
  429. package/src/lib/transfer/deals.d.ts +66 -0
  430. package/src/lib/transfer/deals.js +698 -0
  431. package/src/lib/transfer/deals.js.map +1 -0
  432. package/src/lib/transfer/email-templates-package.d.ts +40 -0
  433. package/src/lib/transfer/email-templates-package.js +76 -0
  434. package/src/lib/transfer/email-templates-package.js.map +1 -0
  435. package/src/lib/transfer/email-templates-package.server.d.ts +32 -0
  436. package/src/lib/transfer/email-templates-package.server.js +176 -0
  437. package/src/lib/transfer/email-templates-package.server.js.map +1 -0
  438. package/src/lib/transfer/fields.d.ts +102 -0
  439. package/src/lib/transfer/fields.js +1755 -0
  440. package/src/lib/transfer/fields.js.map +1 -0
  441. package/src/lib/transfer/leads.d.ts +30 -0
  442. package/src/lib/transfer/leads.js +582 -0
  443. package/src/lib/transfer/leads.js.map +1 -0
  444. package/src/lib/transfer/pipelines.d.ts +21 -0
  445. package/src/lib/transfer/pipelines.js +172 -0
  446. package/src/lib/transfer/pipelines.js.map +1 -0
  447. package/src/lib/transfer/register.d.ts +47 -0
  448. package/src/lib/transfer/register.js +277 -0
  449. package/src/lib/transfer/register.js.map +1 -0
  450. package/src/lib/transfer/tasks.d.ts +30 -0
  451. package/src/lib/transfer/tasks.js +418 -0
  452. package/src/lib/transfer/tasks.js.map +1 -0
  453. package/src/lib/transfer/ui.d.ts +20 -0
  454. package/src/lib/transfer/ui.js +42 -0
  455. package/src/lib/transfer/ui.js.map +1 -0
  456. package/src/lib/components/company-import-drawer.d.ts +0 -42
  457. package/src/lib/components/company-import-drawer.js +0 -99
  458. package/src/lib/components/company-import-drawer.js.map +0 -1
  459. package/src/lib/components/contact-import-drawer.d.ts +0 -46
  460. package/src/lib/components/contact-import-drawer.js +0 -149
  461. package/src/lib/components/contact-import-drawer.js.map +0 -1
  462. package/src/lib/components/crm-export-all-button.d.ts +0 -39
  463. package/src/lib/components/crm-export-all-button.js +0 -98
  464. package/src/lib/components/crm-export-all-button.js.map +0 -1
  465. package/src/lib/components/csv-import-drawer.d.ts +0 -139
  466. package/src/lib/components/csv-import-drawer.js +0 -757
  467. package/src/lib/components/csv-import-drawer.js.map +0 -1
  468. package/src/lib/components/deal-import-drawer.d.ts +0 -24
  469. package/src/lib/components/deal-import-drawer.js +0 -83
  470. package/src/lib/components/deal-import-drawer.js.map +0 -1
  471. package/src/lib/components/lead-import-drawer.d.ts +0 -27
  472. package/src/lib/components/lead-import-drawer.js +0 -118
  473. package/src/lib/components/lead-import-drawer.js.map +0 -1
  474. package/src/lib/components/lead-source-values-card.d.ts +0 -27
  475. package/src/lib/components/lead-source-values-card.js.map +0 -1
  476. package/src/lib/components/task-import-drawer.d.ts +0 -24
  477. package/src/lib/components/task-import-drawer.js +0 -82
  478. package/src/lib/components/task-import-drawer.js.map +0 -1
  479. package/src/lib/model/companies-csv.d.ts +0 -36
  480. package/src/lib/model/companies-csv.js +0 -38
  481. package/src/lib/model/companies-csv.js.map +0 -1
  482. package/src/lib/model/contacts-csv.d.ts +0 -54
  483. package/src/lib/model/contacts-csv.js +0 -67
  484. package/src/lib/model/contacts-csv.js.map +0 -1
  485. package/src/lib/model/crm-company-import.d.ts +0 -154
  486. package/src/lib/model/crm-company-import.js +0 -331
  487. package/src/lib/model/crm-company-import.js.map +0 -1
  488. package/src/lib/model/crm-csv.d.ts +0 -239
  489. package/src/lib/model/crm-csv.js +0 -467
  490. package/src/lib/model/crm-csv.js.map +0 -1
  491. package/src/lib/model/crm-deal-import.d.ts +0 -112
  492. package/src/lib/model/crm-deal-import.js +0 -263
  493. package/src/lib/model/crm-deal-import.js.map +0 -1
  494. package/src/lib/model/crm-import.d.ts +0 -250
  495. package/src/lib/model/crm-import.js +0 -467
  496. package/src/lib/model/crm-import.js.map +0 -1
  497. package/src/lib/model/crm-lead-import.d.ts +0 -138
  498. package/src/lib/model/crm-lead-import.js +0 -446
  499. package/src/lib/model/crm-lead-import.js.map +0 -1
  500. package/src/lib/model/crm-task-import.d.ts +0 -94
  501. package/src/lib/model/crm-task-import.js +0 -275
  502. package/src/lib/model/crm-task-import.js.map +0 -1
  503. package/src/lib/model/deals-csv.d.ts +0 -32
  504. package/src/lib/model/deals-csv.js +0 -36
  505. package/src/lib/model/deals-csv.js.map +0 -1
  506. package/src/lib/model/import-column-shapes.d.ts +0 -36
  507. package/src/lib/model/import-column-shapes.js +0 -76
  508. package/src/lib/model/import-column-shapes.js.map +0 -1
  509. package/src/lib/model/lead-source-values.d.ts +0 -82
  510. package/src/lib/model/lead-source-values.js +0 -149
  511. package/src/lib/model/lead-source-values.js.map +0 -1
  512. package/src/lib/model/leads-csv.d.ts +0 -40
  513. package/src/lib/model/leads-csv.js +0 -40
  514. package/src/lib/model/leads-csv.js.map +0 -1
  515. package/src/lib/model/tasks-csv.d.ts +0 -28
  516. package/src/lib/model/tasks-csv.js +0 -28
  517. package/src/lib/model/tasks-csv.js.map +0 -1
  518. package/src/lib/server/companies-import.d.ts +0 -57
  519. package/src/lib/server/companies-import.js +0 -242
  520. package/src/lib/server/companies-import.js.map +0 -1
  521. package/src/lib/server/contacts-import.d.ts +0 -109
  522. package/src/lib/server/contacts-import.js +0 -393
  523. package/src/lib/server/contacts-import.js.map +0 -1
  524. package/src/lib/server/deals-import.d.ts +0 -76
  525. package/src/lib/server/deals-import.js +0 -248
  526. package/src/lib/server/deals-import.js.map +0 -1
  527. package/src/lib/server/export-route.d.ts +0 -60
  528. package/src/lib/server/export-route.js +0 -447
  529. package/src/lib/server/export-route.js.map +0 -1
  530. package/src/lib/server/import-context.d.ts +0 -87
  531. package/src/lib/server/import-context.js +0 -182
  532. package/src/lib/server/import-context.js.map +0 -1
  533. package/src/lib/server/lead-source-values.d.ts +0 -50
  534. package/src/lib/server/lead-source-values.js +0 -191
  535. package/src/lib/server/lead-source-values.js.map +0 -1
  536. package/src/lib/server/leads-import.d.ts +0 -84
  537. package/src/lib/server/leads-import.js +0 -367
  538. package/src/lib/server/leads-import.js.map +0 -1
  539. package/src/lib/server/tasks-import.d.ts +0 -54
  540. package/src/lib/server/tasks-import.js +0 -160
  541. package/src/lib/server/tasks-import.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/crm-reports.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 * CRM reporting arithmetic (AGL-2604).\n *\n * The Reports section and the dashboard glance card read Firestore\n * aggregates and bounded windows and then have to turn them into a period, a\n * set of weekly bars, a funnel and a pipeline total. Every one of those is a\n * decision about numbers that a card could get subtly wrong in its own JSX —\n * a bucket boundary that is closed on both ends counts a midnight contact\n * twice, a funnel that divides by the step before reads 100% where nobody\n * arrived, a forecast that weights a closed deal forecasts a sale twice. So\n * they live here, pure and specified, and the cards only draw.\n *\n * Pure data module: no Firestore, no React, no clock — every function that\n * needs \"now\" is handed it.\n */\n\nimport type { ContactFacet, ContactSource } from '@aglyn/aglyn/app-utils/contacts'\nimport {\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CRM_ACTIVITY_KINDS,\n CRM_LEAD_STATUSES,\n type ContactLifecycleStage,\n type CrmActivity,\n type CrmActivityKind,\n type CrmDeal,\n type CrmDealStage,\n type CrmLeadFields,\n type CrmLeadStatus,\n type CrmPipeline,\n type CrmTask,\n crmLeadStatus,\n dealStageById,\n isCrmActivityKind,\n isPipelineArchived,\n weightedDealAmountCents,\n} from '@aglyn/aglyn/app-utils/crm'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst WEEK_MS = 7 * DAY_MS\n\n/**\n * The periods a report can be read over.\n *\n * A fixed list rather than a free date range because every period here is\n * also a READ COST: the contact trend runs one count aggregate per week of\n * the period, and the closed-deal chart reads the deals closed within it. A\n * picker that offered \"all time\" would offer a read nobody has sized.\n */\nexport const CRM_REPORT_PERIODS = ['7d', '30d', '90d', 'month'] as const\n\nexport type CrmReportPeriod = (typeof CRM_REPORT_PERIODS)[number]\n\nexport const CRM_REPORT_PERIOD_LABELS: Record<CrmReportPeriod, string> = {\n '7d': 'Last 7 days',\n '30d': 'Last 30 days',\n '90d': 'Last 90 days',\n month: 'This month',\n}\n\n/**\n * A period and the period before it, as half-open millisecond ranges\n * `[from, to)`.\n *\n * The previous period is what a delta is measured against, so it is always\n * the same LENGTH as the current one and ends exactly where the current one\n * begins: \"this month\" against \"last month\", thirty days against the thirty\n * before them. A gap or an overlap between the two would make the comparison\n * a comparison of different things.\n */\nexport interface CrmReportRange {\n from: number\n to: number\n previousFrom: number\n previousTo: number\n}\n\n/**\n * The range a period names at a given moment.\n *\n * `to` is the moment itself, so a period is always \"up to now\" and a report\n * read twice in one sitting shows the same figures with the newer minutes\n * added. \"This month\" starts on the first of the LOCAL month, because that\n * is the month the reader sees on their calendar; the day periods count back\n * from the moment rather than from midnight, so \"last 7 days\" is a full\n * seven days rather than six and a fraction.\n */\nexport function crmReportRange(\n period: CrmReportPeriod,\n nowMs: number,\n): CrmReportRange {\n if (period === 'month') {\n const now = new Date(nowMs)\n const from = new Date(now.getFullYear(), now.getMonth(), 1).getTime()\n const previousFrom = new Date(\n now.getFullYear(),\n now.getMonth() - 1,\n 1,\n ).getTime()\n return { from, to: nowMs, previousFrom, previousTo: from }\n }\n const days = period === '7d' ? 7 : period === '30d' ? 30 : 90\n const from = nowMs - days * DAY_MS\n return {\n from,\n to: nowMs,\n previousFrom: from - days * DAY_MS,\n previousTo: from,\n }\n}\n\n/** One week of a period, `[start, end)`; the last week of a period may be short. */\nexport interface WeekBucketBounds {\n start: number\n end: number\n}\n\n/**\n * The weeks a range divides into: seven-day strides from `from`, the last\n * clipped at `to`.\n *\n * Strides from the START of the period rather than calendar weeks, so that\n * \"last 7 days\" is one bar and \"last 30 days\" is four full weeks and two\n * days — the period the reader picked, in the reader's units — instead of\n * whatever fragment of a Monday-to-Sunday week the period happened to start\n * inside. An empty or inverted range has no weeks.\n */\nexport function weekBuckets(from: number, to: number): WeekBucketBounds[] {\n const buckets: WeekBucketBounds[] = []\n if (!(to > from)) return buckets\n for (let start = from; start < to; start += WEEK_MS) {\n buckets.push({ start, end: Math.min(start + WEEK_MS, to) })\n }\n return buckets\n}\n\nexport interface WeekBucket<T> extends WeekBucketBounds {\n items: T[]\n}\n\n/**\n * The items of a range, grouped into its weeks.\n *\n * Half-open on every boundary, so an item at exactly midnight between two\n * weeks is in the later one and only the later one, and an item at `to` is\n * outside the period the way an item at `from` is inside it. Items with no\n * usable time are dropped rather than placed anywhere — an undated deal is\n * not a deal closed this week. Every week is returned, empty ones included,\n * because a chart wants a bar per week and a gap where a week had nothing is\n * a chart with the wrong number of bars.\n */\nexport function bucketByWeek<T>(\n items: readonly T[],\n atMs: (item: T) => number | null | undefined,\n from: number,\n to: number,\n): WeekBucket<T>[] {\n const buckets: WeekBucket<T>[] = weekBuckets(from, to).map((bounds) => ({\n ...bounds,\n items: [] as T[],\n }))\n if (!buckets.length) return buckets\n for (const item of items) {\n const at = atMs(item)\n if (typeof at !== 'number' || !Number.isFinite(at)) continue\n if (at < from || at >= to) continue\n const index = Math.min(\n buckets.length - 1,\n Math.floor((at - from) / WEEK_MS),\n )\n buckets[index].items.push(item)\n }\n return buckets\n}\n\n/** One step of the lifecycle funnel. */\nexport interface LifecycleFunnelStep {\n stage: ContactLifecycleStage\n label: string\n /** People AT this stage. */\n count: number\n /** People at this stage or any later one — everyone who got at least this far. */\n reached: number\n /**\n * `reached` over the previous step's `reached`, 0–1, or `null` on the first\n * step and wherever nobody reached the step before.\n */\n conversion: number | null\n}\n\nexport interface LifecycleFunnel {\n /** The progression, in `CONTACT_LIFECYCLE_STAGES` order, `other` left out. */\n steps: LifecycleFunnelStep[]\n /** People at the `other` stage, which is beside the funnel rather than in it. */\n other: number\n /** Every counted person, `other` included. */\n total: number\n}\n\n/**\n * The lifecycle funnel a set of per-stage counts describes.\n *\n * A funnel is read CUMULATIVELY: a customer was once a lead, so the \"lead\"\n * step is everyone who is a lead now or has moved past it, not only the\n * people currently sitting there. That is what makes the steps narrow as\n * they go down and what makes a step-to-step conversion mean something.\n * `other` is a stage a merchant chose for a person the funnel does not\n * describe, so it is reported beside the funnel and never folded into a\n * step. A conversion where the previous step was empty is `null` rather than\n * 0 or 100 — there is nothing to divide by, and either number would be read\n * as a measurement.\n */\nexport function funnelFromStages(\n counts: Partial<Record<ContactLifecycleStage, number>>,\n): LifecycleFunnel {\n const countOf = (stage: ContactLifecycleStage): number => {\n const value = Number(counts[stage] ?? 0)\n return Number.isFinite(value) && value > 0 ? Math.round(value) : 0\n }\n const progression = CONTACT_LIFECYCLE_STAGES.filter(\n (stage) => stage !== 'other',\n )\n const steps: LifecycleFunnelStep[] = []\n let reachedAfter = 0\n for (let index = progression.length - 1; index >= 0; index -= 1) {\n const stage = progression[index]\n const count = countOf(stage)\n const reached = count + reachedAfter\n steps.unshift({\n stage,\n label: CONTACT_LIFECYCLE_STAGE_LABELS[stage],\n count,\n reached,\n conversion: null,\n })\n reachedAfter = reached\n }\n for (let index = 1; index < steps.length; index += 1) {\n const previous = steps[index - 1].reached\n steps[index].conversion = previous > 0 ? steps[index].reached / previous : null\n }\n const other = countOf('other')\n return {\n steps,\n other,\n total: steps.reduce((sum, step) => sum + step.count, 0) + other,\n }\n}\n\n/** The open deals sitting in one stage of a pipeline. */\nexport interface PipelineStageTotal {\n stage: CrmDealStage\n count: number\n amountCents: number\n weightedCents: number\n}\n\nexport interface PipelineTotals {\n /** Open deals, wherever they sit. */\n count: number\n amountCents: number\n /** The forecast: each open deal at its stage's odds. */\n weightedCents: number\n /** The pipeline's open stages in order, every one present even when empty. */\n stages: PipelineStageTotal[]\n /**\n * Open deals in a stage the pipeline does not have — retired, renamed, or\n * a closed-kind stage the status disagrees with. Counted in the totals,\n * worth nothing to the forecast, and reported so a merchant can find them.\n */\n unplaced: { count: number; amountCents: number }\n}\n\ntype PipelineDeal = Pick<CrmDeal, 'status' | 'stageId' | 'amountCents'>\n\n/**\n * What a pipeline holds, by stage.\n *\n * Only OPEN deals: a won deal is revenue and a lost one is history, and a\n * pipeline report that counted either would show a stage full of deals that\n * cannot move. Only the pipeline's open-kind stages are rows, for the same\n * reason from the other side — a \"Won\" column in a pipeline chart is the\n * closed-deals report drawn in the wrong place. The weighting is\n * `weightedDealAmountCents`, applied here rather than restated.\n */\nexport function pipelineTotals(\n deals: readonly PipelineDeal[],\n pipeline: Pick<CrmPipeline, 'stages'> | null | undefined,\n): PipelineTotals {\n const stages = [...(pipeline?.stages ?? [])]\n .filter((stage) => stage.kind === 'open')\n .sort((a, b) => a.order - b.order)\n const rows = new Map<string, PipelineStageTotal>(\n stages.map((stage) => [\n stage.id,\n { stage, count: 0, amountCents: 0, weightedCents: 0 },\n ]),\n )\n const totals: PipelineTotals = {\n count: 0,\n amountCents: 0,\n weightedCents: 0,\n stages: stages.map((stage) => rows.get(stage.id) as PipelineStageTotal),\n unplaced: { count: 0, amountCents: 0 },\n }\n for (const deal of deals) {\n if (deal.status !== 'open') continue\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n totals.count += 1\n totals.amountCents += amount\n const row = rows.get(deal.stageId)\n if (!row) {\n totals.unplaced.count += 1\n totals.unplaced.amountCents += amount\n continue\n }\n const weighted = weightedDealAmountCents(deal, row.stage)\n row.count += 1\n row.amountCents += amount\n row.weightedCents += weighted\n totals.weightedCents += weighted\n }\n return totals\n}\n\n/** How many months ahead the close-month forecast looks. */\nexport const FORECAST_MONTHS = 6\n\n/** One calendar month of the forecast, `[start, end)` in local time. */\nexport interface CloseMonthBucket {\n /** `2026-09` — stable across locales, what a row is keyed by. */\n key: string\n start: number\n end: number\n}\n\n/**\n * The next `months` calendar months from the one `nowMs` falls in, each\n * `[first of the month, first of the next)` on the reader's calendar.\n *\n * The current month is the first bucket, whole: a deal expected to close\n * on the 3rd when today is the 20th is late, not next month's, and a\n * forecast that started at \"now\" would drop it between the rows. Local\n * months rather than UTC, because `expectedCloseAtMs` is stored at local\n * noon of the day picked (`dateInputMs`) and a UTC boundary would put a\n * deal picked for the 1st into the month before in half the world.\n */\nexport function closeMonthBuckets(\n nowMs: number,\n months = FORECAST_MONTHS,\n): CloseMonthBucket[] {\n const now = new Date(nowMs)\n const buckets: CloseMonthBucket[] = []\n for (let offset = 0; offset < months; offset += 1) {\n const start = new Date(now.getFullYear(), now.getMonth() + offset, 1)\n const end = new Date(now.getFullYear(), now.getMonth() + offset + 1, 1)\n buckets.push({\n key: `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}`,\n start: start.getTime(),\n end: end.getTime(),\n })\n }\n return buckets\n}\n\n/** One pipeline's open deals expected to close in one month. */\nexport interface ForecastCell {\n count: number\n amountCents: number\n weightedCents: number\n}\n\nexport interface PipelineForecast {\n pipelineId: string\n name: string\n /** One cell per bucket, in `buckets` order, every one present even when empty. */\n months: ForecastCell[]\n /** Open deals with no expected close — their own row, never a guess. */\n undated: ForecastCell\n /** Expected before the first bucket — overdue to close. */\n overdue: ForecastCell\n /** Expected past the last bucket. */\n later: ForecastCell\n total: ForecastCell\n}\n\nexport interface CloseMonthForecast {\n buckets: CloseMonthBucket[]\n pipelines: PipelineForecast[]\n /** Every pipeline's cells added together, in `buckets` order. */\n months: ForecastCell[]\n undated: ForecastCell\n overdue: ForecastCell\n later: ForecastCell\n total: ForecastCell\n}\n\ntype ForecastDeal = Pick<\n CrmDeal,\n 'status' | 'pipelineId' | 'stageId' | 'amountCents' | 'expectedCloseAtMs'\n>\n\nconst emptyCell = (): ForecastCell => ({ count: 0, amountCents: 0, weightedCents: 0 })\n\nfunction addToCell(cell: ForecastCell, amountCents: number, weightedCents: number) {\n cell.count += 1\n cell.amountCents += amountCents\n cell.weightedCents += weightedCents\n}\n\n/**\n * The open pipeline laid out by the month each deal is expected to close\n * (AGL-2620): per pipeline, one cell per month for the next\n * {@link FORECAST_MONTHS}, at face value and weighted by the deal's stage.\n *\n * Only open deals, for the reason `pipelineTotals` gives — a closed deal\n * has closed. A deal with no `expectedCloseAtMs` is its own row rather than\n * being dropped or dated \"now\": a forecast that quietly omitted undated\n * deals would understate the pipeline by exactly the deals nobody has\n * scheduled, which is the number a sales lead most wants to see. A deal\n * dated before the first month is overdue and a deal dated past the last\n * is \"later\"; both are reported so the rows add up to the pipeline.\n *\n * Every ACTIVE pipeline is a row even with nothing open — an empty\n * pipeline is a fact about the forecast too — while an archived one is a\n * row only if a deal still sits open in it. A deal in a pipeline the list\n * does not carry at all is grouped under its id with no name, so the\n * total still counts it; its stage cannot be resolved, so it weighs\n * nothing, as everywhere else.\n */\nexport function forecastByCloseMonth(\n deals: readonly ForecastDeal[],\n pipelines: ReadonlyArray<Pick<CrmPipeline, 'name' | 'stages' | 'archivedAt'> & { $id: string }>,\n nowMs: number,\n months = FORECAST_MONTHS,\n): CloseMonthForecast {\n const buckets = closeMonthBuckets(nowMs, months)\n const byId = new Map<string, PipelineForecast>()\n const pipelineRow = (pipelineId: string): PipelineForecast => {\n let row = byId.get(pipelineId)\n if (!row) {\n const pipeline = pipelines.find((entry) => entry.$id === pipelineId)\n row = {\n pipelineId,\n name: pipeline?.name ?? '',\n months: buckets.map(emptyCell),\n undated: emptyCell(),\n overdue: emptyCell(),\n later: emptyCell(),\n total: emptyCell(),\n }\n byId.set(pipelineId, row)\n }\n return row\n }\n for (const pipeline of pipelines) {\n if (!isPipelineArchived(pipeline)) pipelineRow(pipeline.$id)\n }\n\n const totals: CloseMonthForecast = {\n buckets,\n pipelines: [],\n months: buckets.map(emptyCell),\n undated: emptyCell(),\n overdue: emptyCell(),\n later: emptyCell(),\n total: emptyCell(),\n }\n const first = buckets[0]?.start ?? nowMs\n const last = buckets[buckets.length - 1]?.end ?? nowMs\n for (const deal of deals) {\n if (deal.status !== 'open') continue\n const pipelineId = String(deal.pipelineId ?? '')\n const row = pipelineRow(pipelineId)\n const pipeline = pipelines.find((entry) => entry.$id === pipelineId)\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n const weighted = weightedDealAmountCents(deal, dealStageById(pipeline, deal.stageId))\n const at = deal.expectedCloseAtMs\n const cells: Array<[ForecastCell, ForecastCell]> = [[row.total, totals.total]]\n if (typeof at !== 'number' || !Number.isFinite(at)) {\n cells.push([row.undated, totals.undated])\n } else if (at < first) {\n cells.push([row.overdue, totals.overdue])\n } else if (at >= last) {\n cells.push([row.later, totals.later])\n } else {\n const index = buckets.findIndex((bucket) => at >= bucket.start && at < bucket.end)\n cells.push([row.months[index], totals.months[index]])\n }\n for (const [own, all] of cells) {\n addToCell(own, amount, weighted)\n addToCell(all, amount, weighted)\n }\n }\n totals.pipelines = [...byId.values()]\n return totals\n}\n\n/**\n * How many of a list share each key, most common first.\n *\n * Ties break on the key so two renders of the same data draw the same order.\n * An item with no key is not \"a key called empty\": a contact whose facet\n * names no source is left out of a by-source chart rather than drawn as a\n * bar labeled with nothing.\n */\nexport function tally<T>(\n items: readonly T[],\n keyOf: (item: T) => string | null | undefined,\n): Array<{ key: string; count: number }> {\n const counts = new Map<string, number>()\n for (const item of items) {\n const key = keyOf(item)\n if (!key) continue\n counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n return [...counts.entries()]\n .map(([key, count]) => ({ key, count }))\n .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))\n}\n\n/**\n * Percentage change against the previous period, to one decimal, or `null`\n * when the previous period had nothing.\n *\n * `null` rather than a number because every number is wrong: \"+100%\" reads\n * as growth, \"+0%\" reads as flat, and a first contact has no growth rate.\n * The tile that renders this draws nothing for `null`, which is the honest\n * rendering of \"there is no baseline\".\n */\nexport function deltaPercent(current: number, previous: number): number | null {\n if (!(previous > 0)) return null\n return Math.round(((current - previous) / previous) * 1000) / 10\n}\n\n/**\n * The currency a set of deals is in, and whether they are actually in more\n * than one.\n *\n * A pipeline total is a sum, and a sum of dollars and euros is a number\n * with no unit. The reports still show that number — a merchant with one\n * stray euro deal wants the pipeline figure, not a refusal — but they say\n * it is mixed. The most common currency names the total; `usd` is what a\n * deal with no currency means, per the model.\n */\nexport function currencyOfDeals(\n deals: ReadonlyArray<Pick<CrmDeal, 'currency'>>,\n): { currency: string; mixed: boolean } {\n const ranked = tally(deals, (deal) =>\n String(deal.currency || 'usd').toLowerCase(),\n )\n return { currency: ranked[0]?.key ?? 'usd', mixed: ranked.length > 1 }\n}\n\n/**\n * The local calendar day a moment falls in, `[start, end)`.\n *\n * The same day `taskDueState` decides \"today\" on, so a count of tasks due\n * today and the list's \"today\" chip agree about the same task at 11 p.m.\n */\nexport function localDayBounds(nowMs: number): { start: number; end: number } {\n const now = new Date(nowMs)\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)\n return { start: start.getTime(), end: end.getTime() }\n}\n\n/*==========================================\n * ACTIVITY BY TEAMMATE (AGL-2624)\n *=========================================*/\n\n/** One teammate's row of the activity leaderboard. */\nexport interface ActivityLeaderboardRow {\n /**\n * The account uid, or `''` for work no person did: an activity an\n * automation logged, a task ticked off with nobody assigned. One row\n * rather than a row per automation, because the leaderboard ranks the\n * team, and \"nobody\" is the honest name for the rest.\n */\n uid: string\n /**\n * The name the newest of this uid's activities was signed with, or `null`\n * when none carried one — a uid that only completed tasks, say. The\n * activity's own `byName` because a scoped reader cannot resolve a\n * colleague's uid any other way; see `CrmActivity.byName`.\n */\n name: string | null\n /** Activities logged, by kind, every kind present even at zero. */\n kinds: Record<CrmActivityKind, number>\n /** Every activity logged, whatever its kind. */\n activities: number\n /** Tasks ticked off. */\n tasksDone: number\n}\n\n/** An activity as stored — `kind` is whatever was written, not only what the model names. */\ntype LeaderboardActivity = Pick<CrmActivity, 'byUid' | 'atMs' | 'byName'> & {\n kind: string\n}\ntype LeaderboardTask = Pick<CrmTask, 'assigneeUid' | 'completedByUid'>\n\n/**\n * Who did what: the activities and the completed tasks of a period,\n * grouped by the person who did them, busiest first.\n *\n * An activity is credited to whoever logged it. A task is credited to\n * whoever COMPLETED it when the route stamped that, and to its assignee\n * otherwise: the leaderboard measures work done, and a manager closing out\n * a departed teammate's list did that work. A kind the model does not\n * name counts as `other` rather than vanishing — the activity happened.\n *\n * Busiest means activities plus tasks; ties break on activities, then on\n * the uid, so two renders of one window draw one order.\n */\nexport function activityLeaderboard(\n activities: readonly LeaderboardActivity[],\n tasksDone: readonly LeaderboardTask[],\n): ActivityLeaderboardRow[] {\n const rows = new Map<string, ActivityLeaderboardRow>()\n /** When each row's name was signed, so a newer signature replaces an older one. */\n const namedAtMs = new Map<string, number>()\n const rowFor = (uid: string) => {\n let row = rows.get(uid)\n if (!row) {\n row = {\n uid,\n name: null,\n kinds: Object.fromEntries(\n CRM_ACTIVITY_KINDS.map((kind) => [kind, 0]),\n ) as Record<CrmActivityKind, number>,\n activities: 0,\n tasksDone: 0,\n }\n rows.set(uid, row)\n }\n return row\n }\n for (const activity of activities) {\n const row = rowFor(String(activity.byUid ?? ''))\n const kind = isCrmActivityKind(activity.kind) ? activity.kind : 'other'\n row.kinds[kind] += 1\n row.activities += 1\n const name = String(activity.byName ?? '').trim()\n const atMs = Number(activity.atMs)\n if (\n name &&\n Number.isFinite(atMs) &&\n atMs > (namedAtMs.get(row.uid) ?? Number.NEGATIVE_INFINITY)\n ) {\n row.name = name\n namedAtMs.set(row.uid, atMs)\n }\n }\n for (const task of tasksDone) {\n rowFor(String(task.completedByUid || task.assigneeUid || '')).tasksDone += 1\n }\n return [...rows.values()].sort(\n (a, b) =>\n b.activities + b.tasksDone - (a.activities + a.tasksDone) ||\n b.activities - a.activities ||\n a.uid.localeCompare(b.uid),\n )\n}\n\n/*==========================================\n * LEAD FUNNEL (AGL-2624)\n *=========================================*/\n\n/** The key a lead unqualified with no reason is tallied under. */\nexport const LEAD_NO_REASON_KEY = '$none'\nexport const LEAD_NO_REASON_LABEL = 'No reason given'\n\nexport interface LeadFunnelReason {\n /** The reason, case-folded and whitespace-collapsed, or {@link LEAD_NO_REASON_KEY}. */\n key: string\n /** The reason as it was first written. */\n label: string\n count: number\n}\n\nexport interface LeadFunnel {\n /** Every lead handed in. */\n total: number\n /** Leads at each status now, every status present. An absent status is `new`. */\n byStatus: Record<CrmLeadStatus, number>\n /** `new`, `nurturing` and `working` — the leads still needing somebody. */\n open: number\n /** Qualified over every lead, 0–1, or `null` when there were none. */\n qualifiedRate: number | null\n /** Unqualified over every lead, 0–1, or `null` when there were none. */\n unqualifiedRate: number | null\n /** Why leads were unqualified, most common first. */\n reasons: LeadFunnelReason[]\n}\n\n/**\n * Where a set of leads stands, and why the ones that were closed without\n * converting were closed.\n *\n * A status breakdown rather than a cumulative funnel, because a lead's\n * history is not on the document — only where it is now — and a lead can\n * be qualified straight from `new` without ever having been `working`. A\n * step that claimed \"reached working\" would be inventing a past. The two\n * rates are over EVERY lead handed in, so a period in which half the leads\n * are still untouched reads as half unconverted, which is what it is.\n *\n * Reasons are free text — the unqualify dialog requires one but does not\n * offer a list — so `Too small` and `too small` are one reason and the\n * label is whichever spelling came first. An unqualified lead written past\n * the dialog with no reason is counted under its own key rather than\n * dropped: the lead was closed, and \"no reason given\" is the count a\n * manager wants to see.\n */\nexport function leadFunnel(\n leads: ReadonlyArray<Pick<CrmLeadFields, 'status' | 'unqualifiedReason'>>,\n): LeadFunnel {\n const byStatus = Object.fromEntries(\n CRM_LEAD_STATUSES.map((status) => [status, 0]),\n ) as Record<CrmLeadStatus, number>\n const reasons = new Map<string, LeadFunnelReason>()\n for (const lead of leads) {\n const status = crmLeadStatus(lead)\n byStatus[status] += 1\n if (status !== 'unqualified') continue\n const label = String(lead.unqualifiedReason ?? '').replace(/\\s+/g, ' ').trim()\n const key = label ? label.toLowerCase() : LEAD_NO_REASON_KEY\n const row = reasons.get(key) ?? {\n key,\n label: label || LEAD_NO_REASON_LABEL,\n count: 0,\n }\n row.count += 1\n reasons.set(key, row)\n }\n const total = leads.length\n return {\n total,\n byStatus,\n open: byStatus.new + byStatus.nurturing + byStatus.working,\n qualifiedRate: total > 0 ? byStatus.qualified / total : null,\n unqualifiedRate: total > 0 ? byStatus.unqualified / total : null,\n reasons: [...reasons.values()].sort(\n (a, b) => b.count - a.count || a.label.localeCompare(b.label),\n ),\n }\n}\n\n/*==========================================\n * CONVERSION BY SOURCE (AGL-2624)\n *=========================================*/\n\n/**\n * The stages that mean a person has bought, or better.\n *\n * `customer` and `evangelist` only. `other` sits after `customer` in the\n * stage list so that no capture can overwrite it, but it names a step the\n * business chose and the report cannot know whether that step is past a\n * purchase; counting it would credit a source with a sale nobody recorded.\n */\nconst CUSTOMER_STAGES: ReadonlySet<ContactLifecycleStage> = new Set([\n 'customer',\n 'evangelist',\n])\n\ntype ConversionFacet = Pick<ContactFacet, 'sources' | 'lifecycleStage' | 'ordersCount'>\n\n/**\n * Whether this holder's view of a person says they have bought: a stage\n * at `customer` or past it, or an order on the books whatever the stage\n * says — an order path that never advanced the stage still sold something.\n */\nexport function contactIsCustomer(\n facet: Pick<ContactFacet, 'lifecycleStage' | 'ordersCount'>,\n): boolean {\n if (facet.lifecycleStage && CUSTOMER_STAGES.has(facet.lifecycleStage)) return true\n const orders = Number(facet.ordersCount ?? 0)\n return Number.isFinite(orders) && orders > 0\n}\n\nexport interface SourceConversionRow {\n source: ContactSource\n /** People this holder captured through the source. */\n captured: number\n /** Of those, the ones who are customers now. */\n customers: number\n /** `customers / captured`, 0–1. */\n rate: number\n}\n\nexport interface SourceConversion {\n /** One row per source anybody came through, most captured first. */\n rows: SourceConversionRow[]\n /** People with no source under this holder — no door to credit. */\n unsourced: number\n /** Every counted person who is a customer, once each. */\n customers: number\n /** Every person handed in. */\n total: number\n}\n\n/**\n * Which capture surfaces turn into customers.\n *\n * Read through one holder's facet, like every by-source figure: a source\n * is which of THIS business's doors the person came through. A person\n * captured two ways counts under both — the question is \"how well does\n * each door convert\", and a person who came through two is evidence for\n * both — so the rows can add to more than `total`, and `customers` counts\n * each person once for the tile above the table. Rows rank by captured,\n * not by rate, because a door with one visitor who bought is not the best\n * door.\n */\nexport function conversionBySource(\n facets: readonly ConversionFacet[],\n): SourceConversion {\n const rows = new Map<ContactSource, SourceConversionRow>()\n let unsourced = 0\n let customers = 0\n for (const facet of facets) {\n const isCustomer = contactIsCustomer(facet)\n if (isCustomer) customers += 1\n const sources = (Object.keys(facet.sources ?? {}) as ContactSource[]).filter(\n (source) => facet.sources?.[source],\n )\n if (!sources.length) {\n unsourced += 1\n continue\n }\n for (const source of sources) {\n const row = rows.get(source) ?? { source, captured: 0, customers: 0, rate: 0 }\n row.captured += 1\n if (isCustomer) row.customers += 1\n rows.set(source, row)\n }\n }\n return {\n rows: [...rows.values()]\n .map((row) => ({ ...row, rate: row.customers / row.captured }))\n .sort(\n (a, b) =>\n b.captured - a.captured ||\n b.customers - a.customers ||\n a.source.localeCompare(b.source),\n ),\n unsourced,\n customers,\n total: facets.length,\n }\n}\n\n/*==========================================\n * WON AND LOST, BY OWNER (AGL-2662)\n *=========================================*/\n\n/** A closed deal as the by-owner tally reads it. */\ntype OwnerOutcomeDeal = Pick<CrmDeal, 'ownerUid' | 'amountCents'>\n\n/** The key an unowned deal is tallied under — see {@link OwnerOutcomeRow.uid}. */\nexport const OWNER_UNASSIGNED_KEY = ''\n\nexport interface OwnerOutcomeRow {\n /**\n * The owner's account uid, or `''` for a deal nobody owns.\n *\n * One row for the unowned rather than none: a pipeline where a third of\n * what closed had no owner is a fact about the team, and dropping those\n * deals would make the rows add to less than the period's totals without\n * saying why.\n */\n uid: string\n won: number\n lost: number\n /** Value of what this owner won, in the deals' own minor units. */\n wonAmountCents: number\n /** Value of what they lost — the size of the miss, not a negative. */\n lostAmountCents: number\n /** Everything that closed for this owner, won or lost. */\n closed: number\n /**\n * Won over closed, 0–1, or `null` when nothing of theirs closed.\n *\n * Null rather than zero because the two read differently: an owner who\n * closed nothing has no rate, and rendering that as 0% would rank a\n * person who was not in the period below one who lost every deal.\n */\n winRate: number | null\n}\n\n/**\n * Who closed what, in the period's window.\n *\n * The by-owner reading of the same two sets the Won and lost card already\n * holds — everything that closed won, and everything that closed lost — so\n * this adds no read. Credited to `ownerUid`, the field the board and the\n * bulk bar both write, and never to the creator: a deal reassigned mid-cycle\n * belongs to whoever carried it at the close, which is what \"by owner\"\n * is asked to mean when a manager reads it.\n *\n * Ranked by how much CLOSED rather than by win rate, for the reason\n * `conversionBySource` ranks by captured: an owner who closed one deal and\n * won it is not the best closer on the team. Ties break on wins, then on\n * the uid, so two renders of one window draw one order.\n */\nexport function wonLostByOwner(\n won: readonly OwnerOutcomeDeal[],\n lost: readonly OwnerOutcomeDeal[],\n): OwnerOutcomeRow[] {\n const rows = new Map<string, OwnerOutcomeRow>()\n const rowFor = (uid: string) => {\n let row = rows.get(uid)\n if (!row) {\n row = {\n uid,\n won: 0,\n lost: 0,\n wonAmountCents: 0,\n lostAmountCents: 0,\n closed: 0,\n winRate: null,\n }\n rows.set(uid, row)\n }\n return row\n }\n /** A missing or negative amount counts as nothing, never as a debit. */\n const cents = (deal: OwnerOutcomeDeal) =>\n Math.max(0, Number(deal.amountCents ?? 0) || 0)\n for (const deal of won) {\n const row = rowFor(String(deal.ownerUid ?? OWNER_UNASSIGNED_KEY))\n row.won += 1\n row.closed += 1\n row.wonAmountCents += cents(deal)\n }\n for (const deal of lost) {\n const row = rowFor(String(deal.ownerUid ?? OWNER_UNASSIGNED_KEY))\n row.lost += 1\n row.closed += 1\n row.lostAmountCents += cents(deal)\n }\n return [...rows.values()]\n .map((row) => ({\n ...row,\n winRate: row.closed ? row.won / row.closed : null,\n }))\n .sort(\n (a, b) => b.closed - a.closed || b.won - a.won || a.uid.localeCompare(b.uid),\n )\n}\n"],"names":["CONTACT_LIFECYCLE_STAGES","CONTACT_LIFECYCLE_STAGE_LABELS","CRM_ACTIVITY_KINDS","CRM_LEAD_STATUSES","crmLeadStatus","dealStageById","isCrmActivityKind","isPipelineArchived","weightedDealAmountCents","DAY_MS","WEEK_MS","CRM_REPORT_PERIODS","CRM_REPORT_PERIOD_LABELS","month","crmReportRange","period","nowMs","now","Date","from","getFullYear","getMonth","getTime","previousFrom","to","previousTo","days","weekBuckets","buckets","start","push","end","Math","min","bucketByWeek","items","atMs","map","bounds","length","item","at","Number","isFinite","index","floor","funnelFromStages","counts","countOf","stage","value","round","progression","filter","steps","reachedAfter","count","reached","unshift","label","conversion","previous","other","total","reduce","sum","step","pipelineTotals","deals","pipeline","stages","kind","sort","a","b","order","rows","Map","id","amountCents","weightedCents","totals","get","unplaced","deal","status","amount","max","row","stageId","weighted","FORECAST_MONTHS","closeMonthBuckets","months","offset","key","String","padStart","emptyCell","addToCell","cell","forecastByCloseMonth","pipelines","byId","pipelineRow","pipelineId","find","entry","$id","name","undated","overdue","later","set","first","last","expectedCloseAtMs","cells","findIndex","bucket","own","all","values","tally","keyOf","entries","localeCompare","deltaPercent","current","currencyOfDeals","ranked","currency","toLowerCase","mixed","localDayBounds","getDate","activityLeaderboard","activities","tasksDone","namedAtMs","rowFor","uid","kinds","Object","fromEntries","activity","byUid","byName","trim","NEGATIVE_INFINITY","task","completedByUid","assigneeUid","LEAD_NO_REASON_KEY","LEAD_NO_REASON_LABEL","leadFunnel","leads","byStatus","reasons","lead","unqualifiedReason","replace","open","new","nurturing","working","qualifiedRate","qualified","unqualifiedRate","unqualified","CUSTOMER_STAGES","Set","contactIsCustomer","facet","lifecycleStage","has","orders","ordersCount","conversionBySource","facets","unsourced","customers","isCustomer","sources","keys","source","captured","rate","OWNER_UNASSIGNED_KEY","wonLostByOwner","won","lost","wonAmountCents","lostAmountCents","closed","winRate","cents","ownerUid"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/crm-reports.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 * CRM reporting arithmetic (AGL-2604).\n *\n * The Reports section and the dashboard glance card read Firestore\n * aggregates and bounded windows and then have to turn them into a period, a\n * set of weekly bars, a funnel and a pipeline total. Every one of those is a\n * decision about numbers that a card could get subtly wrong in its own JSX —\n * a bucket boundary that is closed on both ends counts a midnight contact\n * twice, a funnel that divides by the step before reads 100% where nobody\n * arrived, a forecast that weights a closed deal forecasts a sale twice. So\n * they live here, pure and specified, and the cards only draw.\n *\n * Pure data module: no Firestore, no React, no clock — every function that\n * needs \"now\" is handed it.\n */\n\nimport type { ContactFacet, ContactSource } from '@aglyn/aglyn/app-utils/contacts'\nimport {\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CRM_ACTIVITY_KINDS,\n CRM_LEAD_STATUSES,\n type ContactLifecycleStage,\n type CrmActivity,\n type CrmActivityKind,\n type CrmDeal,\n type CrmDealStage,\n type CrmForecastCategory,\n type CrmLeadFields,\n type CrmLeadStatus,\n type CrmPicklist,\n type CrmPipeline,\n type CrmTask,\n crmLeadStatus,\n crmPicklistRank,\n crmPicklistValueByLabel,\n dealForecastCategory,\n dealStageById,\n isCrmActivityKind,\n isPipelineArchived,\n weightedDealAmountCents,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { type LeadSourceDirection, leadSourceDirectionOf } from './lead-source-direction'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst WEEK_MS = 7 * DAY_MS\n\n/**\n * The periods a report can be read over.\n *\n * A fixed list rather than a free date range because every period here is\n * also a READ COST: the contact trend runs one count aggregate per week of\n * the period, and the closed-deal chart reads the deals closed within it. A\n * picker that offered \"all time\" would offer a read nobody has sized.\n */\nexport const CRM_REPORT_PERIODS = ['7d', '30d', '90d', 'month'] as const\n\nexport type CrmReportPeriod = (typeof CRM_REPORT_PERIODS)[number]\n\nexport const CRM_REPORT_PERIOD_LABELS: Record<CrmReportPeriod, string> = {\n '7d': 'Last 7 days',\n '30d': 'Last 30 days',\n '90d': 'Last 90 days',\n month: 'This month',\n}\n\n/**\n * A period and the period before it, as half-open millisecond ranges\n * `[from, to)`.\n *\n * The previous period is what a delta is measured against, so it is always\n * the same LENGTH as the current one and ends exactly where the current one\n * begins: \"this month\" against \"last month\", thirty days against the thirty\n * before them. A gap or an overlap between the two would make the comparison\n * a comparison of different things.\n */\nexport interface CrmReportRange {\n from: number\n to: number\n previousFrom: number\n previousTo: number\n}\n\n/**\n * The range a period names at a given moment.\n *\n * `to` is the moment itself, so a period is always \"up to now\" and a report\n * read twice in one sitting shows the same figures with the newer minutes\n * added. \"This month\" starts on the first of the LOCAL month, because that\n * is the month the reader sees on their calendar; the day periods count back\n * from the moment rather than from midnight, so \"last 7 days\" is a full\n * seven days rather than six and a fraction.\n */\nexport function crmReportRange(\n period: CrmReportPeriod,\n nowMs: number,\n): CrmReportRange {\n if (period === 'month') {\n const now = new Date(nowMs)\n const from = new Date(now.getFullYear(), now.getMonth(), 1).getTime()\n const previousFrom = new Date(\n now.getFullYear(),\n now.getMonth() - 1,\n 1,\n ).getTime()\n return { from, to: nowMs, previousFrom, previousTo: from }\n }\n const days = period === '7d' ? 7 : period === '30d' ? 30 : 90\n const from = nowMs - days * DAY_MS\n return {\n from,\n to: nowMs,\n previousFrom: from - days * DAY_MS,\n previousTo: from,\n }\n}\n\n/** One week of a period, `[start, end)`; the last week of a period may be short. */\nexport interface WeekBucketBounds {\n start: number\n end: number\n}\n\n/**\n * The weeks a range divides into: seven-day strides from `from`, the last\n * clipped at `to`.\n *\n * Strides from the START of the period rather than calendar weeks, so that\n * \"last 7 days\" is one bar and \"last 30 days\" is four full weeks and two\n * days — the period the reader picked, in the reader's units — instead of\n * whatever fragment of a Monday-to-Sunday week the period happened to start\n * inside. An empty or inverted range has no weeks.\n */\nexport function weekBuckets(from: number, to: number): WeekBucketBounds[] {\n const buckets: WeekBucketBounds[] = []\n if (!(to > from)) return buckets\n for (let start = from; start < to; start += WEEK_MS) {\n buckets.push({ start, end: Math.min(start + WEEK_MS, to) })\n }\n return buckets\n}\n\nexport interface WeekBucket<T> extends WeekBucketBounds {\n items: T[]\n}\n\n/**\n * The items of a range, grouped into its weeks.\n *\n * Half-open on every boundary, so an item at exactly midnight between two\n * weeks is in the later one and only the later one, and an item at `to` is\n * outside the period the way an item at `from` is inside it. Items with no\n * usable time are dropped rather than placed anywhere — an undated deal is\n * not a deal closed this week. Every week is returned, empty ones included,\n * because a chart wants a bar per week and a gap where a week had nothing is\n * a chart with the wrong number of bars.\n */\nexport function bucketByWeek<T>(\n items: readonly T[],\n atMs: (item: T) => number | null | undefined,\n from: number,\n to: number,\n): WeekBucket<T>[] {\n const buckets: WeekBucket<T>[] = weekBuckets(from, to).map((bounds) => ({\n ...bounds,\n items: [] as T[],\n }))\n if (!buckets.length) return buckets\n for (const item of items) {\n const at = atMs(item)\n if (typeof at !== 'number' || !Number.isFinite(at)) continue\n if (at < from || at >= to) continue\n const index = Math.min(\n buckets.length - 1,\n Math.floor((at - from) / WEEK_MS),\n )\n buckets[index].items.push(item)\n }\n return buckets\n}\n\n/** One step of the lifecycle funnel. */\nexport interface LifecycleFunnelStep {\n stage: ContactLifecycleStage\n label: string\n /** People AT this stage. */\n count: number\n /** People at this stage or any later one — everyone who got at least this far. */\n reached: number\n /**\n * `reached` over the previous step's `reached`, 0–1, or `null` on the first\n * step and wherever nobody reached the step before.\n */\n conversion: number | null\n}\n\nexport interface LifecycleFunnel {\n /** The progression, in `CONTACT_LIFECYCLE_STAGES` order, `other` left out. */\n steps: LifecycleFunnelStep[]\n /** People at the `other` stage, which is beside the funnel rather than in it. */\n other: number\n /** Every counted person, `other` included. */\n total: number\n}\n\n/**\n * The lifecycle funnel a set of per-stage counts describes.\n *\n * A funnel is read CUMULATIVELY: a customer was once a lead, so the \"lead\"\n * step is everyone who is a lead now or has moved past it, not only the\n * people currently sitting there. That is what makes the steps narrow as\n * they go down and what makes a step-to-step conversion mean something.\n * `other` is a stage a merchant chose for a person the funnel does not\n * describe, so it is reported beside the funnel and never folded into a\n * step. A conversion where the previous step was empty is `null` rather than\n * 0 or 100 — there is nothing to divide by, and either number would be read\n * as a measurement.\n */\nexport function funnelFromStages(\n counts: Partial<Record<ContactLifecycleStage, number>>,\n): LifecycleFunnel {\n const countOf = (stage: ContactLifecycleStage): number => {\n const value = Number(counts[stage] ?? 0)\n return Number.isFinite(value) && value > 0 ? Math.round(value) : 0\n }\n const progression = CONTACT_LIFECYCLE_STAGES.filter(\n (stage) => stage !== 'other',\n )\n const steps: LifecycleFunnelStep[] = []\n let reachedAfter = 0\n for (let index = progression.length - 1; index >= 0; index -= 1) {\n const stage = progression[index]\n const count = countOf(stage)\n const reached = count + reachedAfter\n steps.unshift({\n stage,\n label: CONTACT_LIFECYCLE_STAGE_LABELS[stage],\n count,\n reached,\n conversion: null,\n })\n reachedAfter = reached\n }\n for (let index = 1; index < steps.length; index += 1) {\n const previous = steps[index - 1].reached\n steps[index].conversion = previous > 0 ? steps[index].reached / previous : null\n }\n const other = countOf('other')\n return {\n steps,\n other,\n total: steps.reduce((sum, step) => sum + step.count, 0) + other,\n }\n}\n\n/** The open deals sitting in one stage of a pipeline. */\nexport interface PipelineStageTotal {\n stage: CrmDealStage\n count: number\n amountCents: number\n weightedCents: number\n}\n\nexport interface PipelineTotals {\n /** Open deals, wherever they sit. */\n count: number\n amountCents: number\n /** The forecast: each open deal at its stage's odds. */\n weightedCents: number\n /** The pipeline's open stages in order, every one present even when empty. */\n stages: PipelineStageTotal[]\n /**\n * Open deals in a stage the pipeline does not have — retired, renamed, or\n * a closed-kind stage the status disagrees with. Counted in the totals,\n * worth nothing to the forecast, and reported so a merchant can find them.\n */\n unplaced: { count: number; amountCents: number }\n}\n\ntype PipelineDeal = Pick<CrmDeal, 'status' | 'stageId' | 'amountCents'> &\n Partial<Pick<CrmDeal, 'probability'>>\n\n/**\n * What a pipeline holds, by stage.\n *\n * Only OPEN deals: a won deal is revenue and a lost one is history, and a\n * pipeline report that counted either would show a stage full of deals that\n * cannot move. Only the pipeline's open-kind stages are rows, for the same\n * reason from the other side — a \"Won\" column in a pipeline chart is the\n * closed-deals report drawn in the wrong place. The weighting is\n * `weightedDealAmountCents`, applied here rather than restated.\n */\nexport function pipelineTotals(\n deals: readonly PipelineDeal[],\n pipeline: Pick<CrmPipeline, 'stages'> | null | undefined,\n): PipelineTotals {\n const stages = [...(pipeline?.stages ?? [])]\n .filter((stage) => stage.kind === 'open')\n .sort((a, b) => a.order - b.order)\n const rows = new Map<string, PipelineStageTotal>(\n stages.map((stage) => [\n stage.id,\n { stage, count: 0, amountCents: 0, weightedCents: 0 },\n ]),\n )\n const totals: PipelineTotals = {\n count: 0,\n amountCents: 0,\n weightedCents: 0,\n stages: stages.map((stage) => rows.get(stage.id) as PipelineStageTotal),\n unplaced: { count: 0, amountCents: 0 },\n }\n for (const deal of deals) {\n if (deal.status !== 'open') continue\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n totals.count += 1\n totals.amountCents += amount\n const row = rows.get(deal.stageId)\n if (!row) {\n totals.unplaced.count += 1\n totals.unplaced.amountCents += amount\n continue\n }\n const weighted = weightedDealAmountCents(deal, row.stage)\n row.count += 1\n row.amountCents += amount\n row.weightedCents += weighted\n totals.weightedCents += weighted\n }\n return totals\n}\n\n/** How many months ahead the close-month forecast looks. */\nexport const FORECAST_MONTHS = 6\n\n/** One calendar month of the forecast, `[start, end)` in local time. */\nexport interface CloseMonthBucket {\n /** `2026-09` — stable across locales, what a row is keyed by. */\n key: string\n start: number\n end: number\n}\n\n/**\n * The next `months` calendar months from the one `nowMs` falls in, each\n * `[first of the month, first of the next)` on the reader's calendar.\n *\n * The current month is the first bucket, whole: a deal expected to close\n * on the 3rd when today is the 20th is late, not next month's, and a\n * forecast that started at \"now\" would drop it between the rows. Local\n * months rather than UTC, because `expectedCloseAtMs` is stored at local\n * noon of the day picked (`dateInputMs`) and a UTC boundary would put a\n * deal picked for the 1st into the month before in half the world.\n */\nexport function closeMonthBuckets(\n nowMs: number,\n months = FORECAST_MONTHS,\n): CloseMonthBucket[] {\n const now = new Date(nowMs)\n const buckets: CloseMonthBucket[] = []\n for (let offset = 0; offset < months; offset += 1) {\n const start = new Date(now.getFullYear(), now.getMonth() + offset, 1)\n const end = new Date(now.getFullYear(), now.getMonth() + offset + 1, 1)\n buckets.push({\n key: `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}`,\n start: start.getTime(),\n end: end.getTime(),\n })\n }\n return buckets\n}\n\n/** One pipeline's open deals expected to close in one month. */\nexport interface ForecastCell {\n count: number\n amountCents: number\n weightedCents: number\n}\n\nexport interface PipelineForecast {\n pipelineId: string\n name: string\n /** One cell per bucket, in `buckets` order, every one present even when empty. */\n months: ForecastCell[]\n /** Open deals with no expected close — their own row, never a guess. */\n undated: ForecastCell\n /** Expected before the first bucket — overdue to close. */\n overdue: ForecastCell\n /** Expected past the last bucket. */\n later: ForecastCell\n total: ForecastCell\n}\n\nexport interface CloseMonthForecast {\n buckets: CloseMonthBucket[]\n pipelines: PipelineForecast[]\n /** Every pipeline's cells added together, in `buckets` order. */\n months: ForecastCell[]\n undated: ForecastCell\n overdue: ForecastCell\n later: ForecastCell\n total: ForecastCell\n}\n\ntype ForecastDeal = Pick<\n CrmDeal,\n 'status' | 'pipelineId' | 'stageId' | 'amountCents' | 'expectedCloseAtMs'\n> &\n Partial<Pick<CrmDeal, 'probability' | 'forecastCategory'>>\n\nconst emptyCell = (): ForecastCell => ({ count: 0, amountCents: 0, weightedCents: 0 })\n\nfunction addToCell(cell: ForecastCell, amountCents: number, weightedCents: number) {\n cell.count += 1\n cell.amountCents += amountCents\n cell.weightedCents += weightedCents\n}\n\n/**\n * The open pipeline laid out by the month each deal is expected to close\n * (AGL-2620): per pipeline, one cell per month for the next\n * {@link FORECAST_MONTHS}, at face value and weighted by the deal's stage.\n *\n * Only open deals, for the reason `pipelineTotals` gives — a closed deal\n * has closed. A deal with no `expectedCloseAtMs` is its own row rather than\n * being dropped or dated \"now\": a forecast that quietly omitted undated\n * deals would understate the pipeline by exactly the deals nobody has\n * scheduled, which is the number a sales lead most wants to see. A deal\n * dated before the first month is overdue and a deal dated past the last\n * is \"later\"; both are reported so the rows add up to the pipeline.\n *\n * Every ACTIVE pipeline is a row even with nothing open — an empty\n * pipeline is a fact about the forecast too — while an archived one is a\n * row only if a deal still sits open in it. A deal in a pipeline the list\n * does not carry at all is grouped under its id with no name, so the\n * total still counts it; its stage cannot be resolved, so it weighs\n * nothing, as everywhere else.\n */\nexport function forecastByCloseMonth(\n deals: readonly ForecastDeal[],\n pipelines: ReadonlyArray<Pick<CrmPipeline, 'name' | 'stages' | 'archivedAt'> & { $id: string }>,\n nowMs: number,\n months = FORECAST_MONTHS,\n): CloseMonthForecast {\n const buckets = closeMonthBuckets(nowMs, months)\n const byId = new Map<string, PipelineForecast>()\n const pipelineRow = (pipelineId: string): PipelineForecast => {\n let row = byId.get(pipelineId)\n if (!row) {\n const pipeline = pipelines.find((entry) => entry.$id === pipelineId)\n row = {\n pipelineId,\n name: pipeline?.name ?? '',\n months: buckets.map(emptyCell),\n undated: emptyCell(),\n overdue: emptyCell(),\n later: emptyCell(),\n total: emptyCell(),\n }\n byId.set(pipelineId, row)\n }\n return row\n }\n for (const pipeline of pipelines) {\n if (!isPipelineArchived(pipeline)) pipelineRow(pipeline.$id)\n }\n\n const totals: CloseMonthForecast = {\n buckets,\n pipelines: [],\n months: buckets.map(emptyCell),\n undated: emptyCell(),\n overdue: emptyCell(),\n later: emptyCell(),\n total: emptyCell(),\n }\n const first = buckets[0]?.start ?? nowMs\n const last = buckets[buckets.length - 1]?.end ?? nowMs\n for (const deal of deals) {\n if (deal.status !== 'open') continue\n const pipelineId = String(deal.pipelineId ?? '')\n const row = pipelineRow(pipelineId)\n const pipeline = pipelines.find((entry) => entry.$id === pipelineId)\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n const weighted = weightedDealAmountCents(deal, dealStageById(pipeline, deal.stageId))\n const at = deal.expectedCloseAtMs\n const cells: Array<[ForecastCell, ForecastCell]> = [[row.total, totals.total]]\n if (typeof at !== 'number' || !Number.isFinite(at)) {\n cells.push([row.undated, totals.undated])\n } else if (at < first) {\n cells.push([row.overdue, totals.overdue])\n } else if (at >= last) {\n cells.push([row.later, totals.later])\n } else {\n const index = buckets.findIndex((bucket) => at >= bucket.start && at < bucket.end)\n cells.push([row.months[index], totals.months[index]])\n }\n for (const [own, all] of cells) {\n addToCell(own, amount, weighted)\n addToCell(all, amount, weighted)\n }\n }\n totals.pipelines = [...byId.values()]\n return totals\n}\n\n/**\n * The rows of the forecast by category (AGL-3516), in the order Salesforce's\n * forecast reads them: what is still in play from least to most certain,\n * then what has closed. Omitted is not a forecast row — a deal put there\n * is one the team chose to leave out — and is reported beside it.\n */\nexport const FORECAST_CATEGORY_ROWS = ['pipeline', 'bestCase', 'commit', 'closed'] as const satisfies readonly CrmForecastCategory[]\n\nexport interface CategoryForecast {\n /** One cell per {@link FORECAST_CATEGORY_ROWS} entry, every one present. */\n rows: Record<(typeof FORECAST_CATEGORY_ROWS)[number], ForecastCell>\n /** Open deals left out of the forecast on purpose. */\n omitted: ForecastCell\n /** Every row added up — what the forecast forecasts. */\n total: ForecastCell\n}\n\n/**\n * The forecast by category (AGL-3516): the OPEN deals by the category each\n * stands in — its own, else its stage's ({@link dealForecastCategory}) —\n * weighted as everywhere else, and the WON deals the caller read as\n * Closed, at full value because they have closed. An open deal somebody\n * put in Closed joins that row at its weighted value — it is still open,\n * so it is not counted as revenue. A lost deal is never a forecast.\n */\nexport function forecastByCategory(\n openDeals: readonly ForecastDeal[],\n wonDeals: ReadonlyArray<Pick<CrmDeal, 'status' | 'amountCents'>>,\n pipelines: ReadonlyArray<Pick<CrmPipeline, 'stages'> & { $id: string }>,\n): CategoryForecast {\n const forecast: CategoryForecast = {\n rows: {\n pipeline: emptyCell(),\n bestCase: emptyCell(),\n commit: emptyCell(),\n closed: emptyCell(),\n },\n omitted: emptyCell(),\n total: emptyCell(),\n }\n for (const deal of openDeals) {\n if (deal.status !== 'open') continue\n const pipeline = pipelines.find((entry) => entry.$id === deal.pipelineId)\n const stage = dealStageById(pipeline, deal.stageId)\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n const weighted = weightedDealAmountCents(deal, stage)\n const category = dealForecastCategory(deal, stage)\n if (category === 'omitted') {\n addToCell(forecast.omitted, amount, weighted)\n continue\n }\n addToCell(forecast.rows[category], amount, weighted)\n addToCell(forecast.total, amount, weighted)\n }\n for (const deal of wonDeals) {\n if (deal.status !== 'won') continue\n const amount = Math.max(0, Math.round(Number(deal.amountCents ?? 0) || 0))\n addToCell(forecast.rows.closed, amount, amount)\n addToCell(forecast.total, amount, amount)\n }\n return forecast\n}\n\n/**\n * How many of a list share each key, most common first.\n *\n * Ties break on the key so two renders of the same data draw the same order.\n * An item with no key is not \"a key called empty\": a contact whose facet\n * names no source is left out of a by-source chart rather than drawn as a\n * bar labeled with nothing.\n */\nexport function tally<T>(\n items: readonly T[],\n keyOf: (item: T) => string | null | undefined,\n): Array<{ key: string; count: number }> {\n const counts = new Map<string, number>()\n for (const item of items) {\n const key = keyOf(item)\n if (!key) continue\n counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n return [...counts.entries()]\n .map(([key, count]) => ({ key, count }))\n .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))\n}\n\n/**\n * Percentage change against the previous period, to one decimal, or `null`\n * when the previous period had nothing.\n *\n * `null` rather than a number because every number is wrong: \"+100%\" reads\n * as growth, \"+0%\" reads as flat, and a first contact has no growth rate.\n * The tile that renders this draws nothing for `null`, which is the honest\n * rendering of \"there is no baseline\".\n */\nexport function deltaPercent(current: number, previous: number): number | null {\n if (!(previous > 0)) return null\n return Math.round(((current - previous) / previous) * 1000) / 10\n}\n\n/**\n * The currency a set of deals is in, and whether they are actually in more\n * than one.\n *\n * A pipeline total is a sum, and a sum of dollars and euros is a number\n * with no unit. The reports still show that number — a merchant with one\n * stray euro deal wants the pipeline figure, not a refusal — but they say\n * it is mixed. The most common currency names the total; `usd` is what a\n * deal with no currency means, per the model.\n */\nexport function currencyOfDeals(\n deals: ReadonlyArray<Pick<CrmDeal, 'currency'>>,\n): { currency: string; mixed: boolean } {\n const ranked = tally(deals, (deal) =>\n String(deal.currency || 'usd').toLowerCase(),\n )\n return { currency: ranked[0]?.key ?? 'usd', mixed: ranked.length > 1 }\n}\n\n/**\n * The local calendar day a moment falls in, `[start, end)`.\n *\n * The same day `taskDueState` decides \"today\" on, so a count of tasks due\n * today and the list's \"today\" chip agree about the same task at 11 p.m.\n */\nexport function localDayBounds(nowMs: number): { start: number; end: number } {\n const now = new Date(nowMs)\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)\n return { start: start.getTime(), end: end.getTime() }\n}\n\n/*==========================================\n * ACTIVITY BY TEAMMATE (AGL-2624)\n *=========================================*/\n\n/** One teammate's row of the activity leaderboard. */\nexport interface ActivityLeaderboardRow {\n /**\n * The account uid, or `''` for work no person did: an activity an\n * automation logged, a task ticked off with nobody assigned. One row\n * rather than a row per automation, because the leaderboard ranks the\n * team, and \"nobody\" is the honest name for the rest.\n */\n uid: string\n /**\n * The name the newest of this uid's activities was signed with, or `null`\n * when none carried one — a uid that only completed tasks, say. The\n * activity's own `byName` because a scoped reader cannot resolve a\n * colleague's uid any other way; see `CrmActivity.byName`.\n */\n name: string | null\n /** Activities logged, by kind, every kind present even at zero. */\n kinds: Record<CrmActivityKind, number>\n /** Every activity logged, whatever its kind. */\n activities: number\n /** Tasks ticked off. */\n tasksDone: number\n}\n\n/** An activity as stored — `kind` is whatever was written, not only what the model names. */\ntype LeaderboardActivity = Pick<CrmActivity, 'byUid' | 'atMs' | 'byName'> & {\n kind: string\n}\ntype LeaderboardTask = Pick<CrmTask, 'assigneeUid' | 'completedByUid'>\n\n/**\n * Who did what: the activities and the completed tasks of a period,\n * grouped by the person who did them, busiest first.\n *\n * An activity is credited to whoever logged it. A task is credited to\n * whoever COMPLETED it when the route stamped that, and to its assignee\n * otherwise: the leaderboard measures work done, and a manager closing out\n * a departed teammate's list did that work. A kind the model does not\n * name counts as `other` rather than vanishing — the activity happened.\n *\n * Busiest means activities plus tasks; ties break on activities, then on\n * the uid, so two renders of one window draw one order.\n */\nexport function activityLeaderboard(\n activities: readonly LeaderboardActivity[],\n tasksDone: readonly LeaderboardTask[],\n): ActivityLeaderboardRow[] {\n const rows = new Map<string, ActivityLeaderboardRow>()\n /** When each row's name was signed, so a newer signature replaces an older one. */\n const namedAtMs = new Map<string, number>()\n const rowFor = (uid: string) => {\n let row = rows.get(uid)\n if (!row) {\n row = {\n uid,\n name: null,\n kinds: Object.fromEntries(\n CRM_ACTIVITY_KINDS.map((kind) => [kind, 0]),\n ) as Record<CrmActivityKind, number>,\n activities: 0,\n tasksDone: 0,\n }\n rows.set(uid, row)\n }\n return row\n }\n for (const activity of activities) {\n const row = rowFor(String(activity.byUid ?? ''))\n const kind = isCrmActivityKind(activity.kind) ? activity.kind : 'other'\n row.kinds[kind] += 1\n row.activities += 1\n const name = String(activity.byName ?? '').trim()\n const atMs = Number(activity.atMs)\n if (\n name &&\n Number.isFinite(atMs) &&\n atMs > (namedAtMs.get(row.uid) ?? Number.NEGATIVE_INFINITY)\n ) {\n row.name = name\n namedAtMs.set(row.uid, atMs)\n }\n }\n for (const task of tasksDone) {\n rowFor(String(task.completedByUid || task.assigneeUid || '')).tasksDone += 1\n }\n return [...rows.values()].sort(\n (a, b) =>\n b.activities + b.tasksDone - (a.activities + a.tasksDone) ||\n b.activities - a.activities ||\n a.uid.localeCompare(b.uid),\n )\n}\n\n/*==========================================\n * LEAD FUNNEL (AGL-2624)\n *=========================================*/\n\n/** The key a lead unqualified with no reason is tallied under. */\nexport const LEAD_NO_REASON_KEY = '$none'\nexport const LEAD_NO_REASON_LABEL = 'No reason given'\n\nexport interface LeadFunnelReason {\n /** The reason, case-folded and whitespace-collapsed, or {@link LEAD_NO_REASON_KEY}. */\n key: string\n /** The reason as it was first written. */\n label: string\n count: number\n}\n\nexport interface LeadFunnel {\n /** Every lead handed in. */\n total: number\n /** Leads at each status now, every status present. An absent status is `new`. */\n byStatus: Record<CrmLeadStatus, number>\n /** `new`, `nurturing` and `working` — the leads still needing somebody. */\n open: number\n /** Qualified over every lead, 0–1, or `null` when there were none. */\n qualifiedRate: number | null\n /** Unqualified over every lead, 0–1, or `null` when there were none. */\n unqualifiedRate: number | null\n /** Why leads were unqualified, most common first. */\n reasons: LeadFunnelReason[]\n}\n\n/**\n * Where a set of leads stands, and why the ones that were closed without\n * converting were closed.\n *\n * A status breakdown rather than a cumulative funnel, because a lead's\n * history is not on the document — only where it is now — and a lead can\n * be qualified straight from `new` without ever having been `working`. A\n * step that claimed \"reached working\" would be inventing a past. The two\n * rates are over EVERY lead handed in, so a period in which half the leads\n * are still untouched reads as half unconverted, which is what it is.\n *\n * Reasons are free text — the unqualify dialog requires one but does not\n * offer a list — so `Too small` and `too small` are one reason and the\n * label is whichever spelling came first. An unqualified lead written past\n * the dialog with no reason is counted under its own key rather than\n * dropped: the lead was closed, and \"no reason given\" is the count a\n * manager wants to see.\n */\nexport function leadFunnel(\n leads: ReadonlyArray<Pick<CrmLeadFields, 'status' | 'unqualifiedReason'>>,\n): LeadFunnel {\n const byStatus = Object.fromEntries(\n CRM_LEAD_STATUSES.map((status) => [status, 0]),\n ) as Record<CrmLeadStatus, number>\n const reasons = new Map<string, LeadFunnelReason>()\n for (const lead of leads) {\n const status = crmLeadStatus(lead)\n byStatus[status] += 1\n if (status !== 'unqualified') continue\n const label = String(lead.unqualifiedReason ?? '').replace(/\\s+/g, ' ').trim()\n const key = label ? label.toLowerCase() : LEAD_NO_REASON_KEY\n const row = reasons.get(key) ?? {\n key,\n label: label || LEAD_NO_REASON_LABEL,\n count: 0,\n }\n row.count += 1\n reasons.set(key, row)\n }\n const total = leads.length\n return {\n total,\n byStatus,\n open: byStatus.new + byStatus.nurturing + byStatus.working,\n qualifiedRate: total > 0 ? byStatus.qualified / total : null,\n unqualifiedRate: total > 0 ? byStatus.unqualified / total : null,\n reasons: [...reasons.values()].sort(\n (a, b) => b.count - a.count || a.label.localeCompare(b.label),\n ),\n }\n}\n\n/*==========================================\n * CONVERSION BY SOURCE (AGL-2624)\n *=========================================*/\n\n/**\n * The stages that mean a person has bought, or better.\n *\n * `customer` and `evangelist` only. `other` sits after `customer` in the\n * stage list so that no capture can overwrite it, but it names a step the\n * business chose and the report cannot know whether that step is past a\n * purchase; counting it would credit a source with a sale nobody recorded.\n */\nconst CUSTOMER_STAGES: ReadonlySet<ContactLifecycleStage> = new Set([\n 'customer',\n 'evangelist',\n])\n\ntype ConversionFacet = Pick<ContactFacet, 'sources' | 'lifecycleStage' | 'ordersCount'>\n\n/**\n * Whether this holder's view of a person says they have bought: a stage\n * at `customer` or past it, or an order on the books whatever the stage\n * says — an order path that never advanced the stage still sold something.\n */\nexport function contactIsCustomer(\n facet: Pick<ContactFacet, 'lifecycleStage' | 'ordersCount'>,\n): boolean {\n if (facet.lifecycleStage && CUSTOMER_STAGES.has(facet.lifecycleStage)) return true\n const orders = Number(facet.ordersCount ?? 0)\n return Number.isFinite(orders) && orders > 0\n}\n\nexport interface SourceConversionRow {\n source: ContactSource\n /** People this holder captured through the source. */\n captured: number\n /** Of those, the ones who are customers now. */\n customers: number\n /** `customers / captured`, 0–1. */\n rate: number\n}\n\nexport interface SourceConversion {\n /** One row per source anybody came through, most captured first. */\n rows: SourceConversionRow[]\n /** People with no source under this holder — no door to credit. */\n unsourced: number\n /** Every counted person who is a customer, once each. */\n customers: number\n /** Every person handed in. */\n total: number\n}\n\n/**\n * Which capture surfaces turn into customers.\n *\n * Read through one holder's facet, like every by-source figure: a source\n * is which of THIS business's doors the person came through. A person\n * captured two ways counts under both — the question is \"how well does\n * each door convert\", and a person who came through two is evidence for\n * both — so the rows can add to more than `total`, and `customers` counts\n * each person once for the tile above the table. Rows rank by captured,\n * not by rate, because a door with one visitor who bought is not the best\n * door.\n */\nexport function conversionBySource(\n facets: readonly ConversionFacet[],\n): SourceConversion {\n const rows = new Map<ContactSource, SourceConversionRow>()\n let unsourced = 0\n let customers = 0\n for (const facet of facets) {\n const isCustomer = contactIsCustomer(facet)\n if (isCustomer) customers += 1\n const sources = (Object.keys(facet.sources ?? {}) as ContactSource[]).filter(\n (source) => facet.sources?.[source],\n )\n if (!sources.length) {\n unsourced += 1\n continue\n }\n for (const source of sources) {\n const row = rows.get(source) ?? { source, captured: 0, customers: 0, rate: 0 }\n row.captured += 1\n if (isCustomer) row.customers += 1\n rows.set(source, row)\n }\n }\n return {\n rows: [...rows.values()]\n .map((row) => ({ ...row, rate: row.customers / row.captured }))\n .sort(\n (a, b) =>\n b.captured - a.captured ||\n b.customers - a.customers ||\n a.source.localeCompare(b.source),\n ),\n unsourced,\n customers,\n total: facets.length,\n }\n}\n\n/*==========================================\n * WON AND LOST, BY OWNER (AGL-2662)\n *=========================================*/\n\n/** A closed deal as the by-owner tally reads it. */\ntype OwnerOutcomeDeal = Pick<CrmDeal, 'ownerUid' | 'amountCents'>\n\n/** The key an unowned deal is tallied under — see {@link OwnerOutcomeRow.uid}. */\nexport const OWNER_UNASSIGNED_KEY = ''\n\nexport interface OwnerOutcomeRow {\n /**\n * The owner's account uid, or `''` for a deal nobody owns.\n *\n * One row for the unowned rather than none: a pipeline where a third of\n * what closed had no owner is a fact about the team, and dropping those\n * deals would make the rows add to less than the period's totals without\n * saying why.\n */\n uid: string\n won: number\n lost: number\n /** Value of what this owner won, in the deals' own minor units. */\n wonAmountCents: number\n /** Value of what they lost — the size of the miss, not a negative. */\n lostAmountCents: number\n /** Everything that closed for this owner, won or lost. */\n closed: number\n /**\n * Won over closed, 0–1, or `null` when nothing of theirs closed.\n *\n * Null rather than zero because the two read differently: an owner who\n * closed nothing has no rate, and rendering that as 0% would rank a\n * person who was not in the period below one who lost every deal.\n */\n winRate: number | null\n}\n\n/**\n * Who closed what, in the period's window.\n *\n * The by-owner reading of the same two sets the Won and lost card already\n * holds — everything that closed won, and everything that closed lost — so\n * this adds no read. Credited to `ownerUid`, the field the board and the\n * bulk bar both write, and never to the creator: a deal reassigned mid-cycle\n * belongs to whoever carried it at the close, which is what \"by owner\"\n * is asked to mean when a manager reads it.\n *\n * Ranked by how much CLOSED rather than by win rate, for the reason\n * `conversionBySource` ranks by captured: an owner who closed one deal and\n * won it is not the best closer on the team. Ties break on wins, then on\n * the uid, so two renders of one window draw one order.\n */\nexport function wonLostByOwner(\n won: readonly OwnerOutcomeDeal[],\n lost: readonly OwnerOutcomeDeal[],\n): OwnerOutcomeRow[] {\n const rows = new Map<string, OwnerOutcomeRow>()\n const rowFor = (uid: string) => {\n let row = rows.get(uid)\n if (!row) {\n row = {\n uid,\n won: 0,\n lost: 0,\n wonAmountCents: 0,\n lostAmountCents: 0,\n closed: 0,\n winRate: null,\n }\n rows.set(uid, row)\n }\n return row\n }\n /** A missing or negative amount counts as nothing, never as a debit. */\n const cents = (deal: OwnerOutcomeDeal) =>\n Math.max(0, Number(deal.amountCents ?? 0) || 0)\n for (const deal of won) {\n const row = rowFor(String(deal.ownerUid ?? OWNER_UNASSIGNED_KEY))\n row.won += 1\n row.closed += 1\n row.wonAmountCents += cents(deal)\n }\n for (const deal of lost) {\n const row = rowFor(String(deal.ownerUid ?? OWNER_UNASSIGNED_KEY))\n row.lost += 1\n row.closed += 1\n row.lostAmountCents += cents(deal)\n }\n return [...rows.values()]\n .map((row) => ({\n ...row,\n winRate: row.closed ? row.won / row.closed : null,\n }))\n .sort(\n (a, b) => b.closed - a.closed || b.won - a.won || a.uid.localeCompare(b.uid),\n )\n}\n\n/*==========================================\n * LEADS BY LEAD SOURCE (AGL-3511)\n *=========================================*/\n\n/** A lead as the lead source tally reads it. */\ntype LeadSourceLead = Pick<CrmLeadFields, 'status' | 'leadSource'>\n\n/** The key a lead with no lead source is tallied under. */\nexport const LEAD_SOURCE_NONE_KEY = ''\n\nexport interface LeadSourceRow {\n /** The label as the org's list spells it, as the lead holds it when unlisted, `''` for none. */\n label: string\n /** The value's group in the org's list; `null` for none, an ungrouped value or an unlisted one. */\n direction: LeadSourceDirection | null\n /** Whether the org's list holds the value at all. */\n listed: boolean\n leads: number\n /** Of those, the ones qualified — converted to a contact. */\n qualified: number\n /** `qualified / leads`, 0–1. */\n rate: number\n}\n\nexport interface LeadSourceDirectionRow {\n /** `null` is Unspecified: no lead source, or one in no group. */\n direction: LeadSourceDirection | null\n leads: number\n qualified: number\n rate: number\n}\n\nexport interface LeadSourceBreakdown {\n /** One row per value any lead holds, in the order the org keeps its list; unlisted after, none last. */\n rows: LeadSourceRow[]\n /** Inbound, Outbound and Unspecified, each present even at zero. */\n directions: LeadSourceDirectionRow[]\n total: number\n qualified: number\n}\n\n/**\n * The period's leads by their lead source, and by the direction each\n * source's group gives it.\n *\n * Grouped through the org's EFFECTIVE list as it stands now: two spellings\n * of one value are one row, a value is listed under the group it is in\n * today, and a label the list no longer holds stays a row of its own rather\n * than being folded into another. Qualified is the conversion the Lead\n * funnel counts — a lead the conversion closed as a contact.\n */\nexport function leadsByLeadSource(\n leads: readonly LeadSourceLead[],\n picklist: CrmPicklist,\n): LeadSourceBreakdown {\n const rows = new Map<string, LeadSourceRow>()\n const directions = new Map<LeadSourceDirection | null, LeadSourceDirectionRow>(\n (['inbound', 'outbound', null] as const).map((direction) => [\n direction,\n { direction, leads: 0, qualified: 0, rate: 0 },\n ]),\n )\n let qualified = 0\n for (const lead of leads) {\n const isQualified = crmLeadStatus(lead) === 'qualified'\n if (isQualified) qualified += 1\n const held = String(lead.leadSource ?? '').replace(/\\s+/g, ' ').trim()\n const value = held ? crmPicklistValueByLabel(picklist, held) : null\n const label = value?.label ?? held\n const key = label.toLowerCase()\n const direction = held ? leadSourceDirectionOf(picklist, held) : null\n const row = rows.get(key) ?? {\n label,\n direction,\n listed: Boolean(value),\n leads: 0,\n qualified: 0,\n rate: 0,\n }\n row.leads += 1\n if (isQualified) row.qualified += 1\n rows.set(key, row)\n const tally = directions.get(direction) as LeadSourceDirectionRow\n tally.leads += 1\n if (isQualified) tally.qualified += 1\n }\n const rate = <T extends { leads: number; qualified: number }>(row: T): T => ({\n ...row,\n rate: row.leads > 0 ? row.qualified / row.leads : 0,\n })\n const rank = (row: LeadSourceRow) =>\n row.label === LEAD_SOURCE_NONE_KEY ? Number.MAX_SAFE_INTEGER : crmPicklistRank(picklist, row.label)\n return {\n rows: [...rows.values()]\n .map(rate)\n .sort((a, b) => rank(a) - rank(b) || a.label.localeCompare(b.label)),\n directions: [...directions.values()].map(rate),\n total: leads.length,\n qualified,\n }\n}\n"],"names":["CONTACT_LIFECYCLE_STAGES","CONTACT_LIFECYCLE_STAGE_LABELS","CRM_ACTIVITY_KINDS","CRM_LEAD_STATUSES","crmLeadStatus","crmPicklistRank","crmPicklistValueByLabel","dealForecastCategory","dealStageById","isCrmActivityKind","isPipelineArchived","weightedDealAmountCents","leadSourceDirectionOf","DAY_MS","WEEK_MS","CRM_REPORT_PERIODS","CRM_REPORT_PERIOD_LABELS","month","crmReportRange","period","nowMs","now","Date","from","getFullYear","getMonth","getTime","previousFrom","to","previousTo","days","weekBuckets","buckets","start","push","end","Math","min","bucketByWeek","items","atMs","map","bounds","length","item","at","Number","isFinite","index","floor","funnelFromStages","counts","countOf","stage","value","round","progression","filter","steps","reachedAfter","count","reached","unshift","label","conversion","previous","other","total","reduce","sum","step","pipelineTotals","deals","pipeline","stages","kind","sort","a","b","order","rows","Map","id","amountCents","weightedCents","totals","get","unplaced","deal","status","amount","max","row","stageId","weighted","FORECAST_MONTHS","closeMonthBuckets","months","offset","key","String","padStart","emptyCell","addToCell","cell","forecastByCloseMonth","pipelines","byId","pipelineRow","pipelineId","find","entry","$id","name","undated","overdue","later","set","first","last","expectedCloseAtMs","cells","findIndex","bucket","own","all","values","FORECAST_CATEGORY_ROWS","forecastByCategory","openDeals","wonDeals","forecast","bestCase","commit","closed","omitted","category","tally","keyOf","entries","localeCompare","deltaPercent","current","currencyOfDeals","ranked","currency","toLowerCase","mixed","localDayBounds","getDate","activityLeaderboard","activities","tasksDone","namedAtMs","rowFor","uid","kinds","Object","fromEntries","activity","byUid","byName","trim","NEGATIVE_INFINITY","task","completedByUid","assigneeUid","LEAD_NO_REASON_KEY","LEAD_NO_REASON_LABEL","leadFunnel","leads","byStatus","reasons","lead","unqualifiedReason","replace","open","new","nurturing","working","qualifiedRate","qualified","unqualifiedRate","unqualified","CUSTOMER_STAGES","Set","contactIsCustomer","facet","lifecycleStage","has","orders","ordersCount","conversionBySource","facets","unsourced","customers","isCustomer","sources","keys","source","captured","rate","OWNER_UNASSIGNED_KEY","wonLostByOwner","won","lost","wonAmountCents","lostAmountCents","winRate","cents","ownerUid","LEAD_SOURCE_NONE_KEY","leadsByLeadSource","picklist","directions","direction","isQualified","held","leadSource","listed","Boolean","rank","MAX_SAFE_INTEGER"],"mappings":"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"}
@@ -57,6 +57,7 @@
57
57
  * the CRM's routes check it; a read-only grant is the default.
58
58
  */
59
59
  import type { ScopeToken } from '@aglyn/aglyn';
60
+ import type { CrmPicklist } from '@aglyn/aglyn/app-utils/crm';
60
61
  /** The four record types a share or a rule applies to, by their collection. */
61
62
  export declare const CRM_SHARING_OBJECTS: readonly ["leads", "contacts", "companies", "deals"];
62
63
  export type CrmSharingObject = (typeof CRM_SHARING_OBJECTS)[number];
@@ -158,6 +159,13 @@ export declare const CRM_SHARING_CRITERION_MAX = 20;
158
159
  export interface CrmSharingCriteria {
159
160
  /** A lead's or a contact's lead source, by label. */
160
161
  leadSources?: string[];
162
+ /**
163
+ * A lead's or a contact's lead source DIRECTION — `inbound` or `outbound`,
164
+ * the Lead source picklist's groups (AGL-3511). Expanded through the org's
165
+ * list when the rule is evaluated, so a value moved between groups moves
166
+ * the records that hold it.
167
+ */
168
+ leadSourceGroups?: string[];
161
169
  /** Any of these tags (lead, contact, company). */
162
170
  tags?: string[];
163
171
  /** Filed under any of these campaigns (lead, contact). */
@@ -167,7 +175,7 @@ export interface CrmSharingCriteria {
167
175
  /** Owned by any of these members. */
168
176
  ownerUids?: string[];
169
177
  }
170
- export declare const CRM_SHARING_CRITERIA_KEYS: readonly ["leadSources", "tags", "campaignIds", "stages", "ownerUids"];
178
+ export declare const CRM_SHARING_CRITERIA_KEYS: readonly ["leadSources", "leadSourceGroups", "tags", "campaignIds", "stages", "ownerUids"];
171
179
  /** Which criteria an object's records can answer. */
172
180
  export declare const CRM_SHARING_CRITERIA_FOR: Readonly<Record<CrmSharingObject, readonly (keyof CrmSharingCriteria)[]>>;
173
181
  /** Where a rule's recompute stands. */
@@ -232,8 +240,18 @@ export interface CrmSharingFacts {
232
240
  * any holder tagged.
233
241
  */
234
242
  export declare function crmSharingFacts(object: CrmSharingObject, record: Readonly<Record<string, unknown>>): CrmSharingFacts;
243
+ /**
244
+ * What a rule is evaluated against beyond the record: the org's Lead source
245
+ * list, which a direction criterion is expanded through (AGL-3511). Absent,
246
+ * the standard values alone answer.
247
+ */
248
+ export interface CrmSharingContext {
249
+ leadSources?: CrmPicklist;
250
+ }
251
+ /** Whether any active rule for `object` asks a lead source direction — whether the list must be read. */
252
+ export declare function sharingRulesAskLeadSourceDirection(rules: readonly CrmSharingRule[], object: CrmSharingObject): boolean;
235
253
  /** Whether a rule shares this record. */
236
- export declare function crmSharingRuleMatches(rule: CrmSharingRule, object: CrmSharingObject, record: Readonly<Record<string, unknown>>): boolean;
254
+ export declare function crmSharingRuleMatches(rule: CrmSharingRule, object: CrmSharingObject, record: Readonly<Record<string, unknown>>, context?: CrmSharingContext): boolean;
237
255
  /**
238
256
  * The grants a record should carry: its hand shares as they are, and one
239
257
  * grant per active rule that matches it now. A rule grant already present
@@ -243,7 +261,7 @@ export declare function crmSharingRuleMatches(rule: CrmSharingRule, object: CrmS
243
261
  * passes every active rule of the org, so a rule absent from them has been
244
262
  * switched off or deleted.
245
263
  */
246
- export declare function evaluateRecordGrants(object: CrmSharingObject, record: Readonly<Record<string, unknown>>, rules: readonly CrmSharingRule[], atMs: number): Record<string, CrmShareGrant>;
264
+ export declare function evaluateRecordGrants(object: CrmSharingObject, record: Readonly<Record<string, unknown>>, rules: readonly CrmSharingRule[], atMs: number, context?: CrmSharingContext): Record<string, CrmShareGrant>;
247
265
  /** The hand shares after sharing with `targets`, by `by`. */
248
266
  export declare function withManualShares(grants: Readonly<Record<string, CrmShareGrant>>, targets: readonly ScopeToken[], access: CrmShareAccess, by: {
249
267
  uid: string;
@@ -56,6 +56,7 @@
56
56
  * the CRM's routes check it; a read-only grant is the default.
57
57
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
58
58
  import { heldScopeTokens, hostIdsFromScope, hostScopeToken, isScopeToken, MAX_SCOPE_HOSTS, ORG_SCOPE_TOKEN, parseScopeToken, SCOPE_GRANTS_FIELD } from "@aglyn/aglyn/app-utils/scope-tokens";
59
+ import { isLeadSourceDirection, LEAD_SOURCE_DIRECTION_LABELS, leadSourceDirectionOf, STANDARD_LEAD_SOURCES } from "./lead-source-direction.js";
59
60
  /*==========================================
60
61
  * WHAT IS SHARED.
61
62
  *=========================================*/ /** The four record types a share or a rule applies to, by their collection. */ export const CRM_SHARING_OBJECTS = [
@@ -244,6 +245,7 @@ const sameGrant = (a, b)=>{
244
245
  /** At most this many values per criterion. */ export const CRM_SHARING_CRITERION_MAX = 20;
245
246
  export const CRM_SHARING_CRITERIA_KEYS = [
246
247
  'leadSources',
248
+ 'leadSourceGroups',
247
249
  'tags',
248
250
  'campaignIds',
249
251
  'stages',
@@ -252,6 +254,7 @@ export const CRM_SHARING_CRITERIA_KEYS = [
252
254
  /** Which criteria an object's records can answer. */ export const CRM_SHARING_CRITERIA_FOR = {
253
255
  leads: [
254
256
  'leadSources',
257
+ 'leadSourceGroups',
255
258
  'tags',
256
259
  'campaignIds',
257
260
  'stages',
@@ -259,6 +262,7 @@ export const CRM_SHARING_CRITERIA_KEYS = [
259
262
  ],
260
263
  contacts: [
261
264
  'leadSources',
265
+ 'leadSourceGroups',
262
266
  'tags',
263
267
  'campaignIds',
264
268
  'stages',
@@ -296,7 +300,9 @@ function stringList(raw, max = CRM_SHARING_CRITERION_MAX) {
296
300
  const rawCriteria = (_value_criteria = value['criteria']) != null ? _value_criteria : {};
297
301
  const criteria = {};
298
302
  for (const key of CRM_SHARING_CRITERIA_FOR[value['object']]){
299
- const list = stringList(rawCriteria[key]);
303
+ let list = stringList(rawCriteria[key]);
304
+ // A direction is one of the picklist's groups, never free text.
305
+ if (key === 'leadSourceGroups') list = list.filter(isLeadSourceDirection);
300
306
  if (list.length) criteria[key] = key === 'tags' ? list.map((tag)=>tag.toLowerCase()) : list;
301
307
  }
302
308
  const run = value['run'];
@@ -412,14 +418,25 @@ const anyOf = (wanted, have, fold = false)=>{
412
418
  const set = new Set(fold ? have.map((value)=>value.toLowerCase()) : have);
413
419
  return wanted.some((value)=>set.has(fold ? value.toLowerCase() : value));
414
420
  };
415
- /** Whether a rule shares this record. */ export function crmSharingRuleMatches(rule, object, record) {
421
+ /** Whether any active rule for `object` asks a lead source direction — whether the list must be read. */ export function sharingRulesAskLeadSourceDirection(rules, object) {
422
+ return activeSharingRules(rules, object).some((rule)=>{
423
+ var _rule_criteria_leadSourceGroups;
424
+ return Boolean((_rule_criteria_leadSourceGroups = rule.criteria.leadSourceGroups) == null ? void 0 : _rule_criteria_leadSourceGroups.length);
425
+ });
426
+ }
427
+ /** Whether a rule shares this record. */ export function crmSharingRuleMatches(rule, object, record, context = {}) {
428
+ var _criteria_leadSourceGroups;
416
429
  if (!rule.enabled || rule.deleting || rule.object !== object) return false;
417
430
  const facts = crmSharingFacts(object, record);
418
431
  if (rule.sourceHostIds.length && !facts.hostIds.some((id)=>rule.sourceHostIds.includes(id))) {
419
432
  return false;
420
433
  }
421
434
  const { criteria } = rule;
422
- return anyOf(criteria.leadSources, facts.leadSources, true) && anyOf(criteria.tags, facts.tags, true) && anyOf(criteria.campaignIds, facts.campaignIds) && anyOf(criteria.stages, facts.stages) && anyOf(criteria.ownerUids, facts.ownerUids);
435
+ const directions = ((_criteria_leadSourceGroups = criteria.leadSourceGroups) == null ? void 0 : _criteria_leadSourceGroups.length) ? facts.leadSources.map((label)=>{
436
+ var _context_leadSources;
437
+ return leadSourceDirectionOf((_context_leadSources = context.leadSources) != null ? _context_leadSources : STANDARD_LEAD_SOURCES, label);
438
+ }).filter((direction)=>direction !== null) : [];
439
+ return anyOf(criteria.leadSources, facts.leadSources, true) && anyOf(criteria.leadSourceGroups, directions) && anyOf(criteria.tags, facts.tags, true) && anyOf(criteria.campaignIds, facts.campaignIds) && anyOf(criteria.stages, facts.stages) && anyOf(criteria.ownerUids, facts.ownerUids);
423
440
  }
424
441
  /**
425
442
  * The grants a record should carry: its hand shares as they are, and one
@@ -429,14 +446,14 @@ const anyOf = (wanted, have, fold = false)=>{
429
446
  * A rule grant whose rule is not among `rules` is dropped — the caller
430
447
  * passes every active rule of the org, so a rule absent from them has been
431
448
  * switched off or deleted.
432
- */ export function evaluateRecordGrants(object, record, rules, atMs) {
449
+ */ export function evaluateRecordGrants(object, record, rules, atMs, context = {}) {
433
450
  const current = readRecordGrants(record);
434
451
  const next = {};
435
452
  for (const [key, grant] of Object.entries(current)){
436
453
  if (grant.source === 'manual') next[key] = grant;
437
454
  }
438
455
  for (const rule of activeSharingRules(rules, object)){
439
- if (!crmSharingRuleMatches(rule, object, record)) continue;
456
+ if (!crmSharingRuleMatches(rule, object, record, context)) continue;
440
457
  const key = ruleGrantKey(rule.id);
441
458
  const before = current[key];
442
459
  next[key] = {
@@ -567,7 +584,7 @@ const anyOf = (wanted, have, fold = false)=>{
567
584
  return `${ids.length} sites`;
568
585
  }
569
586
  /** A rule as one sentence of its "which records" half. */ export function describeSharingRuleScope(rule, siteName, labels = {}) {
570
- var _criteria_leadSources, _criteria_tags, _criteria_campaignIds, _criteria_stages, _criteria_ownerUids;
587
+ var _criteria_leadSources, _criteria_leadSourceGroups, _criteria_tags, _criteria_campaignIds, _criteria_stages, _criteria_ownerUids;
571
588
  const parts = [
572
589
  CRM_SHARING_OBJECT_LABELS[rule.object].many
573
590
  ];
@@ -576,6 +593,10 @@ const anyOf = (wanted, have, fold = false)=>{
576
593
  }
577
594
  const { criteria } = rule;
578
595
  if ((_criteria_leadSources = criteria.leadSources) == null ? void 0 : _criteria_leadSources.length) parts.push(`from ${criteria.leadSources.join(' or ')}`);
596
+ if ((_criteria_leadSourceGroups = criteria.leadSourceGroups) == null ? void 0 : _criteria_leadSourceGroups.length) {
597
+ const directions = criteria.leadSourceGroups.map((group)=>isLeadSourceDirection(group) ? LEAD_SOURCE_DIRECTION_LABELS[group] : group);
598
+ parts.push(`${directions.join(' or ')} by lead source`);
599
+ }
579
600
  if ((_criteria_tags = criteria.tags) == null ? void 0 : _criteria_tags.length) parts.push(`tagged ${criteria.tags.join(' or ')}`);
580
601
  if ((_criteria_campaignIds = criteria.campaignIds) == null ? void 0 : _criteria_campaignIds.length) {
581
602
  parts.push(`in ${criteria.campaignIds.length === 1 ? 'a campaign' : `${criteria.campaignIds.length} campaigns`}`);