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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/package.json +15 -11
  2. package/src/lib/app-utils/agent-openapi.js +1 -1
  3. package/src/lib/app-utils/agent-openapi.js.map +1 -1
  4. package/src/lib/app-utils/analytics-events.d.ts +3 -2
  5. package/src/lib/app-utils/analytics-events.js +3 -2
  6. package/src/lib/app-utils/analytics-events.js.map +1 -1
  7. package/src/lib/app-utils/api-plugins.d.ts +30 -0
  8. package/src/lib/app-utils/api-plugins.js +24 -1
  9. package/src/lib/app-utils/api-plugins.js.map +1 -1
  10. package/src/lib/app-utils/author-html.d.ts +1 -1
  11. package/src/lib/app-utils/author-html.js.map +1 -1
  12. package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
  13. package/src/lib/app-utils/bandwidth-cap.js +17 -1
  14. package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
  15. package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
  16. package/src/lib/app-utils/compose-reusable-components.js +116 -26
  17. package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
  18. package/src/lib/app-utils/console-routes.d.ts +4 -0
  19. package/src/lib/app-utils/console-routes.js +3 -0
  20. package/src/lib/app-utils/console-routes.js.map +1 -1
  21. package/src/lib/app-utils/contacts.d.ts +71 -0
  22. package/src/lib/app-utils/contacts.js +55 -0
  23. package/src/lib/app-utils/contacts.js.map +1 -1
  24. package/src/lib/app-utils/contexts.d.ts +1 -0
  25. package/src/lib/app-utils/contexts.js +1 -0
  26. package/src/lib/app-utils/contexts.js.map +1 -1
  27. package/src/lib/app-utils/crm-email-templates.js +21 -4
  28. package/src/lib/app-utils/crm-email-templates.js.map +1 -1
  29. package/src/lib/app-utils/crm-kinds.d.ts +2 -0
  30. package/src/lib/app-utils/crm-kinds.js +1 -0
  31. package/src/lib/app-utils/crm-kinds.js.map +1 -1
  32. package/src/lib/app-utils/crm.d.ts +1199 -67
  33. package/src/lib/app-utils/crm.js +1951 -310
  34. package/src/lib/app-utils/crm.js.map +1 -1
  35. package/src/lib/app-utils/csv-import.d.ts +10 -38
  36. package/src/lib/app-utils/csv-import.js +17 -42
  37. package/src/lib/app-utils/csv-import.js.map +1 -1
  38. package/src/lib/app-utils/csv.d.ts +32 -11
  39. package/src/lib/app-utils/csv.js +43 -5
  40. package/src/lib/app-utils/csv.js.map +1 -1
  41. package/src/lib/app-utils/default-site.d.ts +116 -0
  42. package/src/lib/app-utils/default-site.js +1024 -0
  43. package/src/lib/app-utils/default-site.js.map +1 -0
  44. package/src/lib/app-utils/docs-help.generated.d.ts +22 -22
  45. package/src/lib/app-utils/docs-help.generated.js +44 -10
  46. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  47. package/src/lib/app-utils/docs-index.generated.js +738 -150
  48. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  49. package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
  50. package/src/lib/app-utils/draft-placeholders.js +115 -0
  51. package/src/lib/app-utils/draft-placeholders.js.map +1 -0
  52. package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
  53. package/src/lib/app-utils/entity-picker-context.js +4 -4
  54. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  55. package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
  56. package/src/lib/app-utils/expand-repeatables.js +88 -14
  57. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  58. package/src/lib/app-utils/forms.d.ts +33 -22
  59. package/src/lib/app-utils/forms.js +47 -28
  60. package/src/lib/app-utils/forms.js.map +1 -1
  61. package/src/lib/app-utils/full-use-cost.d.ts +277 -0
  62. package/src/lib/app-utils/full-use-cost.js +421 -0
  63. package/src/lib/app-utils/full-use-cost.js.map +1 -0
  64. package/src/lib/app-utils/health-report.js +11 -0
  65. package/src/lib/app-utils/health-report.js.map +1 -1
  66. package/src/lib/app-utils/llms-txt.d.ts +20 -0
  67. package/src/lib/app-utils/llms-txt.js +7 -1
  68. package/src/lib/app-utils/llms-txt.js.map +1 -1
  69. package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
  70. package/src/lib/app-utils/media-asset-facts.js +40 -13
  71. package/src/lib/app-utils/media-asset-facts.js.map +1 -1
  72. package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
  73. package/src/lib/app-utils/media-bandwidth.js +94 -0
  74. package/src/lib/app-utils/media-bandwidth.js.map +1 -0
  75. package/src/lib/app-utils/media-ref.d.ts +241 -33
  76. package/src/lib/app-utils/media-ref.js +324 -43
  77. package/src/lib/app-utils/media-ref.js.map +1 -1
  78. package/src/lib/app-utils/media-src.d.ts +8 -8
  79. package/src/lib/app-utils/media-src.js +27 -11
  80. package/src/lib/app-utils/media-src.js.map +1 -1
  81. package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
  82. package/src/lib/app-utils/media-takedown-reach.js +6 -3
  83. package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
  84. package/src/lib/app-utils/notifications.d.ts +1 -1
  85. package/src/lib/app-utils/notifications.js +2 -0
  86. package/src/lib/app-utils/notifications.js.map +1 -1
  87. package/src/lib/app-utils/organizations.d.ts +51 -2
  88. package/src/lib/app-utils/organizations.js +56 -3
  89. package/src/lib/app-utils/organizations.js.map +1 -1
  90. package/src/lib/app-utils/page-record-sources.d.ts +87 -0
  91. package/src/lib/app-utils/page-record-sources.js +53 -0
  92. package/src/lib/app-utils/page-record-sources.js.map +1 -0
  93. package/src/lib/app-utils/paid-media-source.d.ts +3 -7
  94. package/src/lib/app-utils/paid-media-source.js +5 -19
  95. package/src/lib/app-utils/paid-media-source.js.map +1 -1
  96. package/src/lib/app-utils/person-erasure.d.ts +5 -2
  97. package/src/lib/app-utils/person-erasure.js.map +1 -1
  98. package/src/lib/app-utils/picklists.d.ts +187 -0
  99. package/src/lib/app-utils/picklists.js +320 -0
  100. package/src/lib/app-utils/picklists.js.map +1 -0
  101. package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
  102. package/src/lib/app-utils/plan-entitlements.js +309 -115
  103. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  104. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  105. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  106. package/src/lib/app-utils/release-flags.d.ts +1 -1
  107. package/src/lib/app-utils/release-flags.js +11 -0
  108. package/src/lib/app-utils/release-flags.js.map +1 -1
  109. package/src/lib/app-utils/rendered-widths.d.ts +121 -0
  110. package/src/lib/app-utils/rendered-widths.js +211 -0
  111. package/src/lib/app-utils/rendered-widths.js.map +1 -0
  112. package/src/lib/app-utils/repeat-sources.d.ts +2 -1
  113. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  114. package/src/lib/app-utils/screen-route.d.ts +154 -36
  115. package/src/lib/app-utils/screen-route.js +185 -40
  116. package/src/lib/app-utils/screen-route.js.map +1 -1
  117. package/src/lib/app-utils/seo-audit.d.ts +11 -1
  118. package/src/lib/app-utils/seo-audit.js +33 -13
  119. package/src/lib/app-utils/seo-audit.js.map +1 -1
  120. package/src/lib/app-utils/seo-keywords.d.ts +11 -0
  121. package/src/lib/app-utils/seo-keywords.js +19 -5
  122. package/src/lib/app-utils/seo-keywords.js.map +1 -1
  123. package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
  124. package/src/lib/app-utils/seo-page-facts.js +136 -31
  125. package/src/lib/app-utils/seo-page-facts.js.map +1 -1
  126. package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
  127. package/src/lib/app-utils/seo-site-scan.js +113 -12
  128. package/src/lib/app-utils/seo-site-scan.js.map +1 -1
  129. package/src/lib/app-utils/server.d.ts +5 -4
  130. package/src/lib/app-utils/server.js +14 -5
  131. package/src/lib/app-utils/server.js.map +1 -1
  132. package/src/lib/app-utils/site-icon-set.d.ts +195 -0
  133. package/src/lib/app-utils/site-icon-set.js +351 -0
  134. package/src/lib/app-utils/site-icon-set.js.map +1 -0
  135. package/src/lib/app-utils/site-interactions.d.ts +101 -4
  136. package/src/lib/app-utils/site-interactions.js +120 -4
  137. package/src/lib/app-utils/site-interactions.js.map +1 -1
  138. package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
  139. package/src/lib/app-utils/starter-template-nodes.js +3 -0
  140. package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
  141. package/src/lib/app-utils/starter-templates.d.ts +5 -19
  142. package/src/lib/app-utils/starter-templates.js +5 -49
  143. package/src/lib/app-utils/starter-templates.js.map +1 -1
  144. package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
  145. package/src/lib/app-utils/transfer-launcher-context.js +23 -0
  146. package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
  147. package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
  148. package/src/lib/app-utils/transfer-resume-import.js +128 -0
  149. package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
  150. package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
  151. package/src/lib/app-utils/upgrade-proposal.js +95 -0
  152. package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
  153. package/src/lib/app-utils/utc-month.d.ts +33 -0
  154. package/src/lib/app-utils/utc-month.js +35 -0
  155. package/src/lib/app-utils/utc-month.js.map +1 -0
  156. package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
  157. package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
  158. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  159. package/src/lib/app-utils/wire-nodes.d.ts +39 -19
  160. package/src/lib/app-utils/wire-nodes.js +120 -34
  161. package/src/lib/app-utils/wire-nodes.js.map +1 -1
  162. package/src/lib/data-transfer/access.d.ts +74 -0
  163. package/src/lib/data-transfer/access.js +93 -0
  164. package/src/lib/data-transfer/access.js.map +1 -0
  165. package/src/lib/data-transfer/derive.d.ts +246 -0
  166. package/src/lib/data-transfer/derive.js +1007 -0
  167. package/src/lib/data-transfer/derive.js.map +1 -0
  168. package/src/lib/data-transfer/export-file.d.ts +46 -0
  169. package/src/lib/data-transfer/export-file.js +146 -0
  170. package/src/lib/data-transfer/export-file.js.map +1 -0
  171. package/src/lib/data-transfer/field-catalog.d.ts +134 -0
  172. package/src/lib/data-transfer/field-catalog.js +278 -0
  173. package/src/lib/data-transfer/field-catalog.js.map +1 -0
  174. package/src/lib/data-transfer/header-match.d.ts +129 -0
  175. package/src/lib/data-transfer/header-match.js +367 -0
  176. package/src/lib/data-transfer/header-match.js.map +1 -0
  177. package/src/lib/data-transfer/index.d.ts +34 -0
  178. package/src/lib/data-transfer/index.js +40 -0
  179. package/src/lib/data-transfer/index.js.map +1 -0
  180. package/src/lib/data-transfer/job.d.ts +190 -0
  181. package/src/lib/data-transfer/job.js +229 -0
  182. package/src/lib/data-transfer/job.js.map +1 -0
  183. package/src/lib/data-transfer/lookup.d.ts +45 -0
  184. package/src/lib/data-transfer/lookup.js +71 -0
  185. package/src/lib/data-transfer/lookup.js.map +1 -0
  186. package/src/lib/data-transfer/match.d.ts +99 -0
  187. package/src/lib/data-transfer/match.js +244 -0
  188. package/src/lib/data-transfer/match.js.map +1 -0
  189. package/src/lib/data-transfer/package-plan.d.ts +205 -0
  190. package/src/lib/data-transfer/package-plan.js +475 -0
  191. package/src/lib/data-transfer/package-plan.js.map +1 -0
  192. package/src/lib/data-transfer/package.d.ts +181 -0
  193. package/src/lib/data-transfer/package.js +426 -0
  194. package/src/lib/data-transfer/package.js.map +1 -0
  195. package/src/lib/data-transfer/picklist-map.d.ts +139 -0
  196. package/src/lib/data-transfer/picklist-map.js +271 -0
  197. package/src/lib/data-transfer/picklist-map.js.map +1 -0
  198. package/src/lib/data-transfer/plan.d.ts +176 -0
  199. package/src/lib/data-transfer/plan.js +451 -0
  200. package/src/lib/data-transfer/plan.js.map +1 -0
  201. package/src/lib/data-transfer/policy.d.ts +170 -0
  202. package/src/lib/data-transfer/policy.js +378 -0
  203. package/src/lib/data-transfer/policy.js.map +1 -0
  204. package/src/lib/data-transfer/resource.d.ts +211 -0
  205. package/src/lib/data-transfer/resource.js +241 -0
  206. package/src/lib/data-transfer/resource.js.map +1 -0
  207. package/src/lib/data-transfer/review.d.ts +70 -0
  208. package/src/lib/data-transfer/review.js +233 -0
  209. package/src/lib/data-transfer/review.js.map +1 -0
  210. package/src/lib/data-transfer/similarity.d.ts +40 -0
  211. package/src/lib/data-transfer/similarity.js +106 -0
  212. package/src/lib/data-transfer/similarity.js.map +1 -0
  213. package/src/lib/data-transfer/site-package.d.ts +365 -0
  214. package/src/lib/data-transfer/site-package.js +1077 -0
  215. package/src/lib/data-transfer/site-package.js.map +1 -0
  216. package/src/lib/data-transfer/source.d.ts +74 -0
  217. package/src/lib/data-transfer/source.js +293 -0
  218. package/src/lib/data-transfer/source.js.map +1 -0
  219. package/src/lib/data-transfer/transfer-api.d.ts +898 -0
  220. package/src/lib/data-transfer/transfer-api.js +107 -0
  221. package/src/lib/data-transfer/transfer-api.js.map +1 -0
  222. package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
  223. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  224. package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
  225. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  226. package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
  227. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  228. package/src/lib/foundation/index.d.ts +1 -1
  229. package/src/lib/foundation/index.js.map +1 -1
  230. package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
  231. package/src/lib/plugin-manager/feature-plugins.js +48 -3
  232. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  233. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +50 -0
  234. package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
  235. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  236. package/src/lib/plugin-manager/index.d.ts +0 -1
  237. package/src/lib/plugin-manager/index.js +0 -1
  238. package/src/lib/plugin-manager/index.js.map +1 -1
  239. package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
  240. package/src/lib/plugin-manager/interaction-recipes.js +104 -0
  241. package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
  242. package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
  243. package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
  244. package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
  245. package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
  246. package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
  247. package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
  248. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
  249. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
  251. package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
  252. package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
  254. package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
  255. package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
  256. package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
  257. package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
  258. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
  259. package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
  260. package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
  261. package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
  263. package/src/lib/plugin-manager/plugin-jobs.js +7 -7
  264. package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
  265. package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
  266. package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
  267. package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
  269. package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
  270. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  271. package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
  272. package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
  273. package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
  274. package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
  275. package/src/lib/plugin-manager/plugin-person-records.js +107 -0
  276. package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
  277. package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
  278. package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
  279. package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
  280. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
  281. package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
  282. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  283. package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
  284. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
  285. package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
  286. package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
  287. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
  288. package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
  289. package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
  290. package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
  291. package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
  292. package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
  293. package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
  294. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
  295. package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
  296. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  297. package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
  298. package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
  299. package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
  300. package/src/lib/plugin-manager/plugin-revocations.js +47 -0
  301. package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
  302. package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
  303. package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
  304. package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
  305. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
  306. package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
  307. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
  308. package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
  309. package/src/lib/plugin-manager/plugin-site-export.js +23 -10
  310. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
  311. package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
  312. package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
  313. package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
  314. package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
  315. package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
  316. package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
  317. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
  318. package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
  319. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
  320. package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
  321. package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
  322. package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
  323. package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
  324. package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
  325. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  326. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  327. package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
  328. package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
  329. package/src/lib/app-utils/actions.d.ts +0 -310
  330. package/src/lib/app-utils/actions.js +0 -506
  331. package/src/lib/app-utils/actions.js.map +0 -1
  332. package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
  333. package/src/lib/app-utils/automation-placeholders.js +0 -207
  334. package/src/lib/app-utils/automation-placeholders.js.map +0 -1
  335. package/src/lib/app-utils/dataset-models.d.ts +0 -339
  336. package/src/lib/app-utils/dataset-models.js +0 -722
  337. package/src/lib/app-utils/dataset-models.js.map +0 -1
  338. package/src/lib/app-utils/datasets.d.ts +0 -171
  339. package/src/lib/app-utils/datasets.js +0 -227
  340. package/src/lib/app-utils/datasets.js.map +0 -1
  341. package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
  342. package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
  343. package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
  344. package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
  345. package/src/lib/plugin-manager/order-fulfilment.js +0 -104
  346. package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
@@ -44,10 +44,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
44
44
  * would otherwise write for itself. No Firestore, no React.
45
45
  */ import { normalizeAddress, normalizePhone } from "../foundation/index.js";
46
46
  import { consentGroupScope } from "./consent-groups.js";
47
- import { CONTACT_LIFECYCLE_STAGES, isContactLifecycleStage } from "./crm-kinds.js";
48
- import { CONTACT_ALTERNATE_EMAILS_FIELD, CONTACT_FACETS_FIELD, CONTACT_SOURCE_LABELS, normalizeContactEmail, readContactFacet } from "./contacts.js";
47
+ import { CONTACT_LIFECYCLE_STAGES, CRM_TASK_KINDS, isContactLifecycleStage } from "./crm-kinds.js";
48
+ import { CONTACT_ALTERNATE_EMAILS_FIELD, CONTACT_FACETS_FIELD, CONTACT_SOURCE_LABELS, composeContactName, normalizeContactEmail, readContactFacet } from "./contacts.js";
49
49
  import { isEmailStateStatus } from "./email-state.js";
50
50
  import { NAME_TOKEN_MAX_PREFIX, nameSearchKey, nameSearchTokens, SCOPED_SEARCH_JOIN, scopedSearchTokens } from "./name-search.js";
51
+ import { effectivePicklistValueSet, isStandardPicklistValueId, judgePicklistValue, mintPicklistValueId, normalizePicklistLabel, normalizePicklistValueSet, PICKLIST_VALUES_MAX, picklistActiveValues, picklistDefaultLabel, picklistFromLabels, picklistOptions, picklistRank, picklistRefusalSentence, picklistValueByLabel } from "./picklists.js";
51
52
  import { MAX_SCOPE_HOSTS, ORG_SCOPE_TOKEN } from "./scope-tokens.js";
52
53
  // The fixed vocabularies and their guards live in a leaf module; every name
53
54
  // stays importable from here.
@@ -201,62 +202,300 @@ export * from "./crm-kinds.js";
201
202
  var _advanceContactLifecycleStage;
202
203
  return (_advanceContactLifecycleStage = advanceContactLifecycleStage(current, 'customer')) != null ? _advanceContactLifecycleStage : 'customer';
203
204
  }
205
+ /*==========================================
206
+ * FORECAST CATEGORIES (AGL-3516).
207
+ *
208
+ * Salesforce's Opportunity forecast categories, a FIXED set — not a
209
+ * picklist an org edits, as in Salesforce, because a forecast rolls up by
210
+ * them and a renamed or added category would be one no forecast knows. A
211
+ * stage names the category a deal takes on landing in it; the deal stores
212
+ * its own copy, stamped at every stage move, which the deal page may then
213
+ * change for that deal alone.
214
+ *=========================================*/ export const CRM_FORECAST_CATEGORIES = [
215
+ 'omitted',
216
+ 'pipeline',
217
+ 'bestCase',
218
+ 'commit',
219
+ 'closed'
220
+ ];
221
+ export const CRM_FORECAST_CATEGORY_LABELS = {
222
+ omitted: 'Omitted',
223
+ pipeline: 'Pipeline',
224
+ bestCase: 'Best Case',
225
+ commit: 'Commit',
226
+ closed: 'Closed'
227
+ };
228
+ export function isCrmForecastCategory(value) {
229
+ return typeof value === 'string' && CRM_FORECAST_CATEGORIES.includes(value);
230
+ }
231
+ /**
232
+ * A category as typed — the stored key or its label in any case and
233
+ * spacing ("Best Case", "best case", `bestCase`) — or `null`.
234
+ */ export function readCrmForecastCategory(value) {
235
+ var _CRM_FORECAST_CATEGORIES_find;
236
+ if (isCrmForecastCategory(value)) return value;
237
+ const key = String(value != null ? value : '').trim().replace(/\s+/g, ' ').toLowerCase();
238
+ if (!key) return null;
239
+ return (_CRM_FORECAST_CATEGORIES_find = CRM_FORECAST_CATEGORIES.find((category)=>category.toLowerCase() === key || CRM_FORECAST_CATEGORY_LABELS[category].toLowerCase() === key)) != null ? _CRM_FORECAST_CATEGORIES_find : null;
240
+ }
241
+ /** The category a stage stamps: its own, else its kind's. */ export function dealStageForecastCategory(stage) {
242
+ if (isCrmForecastCategory(stage.forecastCategory)) return stage.forecastCategory;
243
+ return stage.kind === 'won' ? 'closed' : stage.kind === 'lost' ? 'omitted' : 'pipeline';
244
+ }
245
+ /**
246
+ * A deal's forecast category as every reader takes it: the deal's own,
247
+ * else its stage's, else its status's — a deal written before the field
248
+ * existed forecasts where its stage puts it.
249
+ */ export function dealForecastCategory(deal, stage) {
250
+ if (isCrmForecastCategory(deal.forecastCategory)) return deal.forecastCategory;
251
+ if (stage) return dealStageForecastCategory(stage);
252
+ return deal.status === 'won' ? 'closed' : deal.status === 'lost' ? 'omitted' : 'pipeline';
253
+ }
254
+ /** The longest Next step a deal keeps — Salesforce's own 255. */ export const DEAL_NEXT_STEP_MAX = 255;
255
+ /**
256
+ * A probability override as stored: a whole number 0–100, or `null` for
257
+ * none (the stage's applies). Anything that is not a number in range is
258
+ * `undefined` — unreadable, for the writer to refuse.
259
+ */ export function readDealProbability(value) {
260
+ if (value === null || value === undefined) return null;
261
+ if (typeof value === 'string' && !value.trim()) return null;
262
+ const number = typeof value === 'number' ? value : Number(String(value).trim().replace(/%$/, ''));
263
+ if (!Number.isFinite(number) || !Number.isInteger(number) || number < 0 || number > 100) {
264
+ return undefined;
265
+ }
266
+ return number;
267
+ }
268
+ /**
269
+ * A deal's probability: its own override when it holds one, else its
270
+ * stage's, else `null` for a stage the pipeline no longer has.
271
+ */ export function dealProbability(deal, stage) {
272
+ const own = readDealProbability(deal.probability);
273
+ if (typeof own === 'number') return own;
274
+ if (!stage) return null;
275
+ return Math.min(100, Math.max(0, Number(stage.probability) || 0));
276
+ }
277
+ /**
278
+ * What a stage move writes besides the stage, the status and the clocks:
279
+ * the new stage's forecast category, and the probability override CLEARED
280
+ * — Salesforce re-defaults a probability when the stage changes, so an
281
+ * override typed for the last stage does not follow the deal into the next.
282
+ * A write that sets either field itself, in the same request, wins.
283
+ */ export function dealStageMoveFields(stage) {
284
+ return {
285
+ forecastCategory: dealStageForecastCategory(stage),
286
+ probability: null
287
+ };
288
+ }
204
289
  /** Whether a pipeline has been retired — see {@link CrmPipeline.archivedAt}. */ export function isPipelineArchived(pipeline) {
205
290
  return typeof (pipeline == null ? void 0 : pipeline.archivedAt) === 'number' && pipeline.archivedAt > 0;
206
291
  }
207
292
  /**
208
- * The stages a fresh pipeline starts with.
293
+ * The stages a fresh pipeline starts with: Salesforce's standard
294
+ * Opportunity stages, with its probabilities and forecast categories
295
+ * (AGL-3516). A pipeline seeded before them keeps the stages it was seeded
296
+ * with — nothing migrates a stored pipeline.
297
+ *
298
+ * The closing stages keep the ids `won` and `lost` every pipeline has had,
299
+ * so an automation filter naming them reads the same on old pipelines and
300
+ * new ones.
209
301
  *
210
302
  * Readonly on purpose: a pipeline document stores its own COPY (`[...]`), so
211
303
  * a merchant editing their stages must not be editing the module's default,
212
304
  * and a second pipeline seeded later must start from the original set.
213
305
  */ export const DEFAULT_DEAL_STAGES = [
214
306
  {
215
- id: 'qualified',
216
- name: 'Qualified',
307
+ id: 'prospecting',
308
+ name: 'Prospecting',
217
309
  order: 0,
218
310
  probability: 10,
219
- kind: 'open'
311
+ kind: 'open',
312
+ forecastCategory: 'pipeline'
220
313
  },
221
314
  {
222
- id: 'contact-made',
223
- name: 'Contact made',
315
+ id: 'qualification',
316
+ name: 'Qualification',
224
317
  order: 1,
225
- probability: 20,
226
- kind: 'open'
318
+ probability: 10,
319
+ kind: 'open',
320
+ forecastCategory: 'pipeline'
227
321
  },
228
322
  {
229
- id: 'proposal-sent',
230
- name: 'Proposal sent',
323
+ id: 'needs-analysis',
324
+ name: 'Needs Analysis',
231
325
  order: 2,
232
- probability: 40,
233
- kind: 'open'
326
+ probability: 20,
327
+ kind: 'open',
328
+ forecastCategory: 'pipeline'
234
329
  },
235
330
  {
236
- id: 'negotiation',
237
- name: 'Negotiation',
331
+ id: 'value-proposition',
332
+ name: 'Value Proposition',
238
333
  order: 3,
334
+ probability: 50,
335
+ kind: 'open',
336
+ forecastCategory: 'pipeline'
337
+ },
338
+ {
339
+ id: 'id-decision-makers',
340
+ name: 'Id. Decision Makers',
341
+ order: 4,
239
342
  probability: 60,
240
- kind: 'open'
343
+ kind: 'open',
344
+ forecastCategory: 'pipeline'
345
+ },
346
+ {
347
+ id: 'perception-analysis',
348
+ name: 'Perception Analysis',
349
+ order: 5,
350
+ probability: 70,
351
+ kind: 'open',
352
+ forecastCategory: 'pipeline'
353
+ },
354
+ {
355
+ id: 'proposal-price-quote',
356
+ name: 'Proposal/Price Quote',
357
+ order: 6,
358
+ probability: 75,
359
+ kind: 'open',
360
+ forecastCategory: 'bestCase'
361
+ },
362
+ {
363
+ id: 'negotiation-review',
364
+ name: 'Negotiation/Review',
365
+ order: 7,
366
+ probability: 90,
367
+ kind: 'open',
368
+ forecastCategory: 'commit'
241
369
  },
242
370
  {
243
371
  id: 'won',
244
- name: 'Won',
245
- order: 4,
372
+ name: 'Closed Won',
373
+ order: 8,
246
374
  probability: 100,
247
- kind: 'won'
375
+ kind: 'won',
376
+ forecastCategory: 'closed'
248
377
  },
249
378
  {
250
379
  id: 'lost',
251
- name: 'Lost',
252
- order: 5,
380
+ name: 'Closed Lost',
381
+ order: 9,
253
382
  probability: 0,
254
- kind: 'lost'
383
+ kind: 'lost',
384
+ forecastCategory: 'omitted'
255
385
  }
256
386
  ];
257
387
  /** The most lines one deal carries — a quote, not a catalog. */ export const DEAL_LINE_ITEMS_MAX = 50;
258
388
  export const DEAL_LINE_ITEM_NAME_MAX = 120;
259
389
  /** Units per line; past this the number is a data-entry slip. */ export const DEAL_LINE_ITEM_QUANTITY_MAX = 1000000;
390
+ /*------------------------------------------
391
+ * OPPORTUNITY CONTACT ROLES (AGL-3521).
392
+ *
393
+ * A deal names any number of contacts, each with the part they play — a
394
+ * label of the org's `opportunityContactRole` picklist, or none — and at
395
+ * most one of them Primary. The Primary is `contactId`, which every reader
396
+ * that existed before roles keeps reading: the won-deal customer floor, the
397
+ * send-email button, the CSV's Contact column, the REST filter. So the two
398
+ * are written together by every door — setting a Primary sets `contactId`,
399
+ * and a door that sets `contactId` the old way makes that contact Primary,
400
+ * adding them without a role when the deal did not name them.
401
+ *
402
+ * A deal written before roles holds `contactId` alone, which reads as one
403
+ * Primary row with no role; its first write through any door stores it.
404
+ * Where the two disagree — a write from a door that knows only
405
+ * `contactId` — `contactId` wins, because it is what every older reader
406
+ * already acted on.
407
+ *-----------------------------------------*/ /** The most contacts one deal names — a buying committee, not a mailing list. */ export const DEAL_CONTACT_ROLES_MAX = 50;
408
+ /**
409
+ * Stored or sent roles, read defensively: a row without a usable contact
410
+ * id is dropped, a contact named twice keeps its first row, only the first
411
+ * Primary stays Primary, and the list stops at {@link DEAL_CONTACT_ROLES_MAX}.
412
+ */ export function readDealContactRoles(raw) {
413
+ if (!Array.isArray(raw)) return [];
414
+ const roles = [];
415
+ const seen = new Set();
416
+ let primaryTaken = false;
417
+ for (const entry of raw){
418
+ if (!entry || typeof entry !== 'object') continue;
419
+ const row = entry;
420
+ const contactId = typeof row['contactId'] === 'string' ? row['contactId'].trim() : '';
421
+ if (!contactId || contactId.length > 200 || contactId.includes('/') || seen.has(contactId)) {
422
+ continue;
423
+ }
424
+ seen.add(contactId);
425
+ const role = typeof row['role'] === 'string' ? normalizePicklistLabel(row['role']) : '';
426
+ const primary = row['primary'] === true && !primaryTaken;
427
+ if (primary) primaryTaken = true;
428
+ roles.push(_extends({
429
+ contactId
430
+ }, role ? {
431
+ role
432
+ } : {}, {
433
+ primary
434
+ }));
435
+ if (roles.length >= DEAL_CONTACT_ROLES_MAX) break;
436
+ }
437
+ return roles;
438
+ }
439
+ /**
440
+ * `roles` with `contactId` as the one Primary — added first, with no role,
441
+ * when the list does not name them — or, for `null`, with no Primary.
442
+ */ export function dealContactRolesWithPrimary(roles, contactId) {
443
+ const id = typeof contactId === 'string' ? contactId.trim() : '';
444
+ const next = roles.map((row)=>_extends({}, row, {
445
+ primary: Boolean(id) && row.contactId === id
446
+ }));
447
+ if (id && !next.some((row)=>row.contactId === id)) next.unshift({
448
+ contactId: id,
449
+ primary: true
450
+ });
451
+ return next.slice(0, DEAL_CONTACT_ROLES_MAX);
452
+ }
453
+ /**
454
+ * The roles a deal holds, in step with its `contactId`: the stored list
455
+ * with the contact `contactId` names as its Primary — so a deal written
456
+ * before roles reads as its one contact, Primary, with no role.
457
+ */ export function dealContactRolesOf(deal) {
458
+ const contactId = typeof deal.contactId === 'string' ? deal.contactId : null;
459
+ return dealContactRolesWithPrimary(readDealContactRoles(deal.contactRoles), contactId);
460
+ }
461
+ /** The Primary's contact id, or `null` for a deal with none. */ export function dealPrimaryContactId(roles) {
462
+ var _ref;
463
+ var _roles_find;
464
+ return (_ref = (_roles_find = roles.find((row)=>row.primary)) == null ? void 0 : _roles_find.contactId) != null ? _ref : null;
465
+ }
466
+ /** `roles` without one contact — a delete or an erasure of that person. */ export function dealContactRolesWithout(roles, contactId) {
467
+ return roles.filter((row)=>row.contactId !== contactId);
468
+ }
469
+ /**
470
+ * `roles` with every row naming `from` moved to `to` — a contact merge.
471
+ * When both are on the deal the survivor keeps its own row, taking the
472
+ * merged row's role where it has none and its Primary where it was.
473
+ */ export function dealContactRolesRepointed(roles, from, to) {
474
+ const merged = roles.find((row)=>row.contactId === from);
475
+ if (!merged || from === to) return [
476
+ ...roles
477
+ ];
478
+ if (!roles.some((row)=>row.contactId === to)) {
479
+ return roles.map((row)=>row.contactId === from ? _extends({}, row, {
480
+ contactId: to
481
+ }) : row);
482
+ }
483
+ return roles.filter((row)=>row.contactId !== from).map((row)=>row.contactId === to ? _extends({}, row, !row.role && merged.role ? {
484
+ role: merged.role
485
+ } : {}, {
486
+ primary: row.primary || merged.primary
487
+ }) : row);
488
+ }
489
+ /**
490
+ * What a deal stores for a list of roles: the list, and the `contactId`
491
+ * its Primary names — `null` for none, which a writer turns into a delete.
492
+ */ export function dealContactRoleFields(roles) {
493
+ const contactRoles = readDealContactRoles(roles);
494
+ return {
495
+ contactRoles,
496
+ contactId: dealPrimaryContactId(contactRoles)
497
+ };
498
+ }
260
499
  export const CRM_TASK_KIND_LABELS = {
261
500
  call: 'Call',
262
501
  email: 'Email',
@@ -386,6 +625,34 @@ export function isCrmEmailDeliveryState(value) {
386
625
  ] : []
387
626
  ];
388
627
  }
628
+ /** The directions each kind takes, in the order a select lists them; none for the rest. */ export const CRM_ACTIVITY_DIRECTIONS = {
629
+ call: [
630
+ 'outbound',
631
+ 'inbound',
632
+ 'internal'
633
+ ],
634
+ email: [
635
+ 'outbound',
636
+ 'inbound'
637
+ ]
638
+ };
639
+ export const CRM_ACTIVITY_DIRECTION_LABELS = {
640
+ outbound: 'Outbound',
641
+ inbound: 'Inbound',
642
+ internal: 'Internal'
643
+ };
644
+ /** `value` when `kind` takes it as a direction, else `null`. */ export function crmActivityDirection(kind, value) {
645
+ var _CRM_ACTIVITY_DIRECTIONS_kind;
646
+ const allowed = (_CRM_ACTIVITY_DIRECTIONS_kind = CRM_ACTIVITY_DIRECTIONS[kind]) != null ? _CRM_ACTIVITY_DIRECTIONS_kind : [];
647
+ const text = String(value != null ? value : '').trim().toLowerCase();
648
+ return allowed.includes(text) ? text : null;
649
+ }
650
+ /** "Inbound call", "Outbound email", "Internal call" — or the kind alone with no direction. */ export function crmActivityKindTitle(kind, direction) {
651
+ var _CRM_ACTIVITY_KIND_LABELS_kind;
652
+ const known = crmActivityDirection(kind, direction);
653
+ const noun = (_CRM_ACTIVITY_KIND_LABELS_kind = CRM_ACTIVITY_KIND_LABELS[kind]) != null ? _CRM_ACTIVITY_KIND_LABELS_kind : String(kind);
654
+ return known ? `${CRM_ACTIVITY_DIRECTION_LABELS[known]} ${noun.toLowerCase()}` : noun;
655
+ }
389
656
  /**
390
657
  * The activity row a sent one-to-one email is logged as (AGL-2615).
391
658
  *
@@ -648,12 +915,15 @@ export function isCrmFieldObject(value) {
648
915
  * of itself. An open deal with no resolvable stage is worth nothing rather
649
916
  * than everything: the pipeline lost the stage, and a forecast that filled
650
917
  * the gap with 100% would be the most optimistic number available.
918
+ *
919
+ * An open deal's own probability override (AGL-3516) replaces its stage's
920
+ * — see {@link dealProbability}.
651
921
  */ export function weightedDealAmountCents(deal, stage) {
652
- var _deal_amountCents;
922
+ var _deal_amountCents, _dealProbability;
653
923
  const amount = Math.max(0, Math.round(Number((_deal_amountCents = deal.amountCents) != null ? _deal_amountCents : 0) || 0));
654
924
  if (deal.status === 'won') return amount;
655
925
  if (deal.status === 'lost' || !stage) return 0;
656
- const probability = Math.min(100, Math.max(0, Number(stage.probability) || 0));
926
+ const probability = (_dealProbability = dealProbability(deal, stage)) != null ? _dealProbability : 0;
657
927
  return Math.round(amount * probability / 100);
658
928
  }
659
929
  /** Whether a deal's amount is derived — it has at least one line item. */ export function dealHasLineItems(deal) {
@@ -815,6 +1085,213 @@ export function isCrmFieldObject(value) {
815
1085
  return null;
816
1086
  }
817
1087
  }
1088
+ /*==========================================
1089
+ * A COMPANY'S ACCOUNT FIELDS (AGL-3514).
1090
+ *
1091
+ * Salesforce's Account carries more than a name and a domain: a Type, an
1092
+ * Industry, a Rating, an Ownership and an Account Source, each a picklist;
1093
+ * an annual revenue and a head count; a fax, an account number, a site, a
1094
+ * ticker symbol and an SIC code; a shipping address beside the billing one;
1095
+ * and a parent account. Every door that writes a company — the drawer, the
1096
+ * REST resource, the import, a lead's conversion — reads them through this
1097
+ * block, so a value one door refuses is refused by all of them.
1098
+ *
1099
+ * ## Each field's three answers
1100
+ *
1101
+ * A door reads what a request said about a field: not named (`undefined`)
1102
+ * leaves the record alone, `null` or a blank clears it, and anything else
1103
+ * is normalized or refused by name. A picklist field stores the value's
1104
+ * LABEL, judged against the org's list with the record's current value
1105
+ * kept (see the picklist block); the Companies list filters by the key of
1106
+ * Type, Industry and Rating, which `crmCompanyListFields` writes beside
1107
+ * the label.
1108
+ *=========================================*/ /** Each company picklist field: the list it holds a value of, and the key the list filters by. */ export const CRM_COMPANY_PICKLIST_FIELDS = [
1109
+ {
1110
+ field: 'type',
1111
+ picklistId: 'accountType',
1112
+ keyField: 'typeKey'
1113
+ },
1114
+ {
1115
+ field: 'industry',
1116
+ picklistId: 'industry',
1117
+ keyField: 'industryKey'
1118
+ },
1119
+ {
1120
+ field: 'rating',
1121
+ picklistId: 'rating',
1122
+ keyField: 'ratingKey'
1123
+ },
1124
+ {
1125
+ field: 'ownership',
1126
+ picklistId: 'ownership'
1127
+ },
1128
+ {
1129
+ field: 'accountSource',
1130
+ picklistId: 'leadSource',
1131
+ keyField: 'accountSourceKey'
1132
+ }
1133
+ ];
1134
+ /** The short text fields, each capped at Salesforce's own length. */ export const CRM_COMPANY_TEXT_MAX = {
1135
+ accountNumber: 40,
1136
+ site: 80,
1137
+ tickerSymbol: 20,
1138
+ sicCode: 20
1139
+ };
1140
+ /** The most employees a company records — Salesforce's eight digits. */ export const CRM_COMPANY_EMPLOYEES_MAX = 99999999;
1141
+ /** The account fields that are not picklists, as every door names them. */ export const CRM_COMPANY_ACCOUNT_FIELDS = [
1142
+ 'annualRevenueCents',
1143
+ 'currency',
1144
+ 'numberOfEmployees',
1145
+ 'fax',
1146
+ 'accountNumber',
1147
+ 'site',
1148
+ 'tickerSymbol',
1149
+ 'sicCode',
1150
+ 'shippingAddress'
1151
+ ];
1152
+ /** A short text field as stored: trimmed, single-spaced. */ export function normalizeCompanyText(value) {
1153
+ return String(value != null ? value : '').trim().replace(/\s+/g, ' ');
1154
+ }
1155
+ const blank = (value)=>value === null || typeof value === 'string' && value.trim() === '';
1156
+ /**
1157
+ * The account fields a request names, normalized, or the refusal of each
1158
+ * one that cannot be stored — see the block above. A value the door has
1159
+ * already parsed from text (the drawer's revenue, a file's head count)
1160
+ * arrives here as the number it parsed.
1161
+ */ export function readCrmCompanyAccountFields(input) {
1162
+ const values = {};
1163
+ const errors = {};
1164
+ for (const field of CRM_COMPANY_ACCOUNT_FIELDS){
1165
+ const raw = input[field];
1166
+ if (raw === undefined) continue;
1167
+ if (blank(raw)) {
1168
+ values[field] = null;
1169
+ continue;
1170
+ }
1171
+ switch(field){
1172
+ case 'annualRevenueCents':
1173
+ if (typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0) values[field] = raw;
1174
+ else errors[field] = 'Must be a whole number of cents, 0 or more';
1175
+ break;
1176
+ case 'currency':
1177
+ {
1178
+ const code = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
1179
+ if (/^[a-z]{3}$/.test(code)) values[field] = code;
1180
+ else errors[field] = 'Must be a three-letter ISO 4217 code, like usd';
1181
+ break;
1182
+ }
1183
+ case 'numberOfEmployees':
1184
+ if (typeof raw === 'number' && Number.isInteger(raw) && raw >= 0 && raw <= CRM_COMPANY_EMPLOYEES_MAX) {
1185
+ values[field] = raw;
1186
+ } else {
1187
+ errors[field] = `Must be a whole number from 0 to ${CRM_COMPANY_EMPLOYEES_MAX}`;
1188
+ }
1189
+ break;
1190
+ case 'fax':
1191
+ {
1192
+ const fax = typeof raw === 'string' ? normalizePhone(raw) : null;
1193
+ if (fax) values[field] = fax;
1194
+ else errors[field] = 'Must be a phone number with a country code, like +15125550123';
1195
+ break;
1196
+ }
1197
+ case 'shippingAddress':
1198
+ if (typeof raw === 'object' && !Array.isArray(raw)) {
1199
+ values[field] = normalizeAddress(raw);
1200
+ } else {
1201
+ errors[field] = 'Must be an address object';
1202
+ }
1203
+ break;
1204
+ default:
1205
+ {
1206
+ const max = CRM_COMPANY_TEXT_MAX[field];
1207
+ if (typeof raw !== 'string' && typeof raw !== 'number') {
1208
+ errors[field] = 'Must be text';
1209
+ break;
1210
+ }
1211
+ const text = normalizeCompanyText(raw);
1212
+ if (text.length > max) errors[field] = `Must be at most ${max} characters`;
1213
+ else values[field] = text;
1214
+ }
1215
+ }
1216
+ }
1217
+ return {
1218
+ values: values,
1219
+ errors
1220
+ };
1221
+ }
1222
+ /**
1223
+ * The picklist fields a request names, judged against the org's lists —
1224
+ * `lists` holds each list the caller read, and a list it did not read is
1225
+ * judged as the standard values alone. `current` is the record as stored:
1226
+ * its value is kept even when the list no longer offers it, which is how
1227
+ * a company whose industry was typed before Industry became a picklist
1228
+ * keeps it. On a create (`created`), a field not named starts from its
1229
+ * list's default when the org set one.
1230
+ */ export function judgeCrmCompanyPicklists(lists, requested, options = {}) {
1231
+ const values = {};
1232
+ const errors = {};
1233
+ for (const { field, picklistId } of CRM_COMPANY_PICKLIST_FIELDS){
1234
+ var _lists_picklistId;
1235
+ var _options_current;
1236
+ const list = (_lists_picklistId = lists[picklistId]) != null ? _lists_picklistId : effectiveCrmPicklist(picklistId, undefined);
1237
+ const raw = requested[field];
1238
+ if (raw === undefined) {
1239
+ const fallback = options.created ? picklistDefaultLabel(list) : null;
1240
+ if (fallback) values[field] = fallback;
1241
+ continue;
1242
+ }
1243
+ if (blank(raw)) {
1244
+ values[field] = null;
1245
+ continue;
1246
+ }
1247
+ if (typeof raw !== 'string') {
1248
+ errors[field] = 'Must be text';
1249
+ continue;
1250
+ }
1251
+ const judged = judgeCrmPicklistValue(picklistId, list, raw, (_options_current = options.current) == null ? void 0 : _options_current[field]);
1252
+ if (judged.ok === false) errors[field] = judged.error;
1253
+ else values[field] = judged.value;
1254
+ }
1255
+ return {
1256
+ values,
1257
+ errors
1258
+ };
1259
+ }
1260
+ /** How deep a chain of parent companies may run. */ export const CRM_COMPANY_PARENT_DEPTH_MAX = 25;
1261
+ /**
1262
+ * Why `parentId` cannot be the parent of `companyId` (`null` for a company
1263
+ * not yet created), or `null` when it can: a company is never its own
1264
+ * parent, the parent must be a company the writer can see, and a company
1265
+ * already UNDER this one cannot be put above it — the cycle Salesforce
1266
+ * refuses too. Walks up from the parent through `read`, at most
1267
+ * {@link CRM_COMPANY_PARENT_DEPTH_MAX} steps.
1268
+ */ export async function crmCompanyParentRefusal(companyId, parentId, read) {
1269
+ if (companyId && parentId === companyId) return 'A company cannot be its own parent.';
1270
+ const parent = await read(parentId);
1271
+ if (!parent) return 'There is no such parent company.';
1272
+ const seen = new Set([
1273
+ parentId
1274
+ ]);
1275
+ let cursor = parent.parentCompanyId;
1276
+ for(let depth = 1; cursor; depth += 1){
1277
+ var _ref;
1278
+ var _this;
1279
+ if (companyId && cursor === companyId) {
1280
+ return 'That company sits under this one, so it cannot be its parent.';
1281
+ }
1282
+ // A loop above that does not pass through this company is not this write's.
1283
+ if (seen.has(cursor)) return null;
1284
+ if (depth >= CRM_COMPANY_PARENT_DEPTH_MAX) {
1285
+ return `A company can sit at most ${CRM_COMPANY_PARENT_DEPTH_MAX} levels under another.`;
1286
+ }
1287
+ seen.add(cursor);
1288
+ cursor = (_ref = (_this = await read(cursor)) == null ? void 0 : _this.parentCompanyId) != null ? _ref : null;
1289
+ }
1290
+ return null;
1291
+ }
1292
+ /** A stored parent id as a reader takes it, `null` for none. */ export function crmCompanyParentId(value) {
1293
+ return typeof value === 'string' && value.trim() !== '' ? value : null;
1294
+ }
818
1295
  /**
819
1296
  * Mailbox domains that belong to people rather than to a business: the
820
1297
  * providers anybody can sign up with, and the ones an internet provider
@@ -1550,21 +2027,32 @@ export function isCrmLeadStatus(value) {
1550
2027
  * `sources` is the capture door's record of which SURFACES met the person
1551
2028
  * — `signup`, `booking`, `form:{id}`, `import` — and no file may rewrite
1552
2029
  * it. `leadSource` is what Salesforce calls Lead Source: one of the org's
1553
- * own picklist values ("Trade show", "Referral" — see LEAD SOURCE IS A
1554
- * PICKLIST below), reported on and filtered by, and never derived. Two
2030
+ * own picklist values ("Trade show", "Webinar" — see THE CRM'S PICKLISTS
2031
+ * below), reported on and filtered by, and never derived. Two
1555
2032
  * fields because they answer two questions, and a file that could write
1556
2033
  * the first would be rewriting the site's own history.
1557
2034
  *=========================================*/ /** The longest text one profile field holds — the contact's own cap. */ export const CRM_LEAD_TEXT_MAX = 120;
1558
2035
  /** The most tags one lead keeps — the contact's own cap. */ export const CRM_LEAD_TAGS_MAX = 20;
1559
2036
  /** The longest note a lead keeps — the lead page's own cap. */ export const CRM_LEAD_NOTES_MAX = 4000;
1560
2037
  /** The profile's keys, in the order a card lists them. */ export const CRM_LEAD_PROFILE_KEYS = [
2038
+ 'salutation',
2039
+ 'firstName',
2040
+ 'lastName',
1561
2041
  'company',
1562
2042
  'jobTitle',
1563
2043
  'phone',
2044
+ 'mobilePhone',
2045
+ 'fax',
2046
+ 'doNotCall',
1564
2047
  'website',
1565
2048
  'address',
1566
2049
  'tags',
1567
- 'leadSource'
2050
+ 'leadSource',
2051
+ 'industry',
2052
+ 'rating',
2053
+ 'annualRevenueCents',
2054
+ 'currency',
2055
+ 'numberOfEmployees'
1568
2056
  ];
1569
2057
  /** The sentence an unreadable phone number is refused with, under the field. */ export const CRM_LEAD_PHONE_REFUSAL = 'That phone number could not be read. Enter it with its country code, like ' + '+1 512 555 0107.';
1570
2058
  /** The sentence an unreadable website is refused with, under the field. */ export const CRM_LEAD_WEBSITE_REFUSAL = 'Enter a web address, like acme.com';
@@ -1589,6 +2077,7 @@ export function isCrmLeadStatus(value) {
1589
2077
  * way a company's is; a phone the way a contact's is; both refuse rather
1590
2078
  * than store a value the record would then render as a link to nowhere.
1591
2079
  */ export function normalizeCrmLeadProfile(input) {
2080
+ var _body_annualRevenueCents, _body_currency;
1592
2081
  const body = input != null ? input : {};
1593
2082
  const patch = {};
1594
2083
  const errors = {};
@@ -1601,14 +2090,60 @@ export function isCrmLeadStatus(value) {
1601
2090
  text('company');
1602
2091
  text('jobTitle');
1603
2092
  text('leadSource');
1604
- if ('phone' in body) {
1605
- var _body_phone;
1606
- const raw = String((_body_phone = body['phone']) != null ? _body_phone : '').trim();
1607
- if (!raw) patch.phone = null;
2093
+ text('salutation');
2094
+ text('industry');
2095
+ text('rating');
2096
+ // A name part as the contact's facet keeps one — see `composeContactName`.
2097
+ for (const key of [
2098
+ 'firstName',
2099
+ 'lastName'
2100
+ ]){
2101
+ if (!(key in body)) continue;
2102
+ const value = composeContactName(body[key], '');
2103
+ patch[key] = value || null;
2104
+ }
2105
+ for (const key of [
2106
+ 'phone',
2107
+ 'mobilePhone',
2108
+ 'fax'
2109
+ ]){
2110
+ var _body_key;
2111
+ if (!(key in body)) continue;
2112
+ const raw = String((_body_key = body[key]) != null ? _body_key : '').trim();
2113
+ if (!raw) patch[key] = null;
1608
2114
  else {
1609
2115
  const phone = normalizePhone(raw);
1610
- if (phone) patch.phone = phone;
1611
- else errors.phone = CRM_LEAD_PHONE_REFUSAL;
2116
+ if (phone) patch[key] = phone;
2117
+ else errors[key] = CRM_LEAD_PHONE_REFUSAL;
2118
+ }
2119
+ }
2120
+ if ('doNotCall' in body) {
2121
+ // Stored only as `true`: anything else is "may be called", a clear.
2122
+ patch.doNotCall = body['doNotCall'] === true ? true : null;
2123
+ }
2124
+ /*
2125
+ * The revenue, its currency and the head count, read by the company's
2126
+ * own reader so a value one record refuses the other refuses in the same
2127
+ * words. A head count typed as digits is read as the number it spells.
2128
+ */ const account = {};
2129
+ if ('annualRevenueCents' in body) account['annualRevenueCents'] = (_body_annualRevenueCents = body['annualRevenueCents']) != null ? _body_annualRevenueCents : null;
2130
+ if ('currency' in body) account['currency'] = (_body_currency = body['currency']) != null ? _body_currency : null;
2131
+ if ('numberOfEmployees' in body) {
2132
+ const raw = body['numberOfEmployees'];
2133
+ const digits = typeof raw === 'string' ? raw.replace(/[\s,]/g, '') : null;
2134
+ account['numberOfEmployees'] = digits === null ? raw != null ? raw : null : digits === '' ? null : /^\d+$/.test(digits) ? Number(digits) : raw;
2135
+ }
2136
+ if (Object.keys(account).length) {
2137
+ const read = readCrmCompanyAccountFields(account);
2138
+ const into = patch;
2139
+ for (const key of [
2140
+ 'annualRevenueCents',
2141
+ 'currency',
2142
+ 'numberOfEmployees'
2143
+ ]){
2144
+ var _read_values_key;
2145
+ if (read.errors[key]) errors[key] = read.errors[key];
2146
+ else if (key in read.values) into[key] = (_read_values_key = read.values[key]) != null ? _read_values_key : null;
1612
2147
  }
1613
2148
  }
1614
2149
  if ('website' in body) {
@@ -1634,282 +2169,1330 @@ export function isCrmLeadStatus(value) {
1634
2169
  errors
1635
2170
  };
1636
2171
  }
1637
- /*==========================================
1638
- * LEAD SOURCE IS A PICKLIST (AGL-3298).
1639
- *
1640
- * Salesforce's Lead Source is a restricted picklist: an admin keeps the
1641
- * list of values, a rep picks one, and an import or an API write naming
1642
- * anything else is refused. Here the list is one org document,
1643
- * `orgs/{orgId}/crmPicklists/leadSource`, holding the values in order as
1644
- * `{ id, label, active }` and an optional default for new records.
1645
- *
1646
- * ## Records store the LABEL
1647
- *
1648
- * A lead carries `leadSource: "Trade show"`, not an id. Every reader that
1649
- * existed before the picklist — the CSV export, the REST resource, the
1650
- * record facts, a saved view's filter, a report grouped by the field —
1651
- * reads the text and keeps working, and a file exported from here
1652
- * re-imports as is. What an id would have bought is a free rename; here a
1653
- * rename (and a delete) REPLACES the old label on every record in the same
1654
- * operation, the way Salesforce's own Replace does, so a report grouped
1655
- * by the field follows the rename rather than splitting in two. The value's
1656
- * `id` is what the list itself is keyed by — the default, a reorder, the
1657
- * manage page's rows — and it never changes.
1658
- *
1659
- * ## Inactive, not gone
1660
- *
1661
- * A deactivated value leaves every picker but stays on the records that
1662
- * hold it, shown with an "(inactive)" hint, and a write that keeps a
1663
- * record's current value is never refused for it. A value is removed only
1664
- * by Delete, which names the value its records move to.
1665
- *
1666
- * ## An org that never edited the list
1667
- *
1668
- * Has no document, and reads {@link CRM_LEAD_SOURCE_STARTER_LABELS} — a
1669
- * small Salesforce-like set. The first edit on the manage page writes that
1670
- * set down as the org's own, so a new org starts with a usable list and no
1671
- * org-creation step has to seed it.
1672
- *=========================================*/ /** The picklists an org keeps, by document id — one per standard field. */ export const CRM_PICKLIST_IDS = [
1673
- 'leadSource'
1674
- ];
1675
- /** The lead source value set's document id. */ export const CRM_LEAD_SOURCE_PICKLIST = 'leadSource';
1676
- /** The most values one picklist holds — a menu, not a table. */ export const CRM_PICKLIST_VALUES_MAX = 200;
1677
- /**
1678
- * The values an org that has never edited its list reads — see the block
1679
- * header. Salesforce's own standard set, trimmed to what a small team
1680
- * recognizes.
1681
- */ export const CRM_LEAD_SOURCE_STARTER_LABELS = [
1682
- 'Web',
1683
- 'Phone inquiry',
1684
- 'Referral',
1685
- 'Partner',
1686
- 'Purchased list',
1687
- 'Trade show',
1688
- 'Other'
1689
- ];
1690
- /** A label as a picklist stores it: trimmed, single-spaced, capped. */ export function normalizeCrmPicklistLabel(value) {
1691
- return String(value != null ? value : '').trim().replace(/\s+/g, ' ').slice(0, CRM_LEAD_TEXT_MAX);
1692
- }
1693
- /** The key two labels are compared by: case and spacing do not make a new value. */ function picklistKey(label) {
1694
- return normalizeCrmPicklistLabel(label).toLowerCase();
1695
- }
1696
2172
  /**
1697
- * A new value's id: the label as a slug, suffixed until it is not one of
1698
- * `taken`. Derived rather than random so a seeded list reads the same in
1699
- * every org, and never reused, because a default names a value by it.
1700
- */ export function crmPicklistValueId(label, taken) {
1701
- const used = new Set(taken);
1702
- const base = normalizeCrmPicklistLabel(label).toLowerCase().normalize('NFKD').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'value';
1703
- let id = base;
1704
- for(let n = 2; used.has(id); n += 1)id = `${base}-${n}`;
1705
- return id;
1706
- }
1707
- /** A picklist of active values from labels, in order, with no default. */ export function crmPicklistFromLabels(labels) {
1708
- const values = [];
1709
- const seen = new Set();
1710
- for (const raw of labels){
1711
- const label = normalizeCrmPicklistLabel(raw);
1712
- if (!label || seen.has(picklistKey(label))) continue;
1713
- seen.add(picklistKey(label));
1714
- values.push({
1715
- id: crmPicklistValueId(label, values.map((value)=>value.id)),
1716
- label,
1717
- active: true
1718
- });
1719
- }
1720
- return {
1721
- values: values.slice(0, CRM_PICKLIST_VALUES_MAX),
1722
- defaultValueId: null
2173
+ * The `name` a write must store beside a lead's first and last names
2174
+ * (AGL-3513), the contact's rule: while either part is set the name is
2175
+ * their composition, written whenever either moves. `undefined` when the
2176
+ * write names neither part, or when it leaves both blank — a name-only
2177
+ * lead keeps its name, and clearing both parts leaves the name as it
2178
+ * stands.
2179
+ */ export function crmLeadComposedName(stored, patch) {
2180
+ if (patch.firstName === undefined && patch.lastName === undefined) return undefined;
2181
+ const part = (key)=>{
2182
+ var _patch_key;
2183
+ return patch[key] === undefined ? stored == null ? void 0 : stored[key] : (_patch_key = patch[key]) != null ? _patch_key : '';
1723
2184
  };
2185
+ return composeContactName(part('firstName'), part('lastName')) || undefined;
1724
2186
  }
1725
2187
  /**
1726
- * A stored document as a picklist, or `null` for one that holds none.
1727
- *
1728
- * Tolerant, because the document is client-written: a value with no label
1729
- * is dropped, a second value with the same label (in any case) or the same
1730
- * id is dropped, `active` is true unless it is `false`, and a default that
1731
- * names no value is no default.
1732
- */ export function normalizeCrmPicklist(raw) {
1733
- var _stored_defaultValueId;
1734
- if (!raw || typeof raw !== 'object') return null;
1735
- const stored = raw;
1736
- if (!Array.isArray(stored['values'])) return null;
1737
- const values = [];
1738
- const labels = new Set();
1739
- const ids = new Set();
1740
- for (const entry of stored['values']){
1741
- var _value_id;
1742
- if (!entry || typeof entry !== 'object') continue;
1743
- const value = entry;
1744
- const label = normalizeCrmPicklistLabel(value['label']);
1745
- if (!label || labels.has(picklistKey(label))) continue;
1746
- let id = String((_value_id = value['id']) != null ? _value_id : '').trim().slice(0, 64);
1747
- if (!id || id.includes('/') || ids.has(id)) id = crmPicklistValueId(label, ids);
1748
- labels.add(picklistKey(label));
1749
- ids.add(id);
1750
- values.push({
1751
- id,
1752
- label,
1753
- active: value['active'] !== false
1754
- });
1755
- if (values.length >= CRM_PICKLIST_VALUES_MAX) break;
2188
+ * Each lead picklist field beside the lead source (AGL-3513): the list it
2189
+ * holds a value of, and the key the Leads list filters by. The lists are
2190
+ * the contact's and the company's own — one Salutation, one Industry, one
2191
+ * Rating per org — so a lead converts into the same values.
2192
+ */ export const CRM_LEAD_PICKLIST_FIELDS = [
2193
+ {
2194
+ field: 'salutation',
2195
+ picklistId: 'salutation'
2196
+ },
2197
+ {
2198
+ field: 'industry',
2199
+ picklistId: 'industry',
2200
+ keyField: 'industryKey'
2201
+ },
2202
+ {
2203
+ field: 'rating',
2204
+ picklistId: 'rating',
2205
+ keyField: 'ratingKey'
1756
2206
  }
1757
- const defaultId = String((_stored_defaultValueId = stored['defaultValueId']) != null ? _stored_defaultValueId : '');
1758
- return {
1759
- values,
1760
- defaultValueId: ids.has(defaultId) ? defaultId : null
1761
- };
1762
- }
1763
- /** The org's lead source list as every reader should take it — see the block header. */ export function effectiveCrmLeadSourcePicklist(raw) {
1764
- var _normalizeCrmPicklist;
1765
- return (_normalizeCrmPicklist = normalizeCrmPicklist(raw)) != null ? _normalizeCrmPicklist : crmPicklistFromLabels(CRM_LEAD_SOURCE_STARTER_LABELS);
1766
- }
1767
- /** The value a label names, in any case or spacing — `null` for none. */ export function crmPicklistValueByLabel(picklist, label) {
1768
- var _picklist_values_find;
1769
- const key = picklistKey(String(label != null ? label : ''));
1770
- if (!key) return null;
1771
- return (_picklist_values_find = picklist.values.find((value)=>picklistKey(value.label) === key)) != null ? _picklist_values_find : null;
1772
- }
1773
- /** The values a picker offers. */ export function crmPicklistActiveValues(picklist) {
1774
- return picklist.values.filter((value)=>value.active);
1775
- }
1776
- /** The label a new record starts with — only while the default value is active. */ export function crmPicklistDefaultLabel(picklist) {
1777
- const value = picklist.values.find((entry)=>entry.id === picklist.defaultValueId);
1778
- return (value == null ? void 0 : value.active) ? value.label : null;
1779
- }
2207
+ ];
1780
2208
  /**
1781
- * The sentence a lead source outside the list is refused with, naming what
1782
- * the list allows. A long list is cut at twenty names so the sentence stays
1783
- * one a person can read.
1784
- */ export function crmLeadSourceRefusal(picklist) {
1785
- const active = crmPicklistActiveValues(picklist).map((value)=>value.label);
1786
- if (!active.length) {
1787
- return 'This organization has no active lead sources. Add one under CRM › Fields › Leads.';
2209
+ * The lead picklist fields a write names, judged against the org's lists
2210
+ * — the company's {@link judgeCrmCompanyPicklists} over the lead's fields:
2211
+ * `lists` holds each list the caller read (a list it did not read is
2212
+ * judged as the standard values alone), the lead's `current` value is kept
2213
+ * even when the list no longer offers it, and on a create a field not
2214
+ * named starts from its list's default.
2215
+ */ export function judgeCrmLeadPicklists(lists, requested, options = {}) {
2216
+ const values = {};
2217
+ const errors = {};
2218
+ for (const { field, picklistId } of CRM_LEAD_PICKLIST_FIELDS){
2219
+ var _lists_picklistId;
2220
+ var _options_current;
2221
+ const list = (_lists_picklistId = lists[picklistId]) != null ? _lists_picklistId : effectiveCrmPicklist(picklistId, undefined);
2222
+ const raw = requested[field];
2223
+ if (raw === undefined) {
2224
+ const fallback = options.created ? picklistDefaultLabel(list) : null;
2225
+ if (fallback) values[field] = fallback;
2226
+ continue;
2227
+ }
2228
+ if (raw === null || typeof raw === 'string' && !raw.trim()) {
2229
+ values[field] = null;
2230
+ continue;
2231
+ }
2232
+ if (typeof raw !== 'string') {
2233
+ errors[field] = 'Must be text';
2234
+ continue;
2235
+ }
2236
+ const judged = judgeCrmPicklistValue(picklistId, list, raw, (_options_current = options.current) == null ? void 0 : _options_current[field]);
2237
+ if (judged.ok === false) errors[field] = judged.error;
2238
+ else values[field] = judged.value;
1788
2239
  }
1789
- const shown = active.slice(0, 20).join(', ');
1790
- const more = active.length > 20 ? `, and ${active.length - 20} more` : '';
1791
- return `Lead source must be one of: ${shown}${more}.`;
1792
- }
1793
- /**
1794
- * Whether a write may store `value` as a record's lead source, and the
1795
- * label it stores.
1796
- *
1797
- * - Blank or `null` clears the field — lead source is never required.
1798
- * - An ACTIVE value, in any case or spacing, stores that value's label.
1799
- * - The record's `current` value is kept even when it is inactive or no
1800
- * longer listed: re-saving a record must never be refused for a value
1801
- * somebody else deactivated.
1802
- * - Anything else is refused with {@link crmLeadSourceRefusal}.
1803
- */ export function judgeCrmLeadSource(picklist, value, current) {
1804
- const label = normalizeCrmPicklistLabel(value);
1805
- if (!label) return {
1806
- ok: true,
1807
- value: null
1808
- };
1809
- const held = normalizeCrmPicklistLabel(current);
1810
- if (held && picklistKey(held) === picklistKey(label)) return {
1811
- ok: true,
1812
- value: held
1813
- };
1814
- const match = crmPicklistValueByLabel(picklist, label);
1815
- if (match == null ? void 0 : match.active) return {
1816
- ok: true,
1817
- value: match.label
1818
- };
1819
2240
  return {
1820
- ok: false,
1821
- error: crmLeadSourceRefusal(picklist)
2241
+ values,
2242
+ errors
1822
2243
  };
1823
2244
  }
1824
2245
  /**
1825
- * What a select offers for a record holding `current`: every active value
1826
- * in order, then the record's own value when the list would not otherwise
1827
- * show it, so the select keeps what the record holds until it is changed.
1828
- */ export function crmPicklistOptions(picklist, current) {
1829
- const options = crmPicklistActiveValues(picklist).map((value)=>({
1830
- label: value.label,
1831
- inactive: false,
1832
- unlisted: false
1833
- }));
1834
- const held = normalizeCrmPicklistLabel(current);
1835
- if (held && !options.some((option)=>picklistKey(option.label) === picklistKey(held))) {
1836
- const listed = crmPicklistValueByLabel(picklist, held);
1837
- options.push({
1838
- label: held,
1839
- inactive: Boolean(listed),
1840
- unlisted: !listed
1841
- });
1842
- }
1843
- return options;
1844
- }
1845
- /**
1846
- * Where a label sorts: its value's position in the list, unlisted values
1847
- * after every listed one, and a record with none last. The order the admin
1848
- * chose is the order a sort by the field reads in, as Salesforce's does.
1849
- */ export function crmPicklistRank(picklist, label) {
1850
- const text = normalizeCrmPicklistLabel(label);
1851
- if (!text) return Number.MAX_SAFE_INTEGER;
1852
- const at = picklist.values.findIndex((value)=>picklistKey(value.label) === picklistKey(text));
1853
- return at < 0 ? CRM_PICKLIST_VALUES_MAX : at;
1854
- }
1855
- /** The name a lead is listed under: the name it carries, else its address. */ export function crmLeadDisplayName(lead) {
1856
- var _ref, _ref1;
1857
- const name = String((_ref = lead == null ? void 0 : lead['name']) != null ? _ref : '').trim();
1858
- return name || String((_ref1 = lead == null ? void 0 : lead['email']) != null ? _ref1 : '').trim();
2246
+ * The Fields tabs, in order: every object with custom fields, then Tasks,
2247
+ * which has standard picklists and no custom fields (AGL-3517).
2248
+ */ export const CRM_PICKLIST_OBJECTS = [
2249
+ ...CRM_FIELD_OBJECTS,
2250
+ 'task'
2251
+ ];
2252
+ /** How each object reads on the Fields tabs and in a refusal. */ export const CRM_PICKLIST_OBJECT_LABELS = _extends({}, CRM_FIELD_OBJECT_LABELS, {
2253
+ task: 'Tasks'
2254
+ });
2255
+ export function isCrmPicklistObject(value) {
2256
+ return typeof value === 'string' && CRM_PICKLIST_OBJECTS.includes(value);
1859
2257
  }
1860
2258
  /**
1861
- * A lead's status as the list and the filter should read it.
1862
- *
1863
- * `new` for a lead that carries no status at all — every lead captured
1864
- * before the CRM existed, and every lead the capture door writes today,
1865
- * because `addHostLead` stamps none. Reading the absence as `new` is what
1866
- * lets the section open on the leads a site already holds rather than on an
1867
- * empty list until each one has been touched once. A stored value the union
1868
- * does not name also reads as `new`: it is a document some other writer
1869
- * produced, and refusing to list it would hide a person.
1870
- */ export function crmLeadStatus(lead) {
1871
- const status = lead == null ? void 0 : lead.status;
1872
- return isCrmLeadStatus(status) ? status : 'new';
1873
- }
1874
- /** Whether a lead still needs working — see {@link CRM_LEAD_OPEN_STATUSES}. */ export function isCrmLeadOpen(lead) {
1875
- return CRM_LEAD_OPEN_STATUSES.includes(crmLeadStatus(lead));
1876
- }
1877
- /*==========================================
1878
- * SAVED VIEWS (AGL-2617) — a list the way one person works it.
1879
- *
1880
- * "My open leads in Texas" is a list a rep opens every morning, and it is
1881
- * three things at once: the filters that pick the people, the columns they
1882
- * want to see about them, and the order they want them in. A `ContactSegment`
1883
- * kept the first of those for two dimensions — tags and capture sources —
1884
- * and nothing kept the rest, so every reload put the reader back at the
1885
- * whole list. A view keeps all three, for every list in the CRM, under a
1886
- * name, and is addressable: `?view=<id>` on the section's own URL opens it.
1887
- *
1888
- * ## The filters are the LIST'S grammar, not a second one
1889
- *
1890
- * A clause here is `{ field, op, value }` — structurally the
1891
- * `ListFilterRequest` the shared list-filter grammar already speaks, the
1892
- * request a grid's filter panel produces and the one the query translator
1893
- * and the in-memory matcher both read. A view stores a list of them and
1894
- * nothing more, so a view can only ask what the list can already answer,
1895
- * and a list that gains a filterable field gains it for its views at no
1896
- * cost. The type is restated here rather than imported because this module
1897
- * is pure data and the grammar's home is a UI library; the shape is the
1898
- * contract, and `normalizeCrmViewFilters` is what holds a stored document
1899
- * to it.
1900
- *
1901
- * ## Mine, and shared
1902
- *
1903
- * A view is one person's working arrangement until they say otherwise, so
1904
- * it lists only for its owner; `shared` puts it in front of everyone who
1905
- * can read the section. Both are stamped `visibleTo` like every CRM row,
1906
- * because a view is a description of the people it selects and answers to
1907
- * the same authority they do — a private view is hidden by the LISTING, not
1908
- * by the rules, and the rules let the creator or an org-wide member change
1909
- * or remove it.
1910
- *
1911
- * ## The default is the reader's, not the view's
1912
- *
2259
+ * Lead Source, with Salesforce's standard values and Aglyn's own (AGL-3519).
2260
+ * Each carries the group a report splits pipeline by: Inbound for a lead
2261
+ * who came to the org, Outbound for one the org went to, and Other in
2262
+ * neither. Aglyn's are the doors and the outreach the platform runs itself
2263
+ * — a website form, a booking, a sequence — stamped on a record the door
2264
+ * met first ({@link CRM_LEAD_SOURCE_ORIGINS}); a vendor the platform does
2265
+ * not run (a data provider, a sending tool) is an org's own value.
2266
+ */ const LEAD_SOURCE_DEFINITION = {
2267
+ id: 'leadSource',
2268
+ label: 'Lead source',
2269
+ plural: 'lead sources',
2270
+ object: 'lead',
2271
+ restricted: true,
2272
+ groups: [
2273
+ {
2274
+ id: 'inbound',
2275
+ label: 'Inbound'
2276
+ },
2277
+ {
2278
+ id: 'outbound',
2279
+ label: 'Outbound'
2280
+ }
2281
+ ],
2282
+ standardValues: [
2283
+ {
2284
+ id: 'web',
2285
+ label: 'Web',
2286
+ group: 'inbound'
2287
+ },
2288
+ {
2289
+ id: 'phone-inquiry',
2290
+ label: 'Phone inquiry',
2291
+ group: 'inbound'
2292
+ },
2293
+ {
2294
+ id: 'email-inquiry',
2295
+ label: 'Email inquiry',
2296
+ group: 'inbound'
2297
+ },
2298
+ {
2299
+ id: 'partner-referral',
2300
+ label: 'Partner referral',
2301
+ group: 'inbound'
2302
+ },
2303
+ {
2304
+ id: 'employee-referral',
2305
+ label: 'Employee referral',
2306
+ group: 'inbound'
2307
+ },
2308
+ {
2309
+ id: 'external-referral',
2310
+ label: 'External referral',
2311
+ group: 'inbound'
2312
+ },
2313
+ {
2314
+ id: 'advertisement',
2315
+ label: 'Advertisement',
2316
+ group: 'inbound'
2317
+ },
2318
+ {
2319
+ id: 'trade-show',
2320
+ label: 'Trade show',
2321
+ group: 'inbound'
2322
+ },
2323
+ {
2324
+ id: 'webinar',
2325
+ label: 'Webinar',
2326
+ group: 'inbound'
2327
+ },
2328
+ {
2329
+ id: 'word-of-mouth',
2330
+ label: 'Word of mouth',
2331
+ group: 'inbound'
2332
+ },
2333
+ // Aglyn's own doors (AGL-3519), each stamped by the door that met the person.
2334
+ {
2335
+ id: 'website-form',
2336
+ label: 'Website form',
2337
+ group: 'inbound'
2338
+ },
2339
+ {
2340
+ id: 'booking',
2341
+ label: 'Booking',
2342
+ group: 'inbound'
2343
+ },
2344
+ {
2345
+ id: 'newsletter-sign-up',
2346
+ label: 'Newsletter sign-up',
2347
+ group: 'inbound'
2348
+ },
2349
+ {
2350
+ id: 'site-member-sign-up',
2351
+ label: 'Site member sign-up',
2352
+ group: 'inbound'
2353
+ },
2354
+ {
2355
+ id: 'online-purchase',
2356
+ label: 'Online purchase',
2357
+ group: 'inbound'
2358
+ },
2359
+ {
2360
+ id: 'account-sign-up',
2361
+ label: 'Account sign-up',
2362
+ group: 'inbound'
2363
+ },
2364
+ {
2365
+ id: 'purchased-list',
2366
+ label: 'Purchased list',
2367
+ group: 'outbound'
2368
+ },
2369
+ // Aglyn's own outreach (AGL-3519).
2370
+ {
2371
+ id: 'sequence',
2372
+ label: 'Sequence',
2373
+ group: 'outbound'
2374
+ },
2375
+ {
2376
+ id: 'email-campaign',
2377
+ label: 'Email campaign',
2378
+ group: 'outbound'
2379
+ },
2380
+ {
2381
+ id: 'other',
2382
+ label: 'Other'
2383
+ }
2384
+ ],
2385
+ targets: [
2386
+ // The key the Leads list filters by moves with the label (AGL-3321).
2387
+ {
2388
+ object: 'lead',
2389
+ field: 'leadSource',
2390
+ keyField: 'leadSourceKey'
2391
+ },
2392
+ // A contact's lead source is each holder's own, like the rest of its profile.
2393
+ {
2394
+ object: 'contact',
2395
+ field: 'leadSource',
2396
+ facet: true
2397
+ },
2398
+ // A company's Account Source is a lead source value (AGL-3514).
2399
+ {
2400
+ object: 'company',
2401
+ field: 'accountSource',
2402
+ keyField: 'accountSourceKey'
2403
+ },
2404
+ // A deal's, stamped by the conversion that opened it (AGL-3516).
2405
+ {
2406
+ object: 'deal',
2407
+ field: 'leadSource',
2408
+ keyField: 'leadSourceKey'
2409
+ }
2410
+ ]
2411
+ };
2412
+ /*------------------------------------------
2413
+ * LEAD STATUS (AGL-3512) — a SEMANTIC picklist.
2414
+ *
2415
+ * Every value MEANS one of {@link CRM_LEAD_STATUSES}, and the meaning stays
2416
+ * on the lead as `status`, which every query, count, index, automation and
2417
+ * rule reads exactly as before. The value's label sits beside it as
2418
+ * `statusLabel` — see {@link crmLeadStatusLabel}. Salesforce ships one
2419
+ * standard value per meaning; an org adds its own ("Contacted", "Meeting
2420
+ * set") under a meaning, never under Qualified, which only a conversion
2421
+ * sets.
2422
+ *-----------------------------------------*/ const LEAD_STATUS_DEFINITION = {
2423
+ id: 'leadStatus',
2424
+ label: 'Lead status',
2425
+ plural: 'lead statuses',
2426
+ object: 'lead',
2427
+ restricted: true,
2428
+ meanings: CRM_LEAD_STATUSES,
2429
+ meaningLabels: CRM_LEAD_STATUS_LABELS,
2430
+ reservedMeanings: [
2431
+ 'qualified'
2432
+ ],
2433
+ standardValues: [
2434
+ {
2435
+ id: 'new',
2436
+ label: CRM_LEAD_STATUS_LABELS.new,
2437
+ meaning: 'new'
2438
+ },
2439
+ {
2440
+ id: 'nurturing',
2441
+ label: CRM_LEAD_STATUS_LABELS.nurturing,
2442
+ meaning: 'nurturing'
2443
+ },
2444
+ {
2445
+ id: 'working',
2446
+ label: CRM_LEAD_STATUS_LABELS.working,
2447
+ meaning: 'working'
2448
+ },
2449
+ {
2450
+ id: 'qualified',
2451
+ label: CRM_LEAD_STATUS_LABELS.qualified,
2452
+ meaning: 'qualified'
2453
+ },
2454
+ {
2455
+ id: 'unqualified',
2456
+ label: CRM_LEAD_STATUS_LABELS.unqualified,
2457
+ meaning: 'unqualified'
2458
+ }
2459
+ ],
2460
+ targets: [
2461
+ {
2462
+ object: 'lead',
2463
+ field: 'statusLabel'
2464
+ }
2465
+ ]
2466
+ };
2467
+ /*------------------------------------------
2468
+ * THE COMPANY PICKLISTS (AGL-3514).
2469
+ *
2470
+ * Salesforce's Account Type, Industry, Rating and Ownership, each kept on
2471
+ * the Companies tab. Industry and Rating are shared with leads, whose
2472
+ * targets join these definitions rather than repeating them. Account
2473
+ * Source is not a list of its own: it holds a lead source value, so it is
2474
+ * a target of the lead source definition above.
2475
+ *-----------------------------------------*/ /** Salesforce's Account Type. */ const ACCOUNT_TYPE_DEFINITION = {
2476
+ id: 'accountType',
2477
+ label: 'Type',
2478
+ plural: 'account types',
2479
+ object: 'company',
2480
+ restricted: true,
2481
+ standardValues: [
2482
+ {
2483
+ id: 'analyst',
2484
+ label: 'Analyst'
2485
+ },
2486
+ {
2487
+ id: 'press',
2488
+ label: 'Press'
2489
+ },
2490
+ {
2491
+ id: 'competitor',
2492
+ label: 'Competitor'
2493
+ },
2494
+ {
2495
+ id: 'prospect',
2496
+ label: 'Prospect'
2497
+ },
2498
+ {
2499
+ id: 'customer',
2500
+ label: 'Customer'
2501
+ },
2502
+ {
2503
+ id: 'reseller',
2504
+ label: 'Reseller'
2505
+ },
2506
+ {
2507
+ id: 'integrator',
2508
+ label: 'Integrator'
2509
+ },
2510
+ {
2511
+ id: 'investor',
2512
+ label: 'Investor'
2513
+ },
2514
+ {
2515
+ id: 'partner',
2516
+ label: 'Partner'
2517
+ },
2518
+ {
2519
+ id: 'consulting',
2520
+ label: 'Consulting'
2521
+ },
2522
+ {
2523
+ id: 'other',
2524
+ label: 'Other'
2525
+ }
2526
+ ],
2527
+ targets: [
2528
+ {
2529
+ object: 'company',
2530
+ field: 'type',
2531
+ keyField: 'typeKey'
2532
+ }
2533
+ ]
2534
+ };
2535
+ /**
2536
+ * Salesforce's Industry. A company written while the field was free text
2537
+ * keeps its text; a write that keeps it is never refused.
2538
+ */ const INDUSTRY_DEFINITION = {
2539
+ id: 'industry',
2540
+ label: 'Industry',
2541
+ plural: 'industries',
2542
+ object: 'company',
2543
+ restricted: true,
2544
+ standardValues: [
2545
+ {
2546
+ id: 'agriculture',
2547
+ label: 'Agriculture'
2548
+ },
2549
+ {
2550
+ id: 'apparel',
2551
+ label: 'Apparel'
2552
+ },
2553
+ {
2554
+ id: 'banking',
2555
+ label: 'Banking'
2556
+ },
2557
+ {
2558
+ id: 'biotechnology',
2559
+ label: 'Biotechnology'
2560
+ },
2561
+ {
2562
+ id: 'chemicals',
2563
+ label: 'Chemicals'
2564
+ },
2565
+ {
2566
+ id: 'communications',
2567
+ label: 'Communications'
2568
+ },
2569
+ {
2570
+ id: 'construction',
2571
+ label: 'Construction'
2572
+ },
2573
+ {
2574
+ id: 'consulting',
2575
+ label: 'Consulting'
2576
+ },
2577
+ {
2578
+ id: 'education',
2579
+ label: 'Education'
2580
+ },
2581
+ {
2582
+ id: 'electronics',
2583
+ label: 'Electronics'
2584
+ },
2585
+ {
2586
+ id: 'energy',
2587
+ label: 'Energy'
2588
+ },
2589
+ {
2590
+ id: 'engineering',
2591
+ label: 'Engineering'
2592
+ },
2593
+ {
2594
+ id: 'entertainment',
2595
+ label: 'Entertainment'
2596
+ },
2597
+ {
2598
+ id: 'environmental',
2599
+ label: 'Environmental'
2600
+ },
2601
+ {
2602
+ id: 'finance',
2603
+ label: 'Finance'
2604
+ },
2605
+ {
2606
+ id: 'food-and-beverage',
2607
+ label: 'Food & Beverage'
2608
+ },
2609
+ {
2610
+ id: 'government',
2611
+ label: 'Government'
2612
+ },
2613
+ {
2614
+ id: 'healthcare',
2615
+ label: 'Healthcare'
2616
+ },
2617
+ {
2618
+ id: 'hospitality',
2619
+ label: 'Hospitality'
2620
+ },
2621
+ {
2622
+ id: 'insurance',
2623
+ label: 'Insurance'
2624
+ },
2625
+ {
2626
+ id: 'machinery',
2627
+ label: 'Machinery'
2628
+ },
2629
+ {
2630
+ id: 'manufacturing',
2631
+ label: 'Manufacturing'
2632
+ },
2633
+ {
2634
+ id: 'media',
2635
+ label: 'Media'
2636
+ },
2637
+ {
2638
+ id: 'not-for-profit',
2639
+ label: 'Not For Profit'
2640
+ },
2641
+ {
2642
+ id: 'recreation',
2643
+ label: 'Recreation'
2644
+ },
2645
+ {
2646
+ id: 'retail',
2647
+ label: 'Retail'
2648
+ },
2649
+ {
2650
+ id: 'shipping',
2651
+ label: 'Shipping'
2652
+ },
2653
+ {
2654
+ id: 'technology',
2655
+ label: 'Technology'
2656
+ },
2657
+ {
2658
+ id: 'telecommunications',
2659
+ label: 'Telecommunications'
2660
+ },
2661
+ {
2662
+ id: 'transportation',
2663
+ label: 'Transportation'
2664
+ },
2665
+ {
2666
+ id: 'utilities',
2667
+ label: 'Utilities'
2668
+ },
2669
+ {
2670
+ id: 'other',
2671
+ label: 'Other'
2672
+ }
2673
+ ],
2674
+ targets: [
2675
+ {
2676
+ object: 'company',
2677
+ field: 'industry',
2678
+ keyField: 'industryKey'
2679
+ },
2680
+ // Leads (AGL-3513): the same list, so a lead converts into its value.
2681
+ {
2682
+ object: 'lead',
2683
+ field: 'industry',
2684
+ keyField: 'industryKey'
2685
+ }
2686
+ ]
2687
+ };
2688
+ /** Salesforce's Rating. */ const RATING_DEFINITION = {
2689
+ id: 'rating',
2690
+ label: 'Rating',
2691
+ plural: 'ratings',
2692
+ object: 'company',
2693
+ restricted: true,
2694
+ standardValues: [
2695
+ {
2696
+ id: 'hot',
2697
+ label: 'Hot'
2698
+ },
2699
+ {
2700
+ id: 'warm',
2701
+ label: 'Warm'
2702
+ },
2703
+ {
2704
+ id: 'cold',
2705
+ label: 'Cold'
2706
+ }
2707
+ ],
2708
+ targets: [
2709
+ {
2710
+ object: 'company',
2711
+ field: 'rating',
2712
+ keyField: 'ratingKey'
2713
+ },
2714
+ // Leads (AGL-3513).
2715
+ {
2716
+ object: 'lead',
2717
+ field: 'rating',
2718
+ keyField: 'ratingKey'
2719
+ }
2720
+ ]
2721
+ };
2722
+ /** Salesforce's Ownership. */ const OWNERSHIP_DEFINITION = {
2723
+ id: 'ownership',
2724
+ label: 'Ownership',
2725
+ plural: 'ownership values',
2726
+ object: 'company',
2727
+ restricted: true,
2728
+ standardValues: [
2729
+ {
2730
+ id: 'public',
2731
+ label: 'Public'
2732
+ },
2733
+ {
2734
+ id: 'private',
2735
+ label: 'Private'
2736
+ },
2737
+ {
2738
+ id: 'subsidiary',
2739
+ label: 'Subsidiary'
2740
+ },
2741
+ {
2742
+ id: 'other',
2743
+ label: 'Other'
2744
+ }
2745
+ ],
2746
+ targets: [
2747
+ {
2748
+ object: 'company',
2749
+ field: 'ownership'
2750
+ }
2751
+ ]
2752
+ };
2753
+ /*------------------------------------------
2754
+ * CONTACT picklists (AGL-3515).
2755
+ *-----------------------------------------*/ /**
2756
+ * Salutation, with Salesforce's standard values. Shared by every object
2757
+ * that addresses a person: defined here with the contact target, where the
2758
+ * label lives in each holder's facet like the rest of the profile.
2759
+ */ const SALUTATION_DEFINITION = {
2760
+ id: 'salutation',
2761
+ label: 'Salutation',
2762
+ plural: 'salutations',
2763
+ object: 'contact',
2764
+ restricted: true,
2765
+ standardValues: [
2766
+ {
2767
+ id: 'mr',
2768
+ label: 'Mr.'
2769
+ },
2770
+ {
2771
+ id: 'ms',
2772
+ label: 'Ms.'
2773
+ },
2774
+ {
2775
+ id: 'mrs',
2776
+ label: 'Mrs.'
2777
+ },
2778
+ {
2779
+ id: 'dr',
2780
+ label: 'Dr.'
2781
+ },
2782
+ {
2783
+ id: 'prof',
2784
+ label: 'Prof.'
2785
+ }
2786
+ ],
2787
+ targets: [
2788
+ {
2789
+ object: 'contact',
2790
+ field: 'salutation',
2791
+ facet: true
2792
+ },
2793
+ // Leads (AGL-3513): on the lead itself, carried to the facet on convert.
2794
+ {
2795
+ object: 'lead',
2796
+ field: 'salutation'
2797
+ }
2798
+ ]
2799
+ };
2800
+ /*------------------------------------------
2801
+ * DEALS (AGL-3516).
2802
+ *-----------------------------------------*/ /**
2803
+ * Salesforce's Opportunity Type. Plain: the deal stores the label, and
2804
+ * `typeKey` beside it for the Deals list's filter.
2805
+ */ const OPPORTUNITY_TYPE_DEFINITION = {
2806
+ id: 'opportunityType',
2807
+ label: 'Type',
2808
+ plural: 'deal types',
2809
+ object: 'deal',
2810
+ restricted: true,
2811
+ standardValues: [
2812
+ {
2813
+ id: 'existing-business',
2814
+ label: 'Existing Business'
2815
+ },
2816
+ {
2817
+ id: 'new-business',
2818
+ label: 'New Business'
2819
+ }
2820
+ ],
2821
+ targets: [
2822
+ {
2823
+ object: 'deal',
2824
+ field: 'type',
2825
+ keyField: 'typeKey'
2826
+ }
2827
+ ]
2828
+ };
2829
+ /**
2830
+ * Salesforce's Opportunity Contact Role (AGL-3521): the part a contact
2831
+ * plays on a deal. Plain; the label sits in each entry of the deal's
2832
+ * `contactRoles`, and `contactRoleKeys` lists their keys, which is how a
2833
+ * rename or a delete finds the deals to rewrite.
2834
+ */ const OPPORTUNITY_CONTACT_ROLE_DEFINITION = {
2835
+ id: 'opportunityContactRole',
2836
+ label: 'Contact role',
2837
+ plural: 'contact roles',
2838
+ object: 'deal',
2839
+ restricted: true,
2840
+ standardValues: [
2841
+ {
2842
+ id: 'business-user',
2843
+ label: 'Business User'
2844
+ },
2845
+ {
2846
+ id: 'decision-maker',
2847
+ label: 'Decision Maker'
2848
+ },
2849
+ {
2850
+ id: 'economic-buyer',
2851
+ label: 'Economic Buyer'
2852
+ },
2853
+ {
2854
+ id: 'economic-decision-maker',
2855
+ label: 'Economic Decision Maker'
2856
+ },
2857
+ {
2858
+ id: 'evaluator',
2859
+ label: 'Evaluator'
2860
+ },
2861
+ {
2862
+ id: 'executive-sponsor',
2863
+ label: 'Executive Sponsor'
2864
+ },
2865
+ {
2866
+ id: 'influencer',
2867
+ label: 'Influencer'
2868
+ },
2869
+ {
2870
+ id: 'technical-buyer',
2871
+ label: 'Technical Buyer'
2872
+ },
2873
+ {
2874
+ id: 'other',
2875
+ label: 'Other'
2876
+ }
2877
+ ],
2878
+ targets: [
2879
+ {
2880
+ object: 'deal',
2881
+ field: 'contactRoles',
2882
+ arrayKey: 'role',
2883
+ keyField: 'contactRoleKeys'
2884
+ }
2885
+ ]
2886
+ };
2887
+ /*
2888
+ * TASKS (AGL-3517) — Salesforce's Task Status, Priority, Type and Subject.
2889
+ *
2890
+ * Status, Priority and Type are SEMANTIC: each value means one of the
2891
+ * task's existing `status`, `priority` or `kind` values, which stay on the
2892
+ * document untouched for every query, reminder, due state, digest and
2893
+ * automation. The value's LABEL sits beside it in `statusLabel`,
2894
+ * `priorityLabel` or `typeLabel`; a task with no label shows the first
2895
+ * active value of its meaning (see `crmTaskPicklistLabel`). Subject is an
2896
+ * unrestricted combobox over the title: its values are suggestions, the
2897
+ * title stays free text, and no record holds a subject "value", so a
2898
+ * rename or a delete rewrites nothing.
2899
+ */ const TASK_STATUS_DEFINITION = {
2900
+ id: 'taskStatus',
2901
+ label: 'Status',
2902
+ plural: 'task statuses',
2903
+ object: 'task',
2904
+ restricted: true,
2905
+ meanings: [
2906
+ 'open',
2907
+ 'done'
2908
+ ],
2909
+ standardValues: [
2910
+ {
2911
+ id: 'not-started',
2912
+ label: 'Not Started',
2913
+ meaning: 'open'
2914
+ },
2915
+ {
2916
+ id: 'in-progress',
2917
+ label: 'In Progress',
2918
+ meaning: 'open'
2919
+ },
2920
+ {
2921
+ id: 'waiting',
2922
+ label: 'Waiting on someone else',
2923
+ meaning: 'open'
2924
+ },
2925
+ {
2926
+ id: 'deferred',
2927
+ label: 'Deferred',
2928
+ meaning: 'open'
2929
+ },
2930
+ {
2931
+ id: 'completed',
2932
+ label: 'Completed',
2933
+ meaning: 'done'
2934
+ }
2935
+ ],
2936
+ defaultValueId: 'not-started',
2937
+ targets: [
2938
+ {
2939
+ object: 'task',
2940
+ field: 'statusLabel'
2941
+ }
2942
+ ]
2943
+ };
2944
+ const TASK_PRIORITY_DEFINITION = {
2945
+ id: 'taskPriority',
2946
+ label: 'Priority',
2947
+ plural: 'task priorities',
2948
+ object: 'task',
2949
+ restricted: true,
2950
+ meanings: [
2951
+ 'low',
2952
+ 'normal',
2953
+ 'high'
2954
+ ],
2955
+ standardValues: [
2956
+ {
2957
+ id: 'high',
2958
+ label: 'High',
2959
+ meaning: 'high'
2960
+ },
2961
+ {
2962
+ id: 'normal',
2963
+ label: 'Normal',
2964
+ meaning: 'normal'
2965
+ },
2966
+ {
2967
+ id: 'low',
2968
+ label: 'Low',
2969
+ meaning: 'low'
2970
+ }
2971
+ ],
2972
+ defaultValueId: 'normal',
2973
+ targets: [
2974
+ {
2975
+ object: 'task',
2976
+ field: 'priorityLabel'
2977
+ }
2978
+ ]
2979
+ };
2980
+ const TASK_TYPE_DEFINITION = {
2981
+ id: 'taskType',
2982
+ label: 'Type',
2983
+ plural: 'task types',
2984
+ object: 'task',
2985
+ restricted: true,
2986
+ meanings: CRM_TASK_KINDS,
2987
+ standardValues: [
2988
+ {
2989
+ id: 'call',
2990
+ label: 'Call',
2991
+ meaning: 'call'
2992
+ },
2993
+ {
2994
+ id: 'email',
2995
+ label: 'Email',
2996
+ meaning: 'email'
2997
+ },
2998
+ {
2999
+ id: 'meeting',
3000
+ label: 'Meeting',
3001
+ meaning: 'meeting'
3002
+ },
3003
+ // Salesforce's "Other": a task that is none of the three conversations.
3004
+ {
3005
+ id: 'todo',
3006
+ label: 'To-do',
3007
+ meaning: 'todo'
3008
+ }
3009
+ ],
3010
+ defaultValueId: 'todo',
3011
+ targets: [
3012
+ {
3013
+ object: 'task',
3014
+ field: 'typeLabel'
3015
+ }
3016
+ ]
3017
+ };
3018
+ const TASK_SUBJECT_DEFINITION = {
3019
+ id: 'taskSubject',
3020
+ label: 'Subject',
3021
+ plural: 'task subjects',
3022
+ object: 'task',
3023
+ restricted: false,
3024
+ standardValues: [
3025
+ {
3026
+ id: 'call',
3027
+ label: 'Call'
3028
+ },
3029
+ {
3030
+ id: 'send-letter',
3031
+ label: 'Send Letter'
3032
+ },
3033
+ {
3034
+ id: 'send-quote',
3035
+ label: 'Send Quote'
3036
+ },
3037
+ {
3038
+ id: 'other',
3039
+ label: 'Other'
3040
+ }
3041
+ ],
3042
+ // Suggestions for a free-text title: nothing holds them, nothing moves.
3043
+ targets: []
3044
+ };
3045
+ /** Every standard picklist field the CRM keeps, one document each. */ export const CRM_PICKLIST_DEFINITIONS = [
3046
+ LEAD_SOURCE_DEFINITION,
3047
+ LEAD_STATUS_DEFINITION,
3048
+ // Companies (AGL-3514).
3049
+ ACCOUNT_TYPE_DEFINITION,
3050
+ INDUSTRY_DEFINITION,
3051
+ RATING_DEFINITION,
3052
+ OWNERSHIP_DEFINITION,
3053
+ // Contacts (AGL-3515).
3054
+ SALUTATION_DEFINITION,
3055
+ // Deals (AGL-3516).
3056
+ OPPORTUNITY_TYPE_DEFINITION,
3057
+ // Deal contact roles (AGL-3521).
3058
+ OPPORTUNITY_CONTACT_ROLE_DEFINITION,
3059
+ // Tasks (AGL-3517).
3060
+ TASK_STATUS_DEFINITION,
3061
+ TASK_PRIORITY_DEFINITION,
3062
+ TASK_TYPE_DEFINITION,
3063
+ TASK_SUBJECT_DEFINITION
3064
+ ];
3065
+ /** The picklists an org keeps, by document id. */ export const CRM_PICKLIST_IDS = CRM_PICKLIST_DEFINITIONS.map((definition)=>definition.id);
3066
+ /** The lead source value set's document id. */ export const CRM_LEAD_SOURCE_PICKLIST = 'leadSource';
3067
+ /** The salutation value set's document id (AGL-3515). */ export const CRM_SALUTATION_PICKLIST = 'salutation';
3068
+ /** A deal's Type value set's document id (AGL-3516). */ export const CRM_OPPORTUNITY_TYPE_PICKLIST = 'opportunityType';
3069
+ /** A deal contact's role value set's document id (AGL-3521). */ export const CRM_OPPORTUNITY_CONTACT_ROLE_PICKLIST = 'opportunityContactRole';
3070
+ /**
3071
+ * The built-in Lead source value each first-party door stamps (AGL-3519), by
3072
+ * the door's word for how it met a person — a capture's source, or the
3073
+ * outreach that reached them. `api`, `import` and `manual` are ways a record
3074
+ * was ADDED, not where a person came from, and stamp nothing.
3075
+ */ export const CRM_LEAD_SOURCE_ORIGINS = {
3076
+ form: 'website-form',
3077
+ booking: 'booking',
3078
+ newsletter: 'newsletter-sign-up',
3079
+ member: 'site-member-sign-up',
3080
+ order: 'online-purchase',
3081
+ account: 'account-sign-up',
3082
+ sequence: 'sequence',
3083
+ emailCampaign: 'email-campaign'
3084
+ };
3085
+ export function isCrmPicklistId(value) {
3086
+ return typeof value === 'string' && CRM_PICKLIST_IDS.includes(value);
3087
+ }
3088
+ /** The definition an id names, or `null` for one the registry does not hold. */ export function crmPicklistDefinition(id) {
3089
+ var _CRM_PICKLIST_DEFINITIONS_find;
3090
+ return (_CRM_PICKLIST_DEFINITIONS_find = CRM_PICKLIST_DEFINITIONS.find((definition)=>definition.id === id)) != null ? _CRM_PICKLIST_DEFINITIONS_find : null;
3091
+ }
3092
+ /** The definitions whose values are managed on one Fields tab, in registry order. */ export function crmPicklistDefinitionsFor(object) {
3093
+ return CRM_PICKLIST_DEFINITIONS.filter((definition)=>definition.object === object);
3094
+ }
3095
+ /** The definition behind a registered id — total, because the id is typed. */ function definitionOf(id) {
3096
+ return crmPicklistDefinition(id);
3097
+ }
3098
+ /** The most values one picklist holds — a menu, not a table. */ export const CRM_PICKLIST_VALUES_MAX = PICKLIST_VALUES_MAX;
3099
+ /**
3100
+ * The values an org without a lead source document offered before the
3101
+ * standard set was built in. "Referral" and "Partner" are not standard: a
3102
+ * record holding one, in an org that never stored its list, reads as not in
3103
+ * the list until the value is written down as the org's own.
3104
+ */ export const CRM_LEAD_SOURCE_STARTER_LABELS = [
3105
+ 'Web',
3106
+ 'Phone inquiry',
3107
+ 'Referral',
3108
+ 'Partner',
3109
+ 'Purchased list',
3110
+ 'Trade show',
3111
+ 'Other'
3112
+ ];
3113
+ /** A label as a picklist stores it: trimmed, single-spaced, capped. */ export const normalizeCrmPicklistLabel = normalizePicklistLabel;
3114
+ /** A new value's id — see `mintPicklistValueId`. */ export const crmPicklistValueId = mintPicklistValueId;
3115
+ /** A picklist of active values from labels, in order, with no default. */ export const crmPicklistFromLabels = picklistFromLabels;
3116
+ /**
3117
+ * A stored document as a picklist, or `null` for one that holds none —
3118
+ * tolerant, as `normalizePicklistValueSet` reads it, and without the
3119
+ * standard values merged in. {@link effectiveCrmPicklist} is what a reader
3120
+ * judges against.
3121
+ */ export function normalizeCrmPicklist(raw) {
3122
+ return normalizePicklistValueSet(raw);
3123
+ }
3124
+ /** An org's list for one picklist as every reader should take it — see the block header. */ export function effectiveCrmPicklist(id, raw) {
3125
+ return effectivePicklistValueSet(definitionOf(id), raw);
3126
+ }
3127
+ /** The org's lead source list as every reader should take it. */ export function effectiveCrmLeadSourcePicklist(raw) {
3128
+ return effectiveCrmPicklist(CRM_LEAD_SOURCE_PICKLIST, raw);
3129
+ }
3130
+ /**
3131
+ * The label a door stamps for `origin` (AGL-3519): its built-in value as the
3132
+ * org spells it, while that value is ACTIVE — an org that deactivated it has
3133
+ * said not to file people under it — else `null`, as for a word with none.
3134
+ */ export function crmLeadSourceForOrigin(picklist, origin) {
3135
+ const id = typeof origin === 'string' ? CRM_LEAD_SOURCE_ORIGINS[origin] : undefined;
3136
+ if (!id || !Object.hasOwn(CRM_LEAD_SOURCE_ORIGINS, origin)) return null;
3137
+ const value = picklist.values.find((entry)=>entry.id === id);
3138
+ return (value == null ? void 0 : value.active) ? value.label : null;
3139
+ }
3140
+ /** Whether a value is one of a picklist's standard values — computed from its id, never stored. */ export function isStandardCrmPicklistValue(id, valueId) {
3141
+ return isStandardPicklistValueId(definitionOf(id), valueId);
3142
+ }
3143
+ /** The value a label names, in any case or spacing — `null` for none. */ export const crmPicklistValueByLabel = picklistValueByLabel;
3144
+ /** The values a picker offers. */ export const crmPicklistActiveValues = picklistActiveValues;
3145
+ /** The label a new record starts with — only while the default value is active. */ export const crmPicklistDefaultLabel = picklistDefaultLabel;
3146
+ /**
3147
+ * The sentence a value outside a restricted picklist is refused with,
3148
+ * naming what the list allows, and where to add one when it allows none.
3149
+ */ export function crmPicklistRefusal(id, picklist) {
3150
+ const definition = definitionOf(id);
3151
+ return picklistRefusalSentence(picklist, {
3152
+ field: definition.label,
3153
+ empty: `This organization has no active ${definition.plural}. Add one under ` + `CRM › Fields › ${CRM_PICKLIST_OBJECT_LABELS[definition.object]}.`
3154
+ });
3155
+ }
3156
+ /** The sentence a lead source outside the list is refused with. */ export function crmLeadSourceRefusal(picklist) {
3157
+ return crmPicklistRefusal(CRM_LEAD_SOURCE_PICKLIST, picklist);
3158
+ }
3159
+ /**
3160
+ * Whether a write may store `value` as a record's value of picklist `id`,
3161
+ * and the label it stores — `judgePicklistValue` under the definition's
3162
+ * restriction: blank clears, an active value stores the list's spelling,
3163
+ * the record's `current` value is always kept, and on a restricted
3164
+ * picklist anything else is refused with {@link crmPicklistRefusal}.
3165
+ */ export function judgeCrmPicklistValue(id, picklist, value, current) {
3166
+ return judgePicklistValue(picklist, value, {
3167
+ restricted: definitionOf(id).restricted,
3168
+ current,
3169
+ refusal: ()=>crmPicklistRefusal(id, picklist)
3170
+ });
3171
+ }
3172
+ /**
3173
+ * A deal's contact roles with each role judged against the org's
3174
+ * `opportunityContactRole` list (AGL-3521): an active value stored as the
3175
+ * list spells it, a blank cleared, and the role a contact already holds on
3176
+ * the deal (`current`) kept — or the first refusal, naming what the list
3177
+ * allows.
3178
+ */ export function judgeDealContactRoles(picklist, roles, current = []) {
3179
+ const judged = [];
3180
+ for (const row of roles){
3181
+ var _row_role;
3182
+ var _current_find;
3183
+ const held = (_current_find = current.find((entry)=>entry.contactId === row.contactId)) == null ? void 0 : _current_find.role;
3184
+ const verdict = judgeCrmPicklistValue('opportunityContactRole', picklist, (_row_role = row.role) != null ? _row_role : '', held);
3185
+ if (verdict.ok === false) return verdict;
3186
+ judged.push(_extends({
3187
+ contactId: row.contactId
3188
+ }, verdict.value ? {
3189
+ role: verdict.value
3190
+ } : {}, {
3191
+ primary: row.primary
3192
+ }));
3193
+ }
3194
+ return {
3195
+ ok: true,
3196
+ roles: judged
3197
+ };
3198
+ }
3199
+ /** {@link judgeCrmPicklistValue} for the lead source. */ export function judgeCrmLeadSource(picklist, value, current) {
3200
+ return judgeCrmPicklistValue(CRM_LEAD_SOURCE_PICKLIST, picklist, value, current);
3201
+ }
3202
+ /**
3203
+ * What a select offers for a record holding `current`: every active value
3204
+ * in order, then the record's own value when the list would not otherwise
3205
+ * show it, so the select keeps what the record holds until it is changed.
3206
+ */ export const crmPicklistOptions = picklistOptions;
3207
+ /**
3208
+ * Where a label sorts: its value's position in the list, unlisted values
3209
+ * after every listed one, and a record with none last. The order the admin
3210
+ * chose is the order a sort by the field reads in, as Salesforce's does.
3211
+ */ export const crmPicklistRank = picklistRank;
3212
+ /*------------------------------------------
3213
+ * A LEAD'S STATUS LABEL (AGL-3512).
3214
+ *
3215
+ * `status` is the meaning and stays the truth: a writer that knows only the
3216
+ * meaning — an automation, an inbox reply, a rule — sets `status` alone and
3217
+ * the lead still reads right. `statusLabel` names which of the org's values
3218
+ * of that meaning the lead holds, and is believed only while it IS one of
3219
+ * that meaning; otherwise, or when absent, the lead shows the default value
3220
+ * when it is active and of that meaning, else the first active value of
3221
+ * that meaning, else the standard label.
3222
+ *-----------------------------------------*/ /** The lead status value set's document id. */ export const CRM_LEAD_STATUS_PICKLIST = 'leadStatus';
3223
+ /** The field a lead's status label is stored in, beside `status`. */ export const CRM_LEAD_STATUS_LABEL_FIELD = 'statusLabel';
3224
+ /** The org's lead status list as every reader should take it. */ export function effectiveCrmLeadStatusPicklist(raw) {
3225
+ return effectiveCrmPicklist(CRM_LEAD_STATUS_PICKLIST, raw);
3226
+ }
3227
+ /** The standard lead statuses alone — what a reader answers before the org's list is read. */ export const STANDARD_CRM_LEAD_STATUS_PICKLIST = effectiveCrmLeadStatusPicklist(null);
3228
+ /**
3229
+ * The value a lead with `status` shows when it names none of its own: the
3230
+ * default when active and of that meaning, else the first active value of
3231
+ * that meaning, else the first of that meaning at all.
3232
+ */ export function crmLeadStatusValueFor(picklist, status) {
3233
+ var _ref, _ref1;
3234
+ const ofMeaning = picklist.values.filter((value)=>value.meaning === status);
3235
+ const preferred = ofMeaning.find((value)=>value.id === picklist.defaultValueId && value.active);
3236
+ return (_ref = (_ref1 = preferred != null ? preferred : ofMeaning.find((value)=>value.active)) != null ? _ref1 : ofMeaning[0]) != null ? _ref : null;
3237
+ }
3238
+ /** The label a writer stamps beside `status` when it sets a meaning and names no value. */ export function crmLeadStatusLabelFor(picklist, status) {
3239
+ var _ref;
3240
+ var _crmLeadStatusValueFor;
3241
+ return (_ref = (_crmLeadStatusValueFor = crmLeadStatusValueFor(picklist, status)) == null ? void 0 : _crmLeadStatusValueFor.label) != null ? _ref : CRM_LEAD_STATUS_LABELS[status];
3242
+ }
3243
+ /**
3244
+ * How a lead's status reads: its own `statusLabel` while that is a value of
3245
+ * its `status`'s meaning (as the list spells it), else the meaning's label
3246
+ * by {@link crmLeadStatusLabelFor}. A label the list no longer holds is shown
3247
+ * as stored while nothing contradicts it — a value deleted without moving
3248
+ * every lead off it reads as what the lead was given.
3249
+ */ export function crmLeadStatusLabel(lead, picklist = STANDARD_CRM_LEAD_STATUS_PICKLIST) {
3250
+ const status = crmLeadStatus(lead);
3251
+ const held = normalizeCrmPicklistLabel(lead == null ? void 0 : lead.statusLabel);
3252
+ if (held) {
3253
+ const value = picklistValueByLabel(picklist, held);
3254
+ if ((value == null ? void 0 : value.meaning) === status) return value.label;
3255
+ if (!value) return held;
3256
+ }
3257
+ return crmLeadStatusLabelFor(picklist, status);
3258
+ }
3259
+ /**
3260
+ * What a write naming a lead status stores (AGL-3512): an ACTIVE value's
3261
+ * label, in any case or spacing, stores its meaning and its label; one of
3262
+ * the meanings themselves (`working`) stores that meaning and the label
3263
+ * {@link crmLeadStatusLabelFor} gives it; the lead's `current` label is
3264
+ * kept even when inactive. `allowed` narrows the meanings this door may
3265
+ * set — every door but a conversion leaves Qualified out — and anything
3266
+ * else is refused naming what the door accepts.
3267
+ */ export function resolveCrmLeadStatusWrite(picklist, requested, options = {}) {
3268
+ var _options_allowed;
3269
+ const allowed = (_options_allowed = options.allowed) != null ? _options_allowed : CRM_LEAD_STATUSES;
3270
+ const text = normalizeCrmPicklistLabel(requested);
3271
+ const refuse = ()=>{
3272
+ const labels = picklist.values.filter((value)=>value.active && allowed.includes(value.meaning)).map((value)=>value.label);
3273
+ return {
3274
+ ok: false,
3275
+ error: `Lead status must be one of: ${labels.join(', ')}.`
3276
+ };
3277
+ };
3278
+ if (!text) return refuse();
3279
+ const value = picklistValueByLabel(picklist, text);
3280
+ const current = options.current ? normalizeCrmPicklistLabel(options.current.statusLabel) : '';
3281
+ const keeps = Boolean(value && current && picklistLabelKeyOf(current) === picklistLabelKeyOf(value.label));
3282
+ if (value && isCrmLeadStatus(value.meaning) && (value.active || keeps)) {
3283
+ return allowed.includes(value.meaning) ? {
3284
+ ok: true,
3285
+ status: value.meaning,
3286
+ statusLabel: value.label
3287
+ } : refuse();
3288
+ }
3289
+ const meaning = text.toLowerCase();
3290
+ if (isCrmLeadStatus(meaning) && allowed.includes(meaning)) {
3291
+ return {
3292
+ ok: true,
3293
+ status: meaning,
3294
+ statusLabel: crmLeadStatusLabelFor(picklist, meaning)
3295
+ };
3296
+ }
3297
+ return refuse();
3298
+ }
3299
+ const picklistLabelKeyOf = (label)=>normalizeCrmPicklistLabel(label).toLowerCase();
3300
+ /**
3301
+ * The values a lead status select offers for a lead holding `current`:
3302
+ * every active value whose meaning `allowed` admits, in the list's order,
3303
+ * then the lead's own value when the list would not otherwise show it.
3304
+ */ export function crmLeadStatusOptions(picklist, allowed = CRM_LEAD_STATUSES, current) {
3305
+ const options = picklist.values.filter((value)=>value.active && allowed.includes(value.meaning)).map((value)=>({
3306
+ label: value.label,
3307
+ status: value.meaning,
3308
+ inactive: false
3309
+ }));
3310
+ if (current) {
3311
+ const label = crmLeadStatusLabel(current, picklist);
3312
+ if (!options.some((option)=>option.label === label)) {
3313
+ options.push({
3314
+ label,
3315
+ status: crmLeadStatus(current),
3316
+ inactive: true
3317
+ });
3318
+ }
3319
+ }
3320
+ return options;
3321
+ }
3322
+ /*==========================================
3323
+ * SEMANTIC PICKLISTS ON A TASK (AGL-3517).
3324
+ *
3325
+ * Status, Priority and Type keep the task's own `status`, `priority` and
3326
+ * `kind` as the MEANING every query, reminder and automation reads, and
3327
+ * store the org's label beside it. These helpers are the two directions:
3328
+ * what a task shows for its meaning and label, and what a write stores for
3329
+ * a label or a meaning a person, a file or an API caller named.
3330
+ *=========================================*/ /** The label a value of `meaning` reads as: the first active one, else the first at all. */ export function crmPicklistMeaningLabel(picklist, meaning) {
3331
+ var _ref, _held_find;
3332
+ var _this;
3333
+ if (typeof meaning !== 'string' || !meaning) return null;
3334
+ const held = picklist.values.filter((value)=>value.meaning === meaning);
3335
+ return (_ref = (_this = (_held_find = held.find((value)=>value.active)) != null ? _held_find : held[0]) == null ? void 0 : _this.label) != null ? _ref : null;
3336
+ }
3337
+ /**
3338
+ * What a record shows for a semantic field: its own label when it holds
3339
+ * one, else the first active value of its meaning, else the meaning itself.
3340
+ */ export function crmPicklistShownLabel(picklist, meaning, label) {
3341
+ var _crmPicklistMeaningLabel;
3342
+ const own = normalizeCrmPicklistLabel(label);
3343
+ if (own) return own;
3344
+ return (_crmPicklistMeaningLabel = crmPicklistMeaningLabel(picklist, meaning)) != null ? _crmPicklistMeaningLabel : String(meaning != null ? meaning : '');
3345
+ }
3346
+ /**
3347
+ * The label a NEW record of `meaning` starts with: the list's default when
3348
+ * it means that, else the first active value that does.
3349
+ */ export function crmPicklistLabelForNew(picklist, meaning) {
3350
+ const fallback = crmPicklistDefaultLabel(picklist);
3351
+ const value = fallback ? crmPicklistValueByLabel(picklist, fallback) : null;
3352
+ return value && value.meaning === meaning ? value.label : crmPicklistMeaningLabel(picklist, meaning);
3353
+ }
3354
+ /**
3355
+ * A label OR a meaning, as a write names it, resolved against picklist `id`:
3356
+ *
3357
+ * - an active value's label (any case or spacing) stores that value's
3358
+ * label and its meaning;
3359
+ * - the record's `current` label is kept, with its value's meaning, even
3360
+ * when the value has since been deactivated;
3361
+ * - one of the definition's meanings (`high`, `done`, `call`) stores that
3362
+ * meaning with the first active value's label — or the record's current
3363
+ * label when that already means it;
3364
+ * - anything else is refused naming the values the list allows.
3365
+ *
3366
+ * `null` answers a blank input: the caller keeps its own default.
3367
+ */ export function resolveCrmSemanticPicklistWrite(id, picklist, input, current) {
3368
+ var _definition_meanings;
3369
+ const text = normalizeCrmPicklistLabel(input);
3370
+ if (!text) return null;
3371
+ const definition = definitionOf(id);
3372
+ const meanings = (_definition_meanings = definition.meanings) != null ? _definition_meanings : [];
3373
+ const held = normalizeCrmPicklistLabel(current);
3374
+ const byLabel = crmPicklistValueByLabel(picklist, text);
3375
+ const keeps = held && held.toLowerCase() === text.toLowerCase();
3376
+ if ((byLabel == null ? void 0 : byLabel.meaning) && (byLabel.active || keeps)) {
3377
+ return {
3378
+ ok: true,
3379
+ meaning: byLabel.meaning,
3380
+ label: keeps ? held : byLabel.label
3381
+ };
3382
+ }
3383
+ const meaning = meanings.find((entry)=>entry === text.toLowerCase());
3384
+ if (meaning) {
3385
+ const heldValue = held ? crmPicklistValueByLabel(picklist, held) : null;
3386
+ return {
3387
+ ok: true,
3388
+ meaning,
3389
+ label: (heldValue == null ? void 0 : heldValue.meaning) === meaning ? held : crmPicklistMeaningLabel(picklist, meaning)
3390
+ };
3391
+ }
3392
+ return {
3393
+ ok: false,
3394
+ error: crmPicklistRefusal(id, picklist)
3395
+ };
3396
+ }
3397
+ /** The picklist id behind each of a task's semantic fields. */ export const CRM_TASK_PICKLIST_IDS = {
3398
+ status: 'taskStatus',
3399
+ priority: 'taskPriority',
3400
+ type: 'taskType'
3401
+ };
3402
+ /** The org's three task lists from their stored documents — standard values alone for none. */ export function effectiveCrmTaskPicklists(raw = {}) {
3403
+ return {
3404
+ status: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.status, raw.status),
3405
+ priority: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.priority, raw.priority),
3406
+ type: effectiveCrmPicklist(CRM_TASK_PICKLIST_IDS.type, raw.type)
3407
+ };
3408
+ }
3409
+ /** What a task shows for its status, priority and type — see `crmPicklistShownLabel`. */ export function crmTaskPicklistLabels(task, picklists) {
3410
+ var _task_status, _task_priority, _task_kind;
3411
+ return {
3412
+ status: crmPicklistShownLabel(picklists.status, (_task_status = task.status) != null ? _task_status : 'open', task.statusLabel),
3413
+ priority: crmPicklistShownLabel(picklists.priority, (_task_priority = task.priority) != null ? _task_priority : 'normal', task.priorityLabel),
3414
+ type: crmPicklistShownLabel(picklists.type, (_task_kind = task.kind) != null ? _task_kind : 'todo', task.typeLabel)
3415
+ };
3416
+ }
3417
+ /**
3418
+ * The status a tick or an untick writes: done with the first active done
3419
+ * value's label ("Completed"), or open with the label a new task starts
3420
+ * with ("Not Started").
3421
+ */ export function crmTaskStatusWrite(picklist, done) {
3422
+ return done ? {
3423
+ status: 'done',
3424
+ statusLabel: crmPicklistMeaningLabel(picklist, 'done')
3425
+ } : {
3426
+ status: 'open',
3427
+ statusLabel: crmPicklistLabelForNew(picklist, 'open')
3428
+ };
3429
+ }
3430
+ /** The labels a new task of these meanings starts with. */ export function crmTaskLabelsForNew(picklists, meanings) {
3431
+ var _meanings_status;
3432
+ return {
3433
+ statusLabel: crmPicklistLabelForNew(picklists.status, (_meanings_status = meanings.status) != null ? _meanings_status : 'open'),
3434
+ priorityLabel: crmPicklistLabelForNew(picklists.priority, meanings.priority),
3435
+ typeLabel: crmPicklistLabelForNew(picklists.type, meanings.kind)
3436
+ };
3437
+ }
3438
+ /** The name a lead is listed under: the name it carries, else its address. */ export function crmLeadDisplayName(lead) {
3439
+ var _ref, _ref1;
3440
+ const name = String((_ref = lead == null ? void 0 : lead['name']) != null ? _ref : '').trim();
3441
+ return name || String((_ref1 = lead == null ? void 0 : lead['email']) != null ? _ref1 : '').trim();
3442
+ }
3443
+ /**
3444
+ * A lead's status as the list and the filter should read it.
3445
+ *
3446
+ * `new` for a lead that carries no status at all — every lead captured
3447
+ * before the CRM existed, and every lead the capture door writes today,
3448
+ * because `addHostLead` stamps none. Reading the absence as `new` is what
3449
+ * lets the section open on the leads a site already holds rather than on an
3450
+ * empty list until each one has been touched once. A stored value the union
3451
+ * does not name also reads as `new`: it is a document some other writer
3452
+ * produced, and refusing to list it would hide a person.
3453
+ */ export function crmLeadStatus(lead) {
3454
+ const status = lead == null ? void 0 : lead.status;
3455
+ return isCrmLeadStatus(status) ? status : 'new';
3456
+ }
3457
+ /** Whether a lead still needs working — see {@link CRM_LEAD_OPEN_STATUSES}. */ export function isCrmLeadOpen(lead) {
3458
+ return CRM_LEAD_OPEN_STATUSES.includes(crmLeadStatus(lead));
3459
+ }
3460
+ /*==========================================
3461
+ * SAVED VIEWS (AGL-2617) — a list the way one person works it.
3462
+ *
3463
+ * "My open leads in Texas" is a list a rep opens every morning, and it is
3464
+ * three things at once: the filters that pick the people, the columns they
3465
+ * want to see about them, and the order they want them in. A `ContactSegment`
3466
+ * kept the first of those for two dimensions — tags and capture sources —
3467
+ * and nothing kept the rest, so every reload put the reader back at the
3468
+ * whole list. A view keeps all three, for every list in the CRM, under a
3469
+ * name, and is addressable: `?view=<id>` on the section's own URL opens it.
3470
+ *
3471
+ * ## The filters are the LIST'S grammar, not a second one
3472
+ *
3473
+ * A clause here is `{ field, op, value }` — structurally the
3474
+ * `ListFilterRequest` the shared list-filter grammar already speaks, the
3475
+ * request a grid's filter panel produces and the one the query translator
3476
+ * and the in-memory matcher both read. A view stores a list of them and
3477
+ * nothing more, so a view can only ask what the list can already answer,
3478
+ * and a list that gains a filterable field gains it for its views at no
3479
+ * cost. The type is restated here rather than imported because this module
3480
+ * is pure data and the grammar's home is a UI library; the shape is the
3481
+ * contract, and `normalizeCrmViewFilters` is what holds a stored document
3482
+ * to it.
3483
+ *
3484
+ * ## Mine, and shared
3485
+ *
3486
+ * A view is one person's working arrangement until they say otherwise, so
3487
+ * it lists only for its owner; `shared` puts it in front of everyone who
3488
+ * can read the section. Both are stamped `visibleTo` like every CRM row,
3489
+ * because a view is a description of the people it selects and answers to
3490
+ * the same authority they do — a private view is hidden by the LISTING, not
3491
+ * by the rules, and the rules let the creator or an org-wide member change
3492
+ * or remove it.
3493
+ *
3494
+ * ## The default is the reader's, not the view's
3495
+ *
1913
3496
  * Which view a section opens on is a preference of one person for one
1914
3497
  * organization, so it lives on the reader's own profile document beside
1915
3498
  * their notification mutes rather than on the view — a shared view somebody
@@ -2328,11 +3911,17 @@ export function crmEmailStatusKey(record) {
2328
3911
  // A status outside the vocabulary reads as nothing known, as `readEmailState` reads it.
2329
3912
  return isEmailStateStatus(status) ? status : CRM_EMAIL_STATUS_NONE;
2330
3913
  }
2331
- /** A lead's lead source as the list compares it: the picklist's key, `null` for none. */ export const CRM_LEAD_SOURCE_KEY_FIELD = 'leadSourceKey';
2332
- export function crmLeadSourceKey(value) {
3914
+ /**
3915
+ * A picklist label as a list query compares it — the key a target's
3916
+ * `keyField` holds beside the label — `null` for none.
3917
+ */ export function crmPicklistKey(value) {
2333
3918
  const key = SEARCH_KEY(normalizeCrmPicklistLabel(value));
2334
3919
  return key || null;
2335
3920
  }
3921
+ /** A lead's lead source as the list compares it: the picklist's key, `null` for none. */ export const CRM_LEAD_SOURCE_KEY_FIELD = 'leadSourceKey';
3922
+ export function crmLeadSourceKey(value) {
3923
+ return crmPicklistKey(value);
3924
+ }
2336
3925
  /** What a lead's search box reads (AGL-3246). */ export const CRM_LEAD_SEARCH_SOURCES = [
2337
3926
  'name',
2338
3927
  'email',
@@ -2362,28 +3951,67 @@ function leadCampaignIds(lead) {
2362
3951
  scopedCampaignIds: scopedSearchTokens(lead['visibleTo'], leadCampaignIds(lead)),
2363
3952
  status: crmLeadStatus(lead),
2364
3953
  leadSourceKey: crmLeadSourceKey(lead['leadSource']),
2365
- emailStatus: crmEmailStatusKey(lead)
3954
+ emailStatus: crmEmailStatusKey(lead),
3955
+ // The Industry and Rating the Leads list filters by (AGL-3513).
3956
+ industryKey: crmPicklistKey(lead['industry']),
3957
+ ratingKey: crmPicklistKey(lead['rating'])
2366
3958
  });
2367
3959
  }
2368
3960
  /** What a company's search box reads: its name and its domain. */ export const CRM_COMPANY_SEARCH_SOURCES = [
2369
3961
  'name',
2370
3962
  'domain'
2371
3963
  ];
2372
- export function crmCompanyListFields(record) {
3964
+ /**
3965
+ * Every field the Companies list queries by: the search tokens, and the
3966
+ * key of each picklist field that names one (AGL-3514) — `null` for none,
3967
+ * so "no value" is a value a query can ask for.
3968
+ */ export function crmCompanyListFields(record) {
2373
3969
  const company = record;
2374
- return crmSearchFields(company['visibleTo'], CRM_COMPANY_SEARCH_SOURCES.map((field)=>company[field]));
3970
+ return _extends({}, crmSearchFields(company['visibleTo'], CRM_COMPANY_SEARCH_SOURCES.map((field)=>company[field])), {
3971
+ typeKey: crmPicklistKey(company['type']),
3972
+ industryKey: crmPicklistKey(company['industry']),
3973
+ ratingKey: crmPicklistKey(company['rating']),
3974
+ accountSourceKey: crmPicklistKey(company['accountSource'])
3975
+ });
2375
3976
  }
2376
3977
  /**
2377
3978
  * What a deal's search box reads: its title (AGL-3315) — as word prefixes,
2378
3979
  * and as `titleLower`, the whole title's key, which a reader whose access
2379
- * is some sites searches by its start.
3980
+ * is some sites searches by its start. And the keys the Deals list filters
3981
+ * its Type and Lead source by (AGL-3516): each label as the picklist
3982
+ * compares it, `null` for none.
2380
3983
  */ export function crmDealListFields(record) {
2381
3984
  const deal = record;
2382
3985
  return _extends({}, crmSearchFields(deal['visibleTo'], [
2383
3986
  deal['title']
2384
3987
  ]), {
2385
- titleLower: SEARCH_KEY(typeof deal['title'] === 'string' ? deal['title'] : '')
2386
- });
3988
+ titleLower: SEARCH_KEY(typeof deal['title'] === 'string' ? deal['title'] : ''),
3989
+ typeKey: crmPicklistKey(deal['type']),
3990
+ leadSourceKey: crmPicklistKey(deal['leadSource'])
3991
+ }, crmDealContactRoleListFields(deal));
3992
+ }
3993
+ /**
3994
+ * The arrays a deal's contact roles are found by (AGL-3521): every contact
3995
+ * on it — `contactRoleContactIds`, which a contact's page asks
3996
+ * `array-contains` at the organization level, and the same ids behind each
3997
+ * scope token (`scopedContactRoleContactIds`, `host:x~contactId`), which it
3998
+ * asks under a site in the one array clause a query has — and the keys of
3999
+ * the roles they hold, which a rename of a role finds the deals by.
4000
+ */ export function crmDealContactRoleListFields(deal) {
4001
+ const roles = dealContactRolesOf(deal);
4002
+ const contactRoleContactIds = roles.map((row)=>row.contactId);
4003
+ const keys = new Set();
4004
+ for (const row of roles){
4005
+ const key = crmPicklistKey(row.role);
4006
+ if (key) keys.add(key);
4007
+ }
4008
+ return {
4009
+ contactRoleContactIds,
4010
+ scopedContactRoleContactIds: scopedSearchTokens(deal['visibleTo'], contactRoleContactIds),
4011
+ contactRoleKeys: [
4012
+ ...keys
4013
+ ]
4014
+ };
2387
4015
  }
2388
4016
  /** What a task's search box reads: its title. */ export function crmTaskListFields(record) {
2389
4017
  const task = record;
@@ -2403,8 +4031,8 @@ export function crmCompanyListFields(record) {
2403
4031
  * `{groupId}:{field}` this holder has one (for "is set")
2404
4032
  * `*:{field}={value}`, `*:{field}` any holder's, for the organization level
2405
4033
  *
2406
- * `field` is `owner`, `stage`, `source`, `company`, `tag` or `custom.{key}`,
2407
- * plus two presence-only fields: `orders` for a holder the person has bought
4034
+ * `field` is `owner`, `stage`, `source`, `company`, `tag`, `custom.{key}` or
4035
+ * `leadSource` (AGL-3511), plus two presence-only fields: `orders` for a holder the person has bought
2408
4036
  * from, and `ltv` for one they are worth something to — the per-holder
2409
4037
  * figures a range cannot reach, asked only whether there are any.
2410
4038
  * A text value is keyed lower-cased and single-spaced; a number or a flag
@@ -2455,6 +4083,8 @@ export function crmCompanyListFields(record) {
2455
4083
  if (/^[A-Za-z0-9_-]{1,64}$/.test(key)) add(`custom.${key}`, value);
2456
4084
  }
2457
4085
  }
4086
+ // Keyed as `crmPicklistKey` compares a label, so a rename's rewrite restamps it.
4087
+ add('leadSource', facet['leadSource']);
2458
4088
  }
2459
4089
  /** Every facet key a contact carries — see the block above. */ export function crmContactFacetKeys(record) {
2460
4090
  const facets = record[CONTACT_FACETS_FIELD];
@@ -2523,7 +4153,10 @@ export function crmContactListFields(record) {
2523
4153
  'status',
2524
4154
  'leadSource',
2525
4155
  'emailState',
2526
- 'campaignIds'
4156
+ 'campaignIds',
4157
+ // AGL-3513.
4158
+ 'industry',
4159
+ 'rating'
2527
4160
  ],
2528
4161
  contacts: [
2529
4162
  'visibleTo',
@@ -2534,11 +4167,19 @@ export function crmContactListFields(record) {
2534
4167
  ],
2535
4168
  companies: [
2536
4169
  'visibleTo',
2537
- ...CRM_COMPANY_SEARCH_SOURCES
4170
+ ...CRM_COMPANY_SEARCH_SOURCES,
4171
+ 'type',
4172
+ 'industry',
4173
+ 'rating',
4174
+ 'accountSource'
2538
4175
  ],
2539
4176
  deals: [
2540
4177
  'visibleTo',
2541
- 'title'
4178
+ 'title',
4179
+ 'type',
4180
+ 'leadSource',
4181
+ 'contactRoles',
4182
+ 'contactId'
2542
4183
  ],
2543
4184
  crmTasks: [
2544
4185
  'visibleTo',