@aglyn/plugins-crm 1.0.0-beta.232 → 1.0.0-beta.234

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 (55) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/companies-section.js +43 -8
  3. package/src/lib/components/companies-section.js.map +1 -1
  4. package/src/lib/components/company-edit-drawer.js +3 -1
  5. package/src/lib/components/company-edit-drawer.js.map +1 -1
  6. package/src/lib/components/contact-detail-page.js +6 -1
  7. package/src/lib/components/contact-detail-page.js.map +1 -1
  8. package/src/lib/components/contacts-section.js +37 -8
  9. package/src/lib/components/contacts-section.js.map +1 -1
  10. package/src/lib/components/crm-email-check.d.ts +14 -0
  11. package/src/lib/components/crm-email-check.js +38 -1
  12. package/src/lib/components/crm-email-check.js.map +1 -1
  13. package/src/lib/components/crm-next-activity-column.d.ts +5 -4
  14. package/src/lib/components/crm-next-activity-column.js +5 -4
  15. package/src/lib/components/crm-next-activity-column.js.map +1 -1
  16. package/src/lib/components/deal-edit-drawer.js +3 -1
  17. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  18. package/src/lib/components/deals-section.js +50 -6
  19. package/src/lib/components/deals-section.js.map +1 -1
  20. package/src/lib/components/lead-properties-card.js +9 -3
  21. package/src/lib/components/lead-properties-card.js.map +1 -1
  22. package/src/lib/components/leads-section.js +61 -11
  23. package/src/lib/components/leads-section.js.map +1 -1
  24. package/src/lib/components/tasks-section.js +61 -9
  25. package/src/lib/components/tasks-section.js.map +1 -1
  26. package/src/lib/constants/company-filters.js +40 -2
  27. package/src/lib/constants/company-filters.js.map +1 -1
  28. package/src/lib/constants/contact-filters.d.ts +10 -1
  29. package/src/lib/constants/contact-filters.js +62 -2
  30. package/src/lib/constants/contact-filters.js.map +1 -1
  31. package/src/lib/constants/deal-filters.js +78 -0
  32. package/src/lib/constants/deal-filters.js.map +1 -1
  33. package/src/lib/hooks/use-crm-column-sort.d.ts +67 -0
  34. package/src/lib/hooks/use-crm-column-sort.js +156 -0
  35. package/src/lib/hooks/use-crm-column-sort.js.map +1 -0
  36. package/src/lib/hooks/use-crm-tasks.d.ts +6 -1
  37. package/src/lib/hooks/use-crm-tasks.js +10 -8
  38. package/src/lib/hooks/use-crm-tasks.js.map +1 -1
  39. package/src/lib/model/crm-list-query.d.ts +6 -0
  40. package/src/lib/model/crm-list-query.js +13 -0
  41. package/src/lib/model/crm-list-query.js.map +1 -1
  42. package/src/lib/model/lead-filters.d.ts +19 -2
  43. package/src/lib/model/lead-filters.js +112 -2
  44. package/src/lib/model/lead-filters.js.map +1 -1
  45. package/src/lib/model/native-vocabulary.d.ts +41 -0
  46. package/src/lib/model/native-vocabulary.js +59 -0
  47. package/src/lib/model/native-vocabulary.js.map +1 -0
  48. package/src/lib/model/task-views.d.ts +15 -2
  49. package/src/lib/model/task-views.js +50 -4
  50. package/src/lib/model/task-views.js.map +1 -1
  51. package/src/lib/server/email-check.d.ts +4 -1
  52. package/src/lib/server/email-check.js +18 -6
  53. package/src/lib/server/email-check.js.map +1 -1
  54. package/src/lib/transfer/tasks.js +2 -1
  55. package/src/lib/transfer/tasks.js.map +1 -1
@@ -19,7 +19,7 @@ import { type EmailStateStatus } from '@aglyn/aglyn/app-utils/email-state';
19
19
  import type { CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm';
20
20
  import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-util-tools/list-query/list-filter';
21
21
  import { type ListGridFilterCodec } from '@aglyn/shared-util-tools/list-query/list-filter-codecs';
22
- import type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan';
22
+ import type { ListQueryDeclaration, ListQueryFilter, ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan';
23
23
  import { type CrmClauseAsked } from './crm-list-query';
24
24
  /**
25
25
  * What the Leads section's `Show` control offers (AGL-2608): the two
@@ -106,7 +106,18 @@ export declare const LEAD_CAMPAIGN_CODEC: ListGridFilterCodec;
106
106
  export declare const LEAD_SOURCE_CODEC: ListGridFilterCodec;
107
107
  /** The Leads fields whose stored clauses translate their own way. */
108
108
  export declare const LEAD_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>>;
109
- /** The one order the Leads list takes: newest seen first. */
109
+ /**
110
+ * The Leads list's orders: newest seen first, then each header the query can
111
+ * order by (AGL-3680) — Last seen the other way, the lead's name or address
112
+ * (`nameSortKey`), its company and title (`companyLower`, `jobTitleLower`)
113
+ * and its address verdict (`emailStatus`), each stored on every lead. Each
114
+ * is `alone`: served with no filter or search on beyond the Status the list
115
+ * always asks (`leadStatusBase`), by one `(visibleTo, field)` and one
116
+ * `(status, field)` composite per direction. The other columns — Status and
117
+ * Lead source, Industry and Rating in the org's picklist order, the owner's
118
+ * and campaigns' names, the sites, sources, tags and custom values — sort
119
+ * the page.
120
+ */
110
121
  export declare const LEAD_LIST_SORTS: readonly ListQuerySort[];
111
122
  /** The fields the Leads query asks — each a stored field, as named. */
112
123
  export declare const LEAD_QUERY_FIELDS: readonly ListFilterField[];
@@ -142,6 +153,12 @@ export interface LeadQueryReader {
142
153
  * a stored view that names a retired choice says so.
143
154
  */
144
155
  export declare function leadQueryClause(clause: CrmViewFilterClause | ListFilterRequest, reader?: LeadQueryReader): CrmClauseAsked;
156
+ /** The asked Status clause as the base predicate it stands for, or none. */
157
+ export declare function leadStatusBase(clause: ListFilterRequest | undefined): ListQueryFilter[];
158
+ /** The base's shape for `listQueryIndexes`: the status equality. */
159
+ export declare const LEAD_LIST_STATUS_BASE_INDEX: readonly [{
160
+ readonly path: "status";
161
+ }];
145
162
  /**
146
163
  * The query shape the Leads list can send beyond its own declaration, for
147
164
  * the index pin (AGL-3321): a collaborator's address prefix beside the
@@ -309,10 +309,86 @@ const STATUS_ALL = {
309
309
  * array clauses, so the two do not stand together (nor under a site, where
310
310
  * the scope clause is the query's one array clause, save the search, which
311
311
  * folds into it for a reader who may drop it).
312
- *=========================================*/ /** The one order the Leads list takes: newest seen first. */ export const LEAD_LIST_SORTS = [
312
+ *=========================================*/ /**
313
+ * The Leads list's orders: newest seen first, then each header the query can
314
+ * order by (AGL-3680) — Last seen the other way, the lead's name or address
315
+ * (`nameSortKey`), its company and title (`companyLower`, `jobTitleLower`)
316
+ * and its address verdict (`emailStatus`), each stored on every lead. Each
317
+ * is `alone`: served with no filter or search on beyond the Status the list
318
+ * always asks (`leadStatusBase`), by one `(visibleTo, field)` and one
319
+ * `(status, field)` composite per direction. The other columns — Status and
320
+ * Lead source, Industry and Rating in the org's picklist order, the owner's
321
+ * and campaigns' names, the sites, sources, tags and custom values — sort
322
+ * the page.
323
+ */ export const LEAD_LIST_SORTS = [
313
324
  {
314
325
  path: 'lastSeenAtMs',
315
- direction: 'desc'
326
+ direction: 'desc',
327
+ column: 'lastSeenAtMs',
328
+ label: 'Last seen'
329
+ },
330
+ {
331
+ path: 'lastSeenAtMs',
332
+ direction: 'asc',
333
+ column: 'lastSeenAtMs',
334
+ label: 'Last seen',
335
+ alone: true
336
+ },
337
+ {
338
+ path: 'nameSortKey',
339
+ direction: 'asc',
340
+ column: 'name',
341
+ label: 'Lead',
342
+ alone: true
343
+ },
344
+ {
345
+ path: 'nameSortKey',
346
+ direction: 'desc',
347
+ column: 'name',
348
+ label: 'Lead',
349
+ alone: true
350
+ },
351
+ {
352
+ path: 'companyLower',
353
+ direction: 'asc',
354
+ column: 'company',
355
+ label: 'Company',
356
+ alone: true
357
+ },
358
+ {
359
+ path: 'companyLower',
360
+ direction: 'desc',
361
+ column: 'company',
362
+ label: 'Company',
363
+ alone: true
364
+ },
365
+ {
366
+ path: 'jobTitleLower',
367
+ direction: 'asc',
368
+ column: 'jobTitle',
369
+ label: 'Title',
370
+ alone: true
371
+ },
372
+ {
373
+ path: 'jobTitleLower',
374
+ direction: 'desc',
375
+ column: 'jobTitle',
376
+ label: 'Title',
377
+ alone: true
378
+ },
379
+ {
380
+ path: 'emailStatus',
381
+ direction: 'asc',
382
+ column: 'emailState',
383
+ label: 'Email',
384
+ alone: true
385
+ },
386
+ {
387
+ path: 'emailStatus',
388
+ direction: 'desc',
389
+ column: 'emailState',
390
+ label: 'Email',
391
+ alone: true
316
392
  }
317
393
  ];
318
394
  /** The fields the Leads query asks — each a stored field, as named. */ export const LEAD_QUERY_FIELDS = [
@@ -477,6 +553,40 @@ const ORG_READER = {
477
553
  };
478
554
  }
479
555
  }
556
+ /*------------------------------------------
557
+ * THE STATUS IS THE LIST'S BASE (AGL-3680).
558
+ *
559
+ * The Leads list always asks a Status — Open unless the reader picks
560
+ * another, or All — so a header order served only with no filter on (an
561
+ * `alone` order) would never be served. The Status clause is therefore put
562
+ * on the query as part of its BASE, beside the scope clause, the way a task
563
+ * view's status is: an `alone` order pairs with it through one
564
+ * `(status, field)` composite, and every other clause still counts as a
565
+ * filter. The clause stays the reader's — the chip, the view and the
566
+ * refusals read it as they always did.
567
+ *-----------------------------------------*/ /** The asked Status clause as the base predicate it stands for, or none. */ export function leadStatusBase(clause) {
568
+ if (!clause || clause.field !== 'status') return [];
569
+ const values = crmClauseValues(clause);
570
+ if (!values.length) return [];
571
+ return values.length === 1 ? [
572
+ {
573
+ path: 'status',
574
+ op: '==',
575
+ value: values[0]
576
+ }
577
+ ] : [
578
+ {
579
+ path: 'status',
580
+ op: 'in',
581
+ value: values
582
+ }
583
+ ];
584
+ }
585
+ /** The base's shape for `listQueryIndexes`: the status equality. */ export const LEAD_LIST_STATUS_BASE_INDEX = [
586
+ {
587
+ path: 'status'
588
+ }
589
+ ];
480
590
  /**
481
591
  * The query shape the Leads list can send beyond its own declaration, for
482
592
  * the index pin (AGL-3321): a collaborator's address prefix beside the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n CRM_LEAD_SOURCE_DIRECTION_FIELD,\n CRM_LEAD_STATUSES,\n type CrmLeadStatus,\n type CrmPicklist,\n crmLeadSourceKey,\n crmPicklistKey,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, type EmailStateStatus } from '@aglyn/aglyn/app-utils/email-state'\nimport type { CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm'\nimport { SCOPED_SEARCH_JOIN } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-util-tools/list-query/list-filter'\nimport { type ListGridFilterCodec, listSelectCodec } from '@aglyn/shared-util-tools/list-query/list-filter-codecs'\nimport type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n crmSelectField,\n} from './crm-list-query'\nimport {\n isLeadSourceDirection,\n LEAD_SOURCE_DIRECTION_LABELS,\n LEAD_SOURCE_DIRECTIONS,\n} from './lead-source-direction'\n\n/**\n * What the Leads section's `Show` control offers (AGL-2608): the two\n * aggregate views and each status on its own. `open` is the default — the\n * list opens on the work — asked of the query as\n * `status in [new, nurturing, working]`,\n * which every lead answers because every lead writer stores a status, `new`\n * for one nobody has touched (`crmLeadListFields`, AGL-3321).\n */\nexport type LeadFilter = 'open' | 'all' | CrmLeadStatus\n\nexport const LEAD_FILTERS: readonly LeadFilter[] = [\n 'open',\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n 'all',\n]\n\nexport const LEAD_FILTER_LABELS: Record<LeadFilter, string> = {\n open: 'Open',\n all: 'All',\n new: CRM_LEAD_STATUS_LABELS.new,\n nurturing: CRM_LEAD_STATUS_LABELS.nurturing,\n working: CRM_LEAD_STATUS_LABELS.working,\n qualified: CRM_LEAD_STATUS_LABELS.qualified,\n unqualified: CRM_LEAD_STATUS_LABELS.unqualified,\n}\n\n/**\n * The Leads section's `Email` control (AGL-3245): every lead, the ones a\n * member must not email — any verdict but `ok` — the ones nothing has been\n * said about, or one verdict on its own. Beside `Show`, not inside it: a\n * bounce does not move a lead out of Open, and a queue of bounced leads is\n * a queue of people to reach some other way.\n */\nexport type LeadEmailFilter = 'any' | 'problem' | 'none' | EmailStateStatus\n\nexport const LEAD_EMAIL_FILTERS: readonly LeadEmailFilter[] = [\n 'any',\n 'problem',\n ...EMAIL_STATE_STATUSES,\n 'none',\n]\n\nexport const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string> = {\n any: 'Any',\n problem: 'Cannot be emailed',\n none: 'Nothing known',\n ...EMAIL_STATE_LABELS,\n}\n\n/**\n * The `Lead source` control's \"no lead source\" choice (AGL-3298). Held in\n * the saved view as an `isEmpty` clause, never as this string; it is only\n * the select's own value for the choice.\n */\nexport const LEAD_SOURCE_FILTER_NONE = '\\u0000none'\n\n/*==========================================\n * THE LEADS GRID'S FILTERS (AGL-3313).\n *\n * The Show, Email, Campaign and Lead source dropdowns became columns of the\n * grid's own Filters panel. Their clauses keep the shape the dropdowns\n * stored, so every saved view of leads reads as it did:\n *\n * status `equals <filter>`; none at all is Open, `all` is every lead\n * emailState `equals <filter>`\n * campaignIds `contains <campaign id>`\n * leadSource `equals <label>`, or `isEmpty` for the leads holding none\n * leadSourceDirection `equals inbound|outbound` (AGL-3511): every lead\n * whose lead source is a value of that group\n * industry, rating `equals <key>` (AGL-3513): a value of the org's\n * Industry or Rating list, by the key the query compares\n *\n * Every one is asked of the Leads query (AGL-3321) through the field a\n * writer stores for it — see {@link leadQueryClause}.\n *=========================================*/\n\n/** The fields the grid's panel offers on Leads. */\nexport const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals', 'isAnyOf'] },\n { column: 'emailState', kind: 'exact', path: 'emailState', operators: ['equals', 'isAnyOf'] },\n { column: 'ownerUid', kind: 'exact', path: 'ownerUid', operators: ['equals', 'isAnyOf'] },\n { column: 'leadSource', kind: 'exact', path: 'leadSource', operators: ['equals', 'isAnyOf'] },\n {\n column: 'leadSourceDirection',\n kind: 'exact',\n path: 'leadSourceDirection',\n operators: ['equals', 'isAnyOf'],\n },\n { column: 'campaignIds', kind: 'exact', path: 'campaignIds', operators: ['equals'] },\n // Salesforce's Industry and Rating (AGL-3513), the lists companies keep.\n { column: 'industry', kind: 'exact', path: 'industry', operators: ['equals', 'isAnyOf'] },\n { column: 'rating', kind: 'exact', path: 'rating', operators: ['equals', 'isAnyOf'] },\n]\n\n/**\n * The lead picklist columns the list filters by (AGL-3513), each with the\n * list its choices come from: a choice's value is the KEY the query\n * compares, its caption the label.\n */\nexport const LEAD_PICKLIST_FILTERS = [\n { column: 'industry', picklistId: 'industry', header: 'Industry' },\n { column: 'rating', picklistId: 'rating', header: 'Rating' },\n] as const\n\n/** What each filter field reads as on a chip. */\nexport const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n emailState: 'Email',\n ownerUid: 'Owner',\n leadSource: 'Lead source',\n leadSourceDirection: 'Lead source direction',\n campaignIds: 'Campaign',\n industry: 'Industry',\n rating: 'Rating',\n}\n\n/** The choices for Status: Open (new, nurturing or working) and each status on its own. */\nexport const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter(\n (option) => option !== 'all',\n).map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : LEAD_FILTER_LABELS[option],\n}))\n\n/** The choices for Email: every verdict, plus the two aggregates. */\nexport const LEAD_EMAIL_FILTER_OPTIONS = LEAD_EMAIL_FILTERS.filter(\n (option) => option !== 'any',\n).map((option) => ({ value: option, label: LEAD_EMAIL_FILTER_LABELS[option] }))\n\n/** The choices for Lead source direction: each group of the Lead source picklist. */\nexport const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS = LEAD_SOURCE_DIRECTIONS.map((direction) => ({\n value: direction,\n label: LEAD_SOURCE_DIRECTION_LABELS[direction],\n}))\n\n/**\n * The Status choices as an org reads them (AGL-3512): still one choice per\n * MEANING — the query asks `status` — each named by the org's values of that\n * meaning, so a list that keeps \"Contacted\" and \"Meeting set\" as Working\n * reads \"Working: Contacted, Meeting set\".\n */\nexport function leadStatusFilterOptions(\n picklist: CrmPicklist,\n): Array<{ value: string; label: string }> {\n const named = (status: CrmLeadStatus): string => {\n const labels = picklist.values\n .filter((value) => value.meaning === status && value.active)\n .map((value) => value.label)\n const meaning = picklistMeaningName(status)\n if (!labels.length) return meaning\n if (labels.length === 1 && labels[0] === meaning) return meaning\n return `${meaning}: ${labels.join(', ')}`\n }\n return LEAD_FILTERS.filter((option) => option !== 'all').map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : named(option),\n }))\n}\n\nconst picklistMeaningName = (status: CrmLeadStatus): string => CRM_LEAD_STATUS_LABELS[status]\n\nconst STATUS_OPEN: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'open' }\nconst STATUS_ALL: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'all' }\n\n/**\n * The clauses the grid shows for a view's stored ones. A stored list with\n * no status clause is Open, which the list opened on before views existed,\n * so it is shown as the clause it means; `status equals all` is every lead\n * and so no clause at all.\n */\nexport function leadClausesForGrid(\n stored: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = stored.find((clause) => clause.field === 'status')\n if (!status) return [STATUS_OPEN, ...stored]\n if (status.op === 'equals' && status.value === 'all') {\n return stored.filter((clause) => clause.field !== 'status')\n }\n return [...stored]\n}\n\n/** The inverse of {@link leadClausesForGrid}: what the view stores. */\nexport function leadClausesToStore(\n shown: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = shown.find((clause) => clause.field === 'status')\n if (!status) return [...shown, STATUS_ALL]\n if (status.op === 'equals' && status.value === 'open') {\n return shown.filter((clause) => clause.field !== 'status')\n }\n return [...shown]\n}\n\n/** Campaign clauses were stored as `contains`; the panel's select says `is`. */\nexport const LEAD_CAMPAIGN_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'contains' || clause.op === 'equals'\n ? { operator: 'is', value: clause.value }\n : null,\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n return clause && clause.op === 'equals' ? { ...clause, op: 'contains' } : null\n },\n}\n\n/** \"No lead source\" is an `isEmpty` clause, shown as a choice of the select. */\nexport const LEAD_SOURCE_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'isEmpty'\n ? { operator: 'is', value: LEAD_SOURCE_FILTER_NONE }\n : listSelectCodec.toItem(clause),\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n if (clause?.op === 'equals' && clause.value === LEAD_SOURCE_FILTER_NONE) {\n return { field: clause.field, op: 'isEmpty', value: '' }\n }\n return clause\n },\n}\n\n/** The Leads fields whose stored clauses translate their own way. */\nexport const LEAD_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>> = {\n campaignIds: LEAD_CAMPAIGN_CODEC,\n leadSource: LEAD_SOURCE_CODEC,\n}\n\n/*==========================================\n * THE LEADS QUERY (AGL-3321).\n *\n * `orgs/{orgId}/leads`, newest seen first, under a site narrowed by the\n * `visibleTo` scope clause. Each grid clause is asked through a field every\n * lead writer stores (`crmLeadListFields` in `@aglyn/aglyn`):\n *\n * status `status`, stored `new` on a lead nobody has touched, so\n * Open is `status in [new, nurturing, working]`\n * emailState `emailStatus`, the verdict's status or `none`; \"Cannot be\n * emailed\" is every verdict but `ok`\n * leadSource `leadSourceKey`, the label as the picklist compares it, or\n * `null` for none\n * leadSourceDirection `leadSourceDirection` (AGL-3577), the group of\n * the lead's value in the org's list, stamped by the writers\n * from that list and rewritten on every lead holding a value\n * the list regroups — so one equality asks a group however\n * many values it holds\n * ownerUid as stored\n * industry, rating `industryKey`, `ratingKey` (AGL-3513), each label\n * as the picklist compares it\n * campaignIds `array-contains` on the lead's own campaigns; under a site,\n * `scopedCampaignIds` — the campaign behind each of the\n * site's scope tokens — which stands in the scope clause's\n * place, so the one array clause answers both\n *\n * and the search box reads `searchTokens` — word prefixes of the name,\n * address, company, title and tags. Every equality rides one\n * `(field, lastSeenAtMs DESC)` composite; the campaign and the search are\n * array clauses, so the two do not stand together (nor under a site, where\n * the scope clause is the query's one array clause, save the search, which\n * folds into it for a reader who may drop it).\n *=========================================*/\n\n/** The one order the Leads list takes: newest seen first. */\nexport const LEAD_LIST_SORTS: readonly ListQuerySort[] = [{ path: 'lastSeenAtMs', direction: 'desc' }]\n\n/** The fields the Leads query asks — each a stored field, as named. */\nexport const LEAD_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n crmSelectField('emailStatus'),\n crmSelectField('ownerUid'),\n {\n column: 'leadSourceKey',\n kind: 'exact',\n path: 'leadSourceKey',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty'],\n },\n {\n column: 'campaignIds',\n kind: 'exact',\n path: 'campaignIds',\n tokensPath: 'campaignIds',\n operators: ['contains'],\n },\n // The lead source's group (AGL-3577), as stamped on the lead.\n crmSelectField(CRM_LEAD_SOURCE_DIRECTION_FIELD),\n // Industry and Rating (AGL-3513), by the key each writer stores.\n crmSelectField('industryKey'),\n crmSelectField('ratingKey'),\n {\n // A campaign under a site: the lead's campaigns behind its scope.\n column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n kind: 'exact',\n path: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n tokensPath: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n operators: ['isAnyOf'],\n },\n]\n\nexport const LEAD_LIST_DECLARATION: ListQueryDeclaration = {\n fields: LEAD_QUERY_FIELDS,\n sorts: LEAD_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the address, beside the scope clause — the `(visibleTo, email)`\n * composite the Inbox's leads list reads too.\n */\nexport const LEAD_PREFIX_SEARCH = {\n field: {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['startsWith'],\n } satisfies ListFilterField,\n notice: 'Search matches the start of a lead’s address for access limited to specific sites.',\n}\n\n/** Whether an asked clause stands in for the scope clause: a campaign behind the site's scope. */\nexport const leadClauseImpliesScope = (clause: ListFilterRequest): boolean =>\n clause.field === CRM_LEAD_SCOPED_CAMPAIGNS_FIELD\n\n/** The verdicts a member must not email to: every one but `ok`. */\nconst EMAIL_PROBLEMS = EMAIL_STATE_STATUSES.filter((status) => status !== 'ok')\n\n/** Who is asking, for the one clause whose shape depends on it: the Campaign. */\nexport interface LeadQueryReader {\n /** The site's scope tokens, or `null` at the organization level. */\n scopeTokens: readonly string[] | null\n /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */\n foldsScope: boolean\n}\n\nconst ORG_READER: LeadQueryReader = { scopeTokens: null, foldsScope: true }\n\n/**\n * One grid clause as the clause the Leads query asks — see the block above.\n * A value the list does not know is refused by name rather than dropped, so\n * a stored view that names a retired choice says so.\n */\nexport function leadQueryClause(\n clause: CrmViewFilterClause | ListFilterRequest,\n reader: LeadQueryReader = ORG_READER,\n): CrmClauseAsked {\n const values = crmClauseValues(clause)\n switch (clause.field) {\n case 'status': {\n const statuses: string[] = []\n for (const value of values) {\n if (value === 'open') statuses.push(...CRM_LEAD_OPEN_STATUSES)\n else if ((CRM_LEAD_STATUSES as readonly string[]).includes(value)) statuses.push(value)\n else return { refused: `${value} is not a lead status` }\n }\n return crmAnyOf('status', statuses)\n }\n case 'emailState': {\n const keys: string[] = []\n for (const value of values) {\n if (value === 'problem') keys.push(...EMAIL_PROBLEMS)\n else if (value === 'none') keys.push('none')\n else if ((EMAIL_STATE_STATUSES as readonly string[]).includes(value)) keys.push(value)\n else return { refused: `${value} is not an email verdict` }\n }\n return crmAnyOf('emailStatus', keys)\n }\n case 'leadSource': {\n if (clause.op === 'isEmpty' || (values.length === 1 && values[0] === LEAD_SOURCE_FILTER_NONE)) {\n return { field: 'leadSourceKey', op: 'isEmpty', value: '' }\n }\n if (values.includes(LEAD_SOURCE_FILTER_NONE)) {\n return { refused: '\"No lead source\" cannot be picked beside a value' }\n }\n const keys = values.map((value) => crmLeadSourceKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf('leadSourceKey', keys)\n }\n case 'leadSourceDirection': {\n for (const value of values) {\n if (!isLeadSourceDirection(value)) return { refused: `${value} is not a lead source direction` }\n }\n return crmAnyOf(CRM_LEAD_SOURCE_DIRECTION_FIELD, values)\n }\n case 'ownerUid':\n return crmAnyOf('ownerUid', values)\n case 'industry':\n case 'rating': {\n // A choice is the key; a label typed into a stored view keys the same way.\n const keys = values.map((value) => crmPicklistKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf(clause.field === 'industry' ? 'industryKey' : 'ratingKey', keys)\n }\n case 'campaignIds': {\n if (values.length !== 1) return { refused: 'pick one campaign' }\n if (!reader.scopeTokens) return { field: 'campaignIds', op: 'contains', value: values[0] }\n if (!reader.foldsScope) {\n return {\n refused:\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it',\n }\n }\n return {\n field: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n op: 'isAnyOf',\n value: reader.scopeTokens.map((token) => `${token}${SCOPED_SEARCH_JOIN}${values[0]}`).join(','),\n }\n }\n default:\n return { refused: 'this list does not filter by that' }\n }\n}\n\n/**\n * The query shape the Leads list can send beyond its own declaration, for\n * the index pin (AGL-3321): a collaborator's address prefix beside the\n * equalities they may set.\n */\nexport function leadIndexShapes(): ListQueryDeclaration[] {\n const equalities = LEAD_QUERY_FIELDS.filter((field) => !field.tokensPath)\n return [{ fields: [LEAD_PREFIX_SEARCH.field, ...equalities], sorts: [] }]\n}\n"],"names":["CRM_LEAD_STATUS_LABELS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","CRM_LEAD_SOURCE_DIRECTION_FIELD","CRM_LEAD_STATUSES","crmLeadSourceKey","crmPicklistKey","EMAIL_STATE_LABELS","EMAIL_STATE_STATUSES","SCOPED_SEARCH_JOIN","listSelectCodec","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","crmSelectField","isLeadSourceDirection","LEAD_SOURCE_DIRECTION_LABELS","LEAD_SOURCE_DIRECTIONS","LEAD_FILTERS","LEAD_FILTER_LABELS","open","all","new","nurturing","working","qualified","unqualified","LEAD_EMAIL_FILTERS","LEAD_EMAIL_FILTER_LABELS","any","problem","none","LEAD_SOURCE_FILTER_NONE","LEAD_LIST_FILTER_FIELDS","column","kind","path","operators","LEAD_PICKLIST_FILTERS","picklistId","header","LEAD_LIST_FILTER_HEADERS","status","emailState","ownerUid","leadSource","leadSourceDirection","campaignIds","industry","rating","LEAD_STATUS_FILTER_OPTIONS","filter","option","map","value","label","LEAD_EMAIL_FILTER_OPTIONS","LEAD_SOURCE_DIRECTION_FILTER_OPTIONS","direction","leadStatusFilterOptions","picklist","named","labels","values","meaning","active","picklistMeaningName","length","join","STATUS_OPEN","field","op","STATUS_ALL","leadClausesForGrid","stored","find","clause","leadClausesToStore","shown","LEAD_CAMPAIGN_CODEC","toItem","operator","toClause","item","LEAD_SOURCE_CODEC","LEAD_FILTER_CODECS","LEAD_LIST_SORTS","LEAD_QUERY_FIELDS","presence","tokensPath","LEAD_LIST_DECLARATION","fields","sorts","search","LEAD_PREFIX_SEARCH","lowerPath","notice","leadClauseImpliesScope","EMAIL_PROBLEMS","ORG_READER","scopeTokens","foldsScope","leadQueryClause","reader","statuses","push","includes","refused","keys","key","Boolean","token","leadIndexShapes","equalities"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,sBAAsB,EACtBC,sBAAsB,EACtBC,+BAA+B,EAC/BC,+BAA+B,EAC/BC,iBAAiB,EAGjBC,gBAAgB,EAChBC,cAAc,QACT,6BAA4B;AACnC,SAASC,kBAAkB,EAAEC,oBAAoB,QAA+B,qCAAoC;AAEpH,SAASC,kBAAkB,QAAQ,qCAAoC;AAEvE,SAAmCC,eAAe,QAAQ,yDAAwD;AAElH,SACEC,eAAe,EAEfC,QAAQ,EACRC,eAAe,EACfC,cAAc,QACT,sBAAkB;AACzB,SACEC,qBAAqB,EACrBC,4BAA4B,EAC5BC,sBAAsB,QACjB,6BAAyB;AAYhC,OAAO,MAAMC,eAAsC;IACjD;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,OAAO,MAAMC,qBAAiD;IAC5DC,MAAM;IACNC,KAAK;IACLC,KAAKtB,uBAAuBsB,GAAG;IAC/BC,WAAWvB,uBAAuBuB,SAAS;IAC3CC,SAASxB,uBAAuBwB,OAAO;IACvCC,WAAWzB,uBAAuByB,SAAS;IAC3CC,aAAa1B,uBAAuB0B,WAAW;AACjD,EAAC;AAWD,OAAO,MAAMC,qBAAiD;IAC5D;IACA;OACGnB;IACH;CACD,CAAA;AAED,OAAO,MAAMoB,2BAA4D;IACvEC,KAAK;IACLC,SAAS;IACTC,MAAM;GACHxB,oBACJ;AAED;;;;CAIC,GACD,OAAO,MAAMyB,0BAA0B,aAAY;AAEnD;;;;;;;;;;;;;;;;;;2CAkB2C,GAE3C,iDAAiD,GACjD,OAAO,MAAMC,0BAAsD;IACjE;QAAEC,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAUC,WAAW;YAAC;YAAU;SAAU;IAAC;IACpF;QAAEH,QAAQ;QAAcC,MAAM;QAASC,MAAM;QAAcC,WAAW;YAAC;YAAU;SAAU;IAAC;IAC5F;QAAEH,QAAQ;QAAYC,MAAM;QAASC,MAAM;QAAYC,WAAW;YAAC;YAAU;SAAU;IAAC;IACxF;QAAEH,QAAQ;QAAcC,MAAM;QAASC,MAAM;QAAcC,WAAW;YAAC;YAAU;SAAU;IAAC;IAC5F;QACEH,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNC,WAAW;YAAC;YAAU;SAAU;IAClC;IACA;QAAEH,QAAQ;QAAeC,MAAM;QAASC,MAAM;QAAeC,WAAW;YAAC;SAAS;IAAC;IACnF,yEAAyE;IACzE;QAAEH,QAAQ;QAAYC,MAAM;QAASC,MAAM;QAAYC,WAAW;YAAC;YAAU;SAAU;IAAC;IACxF;QAAEH,QAAQ;QAAUC,MAAM;QAASC,MAAM;QAAUC,WAAW;YAAC;YAAU;SAAU;IAAC;CACrF,CAAA;AAED;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB;IACnC;QAAEJ,QAAQ;QAAYK,YAAY;QAAYC,QAAQ;IAAW;IACjE;QAAEN,QAAQ;QAAUK,YAAY;QAAUC,QAAQ;IAAS;CAC5D,CAAS;AAEV,+CAA+C,GAC/C,OAAO,MAAMC,2BAA6D;IACxEC,QAAQ;IACRC,YAAY;IACZC,UAAU;IACVC,YAAY;IACZC,qBAAqB;IACrBC,aAAa;IACbC,UAAU;IACVC,QAAQ;AACV,EAAC;AAED,yFAAyF,GACzF,OAAO,MAAMC,6BAA6BhC,aAAaiC,MAAM,CAC3D,CAACC,SAAWA,WAAW,OACvBC,GAAG,CAAC,CAACD,SAAY,CAAA;QACjBE,OAAOF;QACPG,OAAOH,WAAW,SAAS,qCAAqCjC,kBAAkB,CAACiC,OAAO;IAC5F,CAAA,GAAG;AAEH,mEAAmE,GACnE,OAAO,MAAMI,4BAA4B7B,mBAAmBwB,MAAM,CAChE,CAACC,SAAWA,WAAW,OACvBC,GAAG,CAAC,CAACD,SAAY,CAAA;QAAEE,OAAOF;QAAQG,OAAO3B,wBAAwB,CAACwB,OAAO;IAAC,CAAA,GAAG;AAE/E,mFAAmF,GACnF,OAAO,MAAMK,uCAAuCxC,uBAAuBoC,GAAG,CAAC,CAACK,YAAe,CAAA;QAC7FJ,OAAOI;QACPH,OAAOvC,4BAA4B,CAAC0C,UAAU;IAChD,CAAA,GAAG;AAEH;;;;;CAKC,GACD,OAAO,SAASC,wBACdC,QAAqB;IAErB,MAAMC,QAAQ,CAACnB;QACb,MAAMoB,SAASF,SAASG,MAAM,CAC3BZ,MAAM,CAAC,CAACG,QAAUA,MAAMU,OAAO,KAAKtB,UAAUY,MAAMW,MAAM,EAC1DZ,GAAG,CAAC,CAACC,QAAUA,MAAMC,KAAK;QAC7B,MAAMS,UAAUE,oBAAoBxB;QACpC,IAAI,CAACoB,OAAOK,MAAM,EAAE,OAAOH;QAC3B,IAAIF,OAAOK,MAAM,KAAK,KAAKL,MAAM,CAAC,EAAE,KAAKE,SAAS,OAAOA;QACzD,OAAO,GAAGA,QAAQ,EAAE,EAAEF,OAAOM,IAAI,CAAC,OAAO;IAC3C;IACA,OAAOlD,aAAaiC,MAAM,CAAC,CAACC,SAAWA,WAAW,OAAOC,GAAG,CAAC,CAACD,SAAY,CAAA;YACxEE,OAAOF;YACPG,OAAOH,WAAW,SAAS,qCAAqCS,MAAMT;QACxE,CAAA;AACF;AAEA,MAAMc,sBAAsB,CAACxB,SAAkC1C,sBAAsB,CAAC0C,OAAO;AAE7F,MAAM2B,cAAmC;IAAEC,OAAO;IAAUC,IAAI;IAAUjB,OAAO;AAAO;AACxF,MAAMkB,aAAkC;IAAEF,OAAO;IAAUC,IAAI;IAAUjB,OAAO;AAAM;AAEtF;;;;;CAKC,GACD,OAAO,SAASmB,mBACdC,MAAsC;IAEtC,MAAMhC,SAASgC,OAAOC,IAAI,CAAC,CAACC,SAAWA,OAAON,KAAK,KAAK;IACxD,IAAI,CAAC5B,QAAQ,OAAO;QAAC2B;WAAgBK;KAAO;IAC5C,IAAIhC,OAAO6B,EAAE,KAAK,YAAY7B,OAAOY,KAAK,KAAK,OAAO;QACpD,OAAOoB,OAAOvB,MAAM,CAAC,CAACyB,SAAWA,OAAON,KAAK,KAAK;IACpD;IACA,OAAO;WAAII;KAAO;AACpB;AAEA,qEAAqE,GACrE,OAAO,SAASG,mBACdC,KAAqC;IAErC,MAAMpC,SAASoC,MAAMH,IAAI,CAAC,CAACC,SAAWA,OAAON,KAAK,KAAK;IACvD,IAAI,CAAC5B,QAAQ,OAAO;WAAIoC;QAAON;KAAW;IAC1C,IAAI9B,OAAO6B,EAAE,KAAK,YAAY7B,OAAOY,KAAK,KAAK,QAAQ;QACrD,OAAOwB,MAAM3B,MAAM,CAAC,CAACyB,SAAWA,OAAON,KAAK,KAAK;IACnD;IACA,OAAO;WAAIQ;KAAM;AACnB;AAEA,8EAA8E,GAC9E,OAAO,MAAMC,sBAA2C;IACtDC,QAAQ,CAACJ,SACPA,OAAOL,EAAE,KAAK,cAAcK,OAAOL,EAAE,KAAK,WACtC;YAAEU,UAAU;YAAM3B,OAAOsB,OAAOtB,KAAK;QAAC,IACtC;IACN4B,UAAU,CAACC;QACT,MAAMP,SAASlE,gBAAgBwE,QAAQ,CAACC;QACxC,OAAOP,UAAUA,OAAOL,EAAE,KAAK,WAAW,aAAKK;YAAQL,IAAI;aAAe;IAC5E;AACF,EAAC;AAED,8EAA8E,GAC9E,OAAO,MAAMa,oBAAyC;IACpDJ,QAAQ,CAACJ,SACPA,OAAOL,EAAE,KAAK,YACV;YAAEU,UAAU;YAAM3B,OAAOtB;QAAwB,IACjDtB,gBAAgBsE,MAAM,CAACJ;IAC7BM,UAAU,CAACC;QACT,MAAMP,SAASlE,gBAAgBwE,QAAQ,CAACC;QACxC,IAAIP,CAAAA,0BAAAA,OAAQL,EAAE,MAAK,YAAYK,OAAOtB,KAAK,KAAKtB,yBAAyB;YACvE,OAAO;gBAAEsC,OAAOM,OAAON,KAAK;gBAAEC,IAAI;gBAAWjB,OAAO;YAAG;QACzD;QACA,OAAOsB;IACT;AACF,EAAC;AAED,mEAAmE,GACnE,OAAO,MAAMS,qBAAoE;IAC/EtC,aAAagC;IACblC,YAAYuC;AACd,EAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAgC2C,GAE3C,2DAA2D,GAC3D,OAAO,MAAME,kBAA4C;IAAC;QAAElD,MAAM;QAAgBsB,WAAW;IAAO;CAAE,CAAA;AAEtG,qEAAqE,GACrE,OAAO,MAAM6B,oBAAgD;IAC3DzE,eAAe;IACfA,eAAe;IACfA,eAAe;IACf;QACEoB,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNoD,UAAU;QACVnD,WAAW;YAAC;YAAU;YAAW;SAAU;IAC7C;IACA;QACEH,QAAQ;QACRC,MAAM;QACNC,MAAM;QACNqD,YAAY;QACZpD,WAAW;YAAC;SAAW;IACzB;IACA,8DAA8D;IAC9DvB,eAAeX;IACf,iEAAiE;IACjEW,eAAe;IACfA,eAAe;IACf;QACE,kEAAkE;QAClEoB,QAAQhC;QACRiC,MAAM;QACNC,MAAMlC;QACNuF,YAAYvF;QACZmC,WAAW;YAAC;SAAU;IACxB;CACD,CAAA;AAED,OAAO,MAAMqD,wBAA8C;IACzDC,QAAQJ;IACRK,OAAON;IACPO,QAAQlF;AACV,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMmF,qBAAqB;IAChCxB,OAAO;QACLpC,QAAQ;QACRC,MAAM;QACNC,MAAM;QACN2D,WAAW;QACXP,UAAU;QACVnD,WAAW;YAAC;SAAa;IAC3B;IACA2D,QAAQ;AACV,EAAC;AAED,gGAAgG,GAChG,OAAO,MAAMC,yBAAyB,CAACrB,SACrCA,OAAON,KAAK,KAAKpE,gCAA+B;AAElD,iEAAiE,GACjE,MAAMgG,iBAAiB1F,qBAAqB2C,MAAM,CAAC,CAACT,SAAWA,WAAW;AAU1E,MAAMyD,aAA8B;IAAEC,aAAa;IAAMC,YAAY;AAAK;AAE1E;;;;CAIC,GACD,OAAO,SAASC,gBACd1B,MAA+C,EAC/C2B,SAA0BJ,UAAU;IAEpC,MAAMpC,SAASlD,gBAAgB+D;IAC/B,OAAQA,OAAON,KAAK;QAClB,KAAK;YAAU;gBACb,MAAMkC,WAAqB,EAAE;gBAC7B,KAAK,MAAMlD,SAASS,OAAQ;oBAC1B,IAAIT,UAAU,QAAQkD,SAASC,IAAI,IAAIxG;yBAClC,IAAI,AAACG,kBAAwCsG,QAAQ,CAACpD,QAAQkD,SAASC,IAAI,CAACnD;yBAC5E,OAAO;wBAAEqD,SAAS,GAAGrD,MAAM,qBAAqB,CAAC;oBAAC;gBACzD;gBACA,OAAO1C,SAAS,UAAU4F;YAC5B;QACA,KAAK;YAAc;gBACjB,MAAMI,OAAiB,EAAE;gBACzB,KAAK,MAAMtD,SAASS,OAAQ;oBAC1B,IAAIT,UAAU,WAAWsD,KAAKH,IAAI,IAAIP;yBACjC,IAAI5C,UAAU,QAAQsD,KAAKH,IAAI,CAAC;yBAChC,IAAI,AAACjG,qBAA2CkG,QAAQ,CAACpD,QAAQsD,KAAKH,IAAI,CAACnD;yBAC3E,OAAO;wBAAEqD,SAAS,GAAGrD,MAAM,wBAAwB,CAAC;oBAAC;gBAC5D;gBACA,OAAO1C,SAAS,eAAegG;YACjC;QACA,KAAK;YAAc;gBACjB,IAAIhC,OAAOL,EAAE,KAAK,aAAcR,OAAOI,MAAM,KAAK,KAAKJ,MAAM,CAAC,EAAE,KAAK/B,yBAA0B;oBAC7F,OAAO;wBAAEsC,OAAO;wBAAiBC,IAAI;wBAAWjB,OAAO;oBAAG;gBAC5D;gBACA,IAAIS,OAAO2C,QAAQ,CAAC1E,0BAA0B;oBAC5C,OAAO;wBAAE2E,SAAS;oBAAmD;gBACvE;gBACA,MAAMC,OAAO7C,OAAOV,GAAG,CAAC,CAACC,QAAUjD,iBAAiBiD,QAAQH,MAAM,CAAC,CAAC0D,MAAuBC,QAAQD;gBACnG,OAAOjG,SAAS,iBAAiBgG;YACnC;QACA,KAAK;YAAuB;gBAC1B,KAAK,MAAMtD,SAASS,OAAQ;oBAC1B,IAAI,CAAChD,sBAAsBuC,QAAQ,OAAO;wBAAEqD,SAAS,GAAGrD,MAAM,+BAA+B,CAAC;oBAAC;gBACjG;gBACA,OAAO1C,SAAST,iCAAiC4D;YACnD;QACA,KAAK;YACH,OAAOnD,SAAS,YAAYmD;QAC9B,KAAK;QACL,KAAK;YAAU;gBACb,2EAA2E;gBAC3E,MAAM6C,OAAO7C,OAAOV,GAAG,CAAC,CAACC,QAAUhD,eAAegD,QAAQH,MAAM,CAAC,CAAC0D,MAAuBC,QAAQD;gBACjG,OAAOjG,SAASgE,OAAON,KAAK,KAAK,aAAa,gBAAgB,aAAasC;YAC7E;QACA,KAAK;YAAe;gBAClB,IAAI7C,OAAOI,MAAM,KAAK,GAAG,OAAO;oBAAEwC,SAAS;gBAAoB;gBAC/D,IAAI,CAACJ,OAAOH,WAAW,EAAE,OAAO;oBAAE9B,OAAO;oBAAeC,IAAI;oBAAYjB,OAAOS,MAAM,CAAC,EAAE;gBAAC;gBACzF,IAAI,CAACwC,OAAOF,UAAU,EAAE;oBACtB,OAAO;wBACLM,SACE;oBACJ;gBACF;gBACA,OAAO;oBACLrC,OAAOpE;oBACPqE,IAAI;oBACJjB,OAAOiD,OAAOH,WAAW,CAAC/C,GAAG,CAAC,CAAC0D,QAAU,GAAGA,QAAQtG,qBAAqBsD,MAAM,CAAC,EAAE,EAAE,EAAEK,IAAI,CAAC;gBAC7F;YACF;QACA;YACE,OAAO;gBAAEuC,SAAS;YAAoC;IAC1D;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASK;IACd,MAAMC,aAAa1B,kBAAkBpC,MAAM,CAAC,CAACmB,QAAU,CAACA,MAAMmB,UAAU;IACxE,OAAO;QAAC;YAAEE,QAAQ;gBAACG,mBAAmBxB,KAAK;mBAAK2C;aAAW;YAAErB,OAAO,EAAE;QAAC;KAAE;AAC3E"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n CRM_LEAD_SOURCE_DIRECTION_FIELD,\n CRM_LEAD_STATUSES,\n type CrmLeadStatus,\n type CrmPicklist,\n crmLeadSourceKey,\n crmPicklistKey,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, type EmailStateStatus } from '@aglyn/aglyn/app-utils/email-state'\nimport type { CrmViewFilterClause } from '@aglyn/aglyn/app-utils/crm'\nimport { SCOPED_SEARCH_JOIN } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-util-tools/list-query/list-filter'\nimport { type ListGridFilterCodec, listSelectCodec } from '@aglyn/shared-util-tools/list-query/list-filter-codecs'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQuerySort,\n} from '@aglyn/shared-util-tools/list-query/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n crmSelectField,\n} from './crm-list-query'\nimport {\n isLeadSourceDirection,\n LEAD_SOURCE_DIRECTION_LABELS,\n LEAD_SOURCE_DIRECTIONS,\n} from './lead-source-direction'\n\n/**\n * What the Leads section's `Show` control offers (AGL-2608): the two\n * aggregate views and each status on its own. `open` is the default — the\n * list opens on the work — asked of the query as\n * `status in [new, nurturing, working]`,\n * which every lead answers because every lead writer stores a status, `new`\n * for one nobody has touched (`crmLeadListFields`, AGL-3321).\n */\nexport type LeadFilter = 'open' | 'all' | CrmLeadStatus\n\nexport const LEAD_FILTERS: readonly LeadFilter[] = [\n 'open',\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n 'all',\n]\n\nexport const LEAD_FILTER_LABELS: Record<LeadFilter, string> = {\n open: 'Open',\n all: 'All',\n new: CRM_LEAD_STATUS_LABELS.new,\n nurturing: CRM_LEAD_STATUS_LABELS.nurturing,\n working: CRM_LEAD_STATUS_LABELS.working,\n qualified: CRM_LEAD_STATUS_LABELS.qualified,\n unqualified: CRM_LEAD_STATUS_LABELS.unqualified,\n}\n\n/**\n * The Leads section's `Email` control (AGL-3245): every lead, the ones a\n * member must not email — any verdict but `ok` — the ones nothing has been\n * said about, or one verdict on its own. Beside `Show`, not inside it: a\n * bounce does not move a lead out of Open, and a queue of bounced leads is\n * a queue of people to reach some other way.\n */\nexport type LeadEmailFilter = 'any' | 'problem' | 'none' | EmailStateStatus\n\nexport const LEAD_EMAIL_FILTERS: readonly LeadEmailFilter[] = [\n 'any',\n 'problem',\n ...EMAIL_STATE_STATUSES,\n 'none',\n]\n\nexport const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string> = {\n any: 'Any',\n problem: 'Cannot be emailed',\n none: 'Nothing known',\n ...EMAIL_STATE_LABELS,\n}\n\n/**\n * The `Lead source` control's \"no lead source\" choice (AGL-3298). Held in\n * the saved view as an `isEmpty` clause, never as this string; it is only\n * the select's own value for the choice.\n */\nexport const LEAD_SOURCE_FILTER_NONE = '\\u0000none'\n\n/*==========================================\n * THE LEADS GRID'S FILTERS (AGL-3313).\n *\n * The Show, Email, Campaign and Lead source dropdowns became columns of the\n * grid's own Filters panel. Their clauses keep the shape the dropdowns\n * stored, so every saved view of leads reads as it did:\n *\n * status `equals <filter>`; none at all is Open, `all` is every lead\n * emailState `equals <filter>`\n * campaignIds `contains <campaign id>`\n * leadSource `equals <label>`, or `isEmpty` for the leads holding none\n * leadSourceDirection `equals inbound|outbound` (AGL-3511): every lead\n * whose lead source is a value of that group\n * industry, rating `equals <key>` (AGL-3513): a value of the org's\n * Industry or Rating list, by the key the query compares\n *\n * Every one is asked of the Leads query (AGL-3321) through the field a\n * writer stores for it — see {@link leadQueryClause}.\n *=========================================*/\n\n/** The fields the grid's panel offers on Leads. */\nexport const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals', 'isAnyOf'] },\n { column: 'emailState', kind: 'exact', path: 'emailState', operators: ['equals', 'isAnyOf'] },\n { column: 'ownerUid', kind: 'exact', path: 'ownerUid', operators: ['equals', 'isAnyOf'] },\n { column: 'leadSource', kind: 'exact', path: 'leadSource', operators: ['equals', 'isAnyOf'] },\n {\n column: 'leadSourceDirection',\n kind: 'exact',\n path: 'leadSourceDirection',\n operators: ['equals', 'isAnyOf'],\n },\n { column: 'campaignIds', kind: 'exact', path: 'campaignIds', operators: ['equals'] },\n // Salesforce's Industry and Rating (AGL-3513), the lists companies keep.\n { column: 'industry', kind: 'exact', path: 'industry', operators: ['equals', 'isAnyOf'] },\n { column: 'rating', kind: 'exact', path: 'rating', operators: ['equals', 'isAnyOf'] },\n]\n\n/**\n * The lead picklist columns the list filters by (AGL-3513), each with the\n * list its choices come from: a choice's value is the KEY the query\n * compares, its caption the label.\n */\nexport const LEAD_PICKLIST_FILTERS = [\n { column: 'industry', picklistId: 'industry', header: 'Industry' },\n { column: 'rating', picklistId: 'rating', header: 'Rating' },\n] as const\n\n/** What each filter field reads as on a chip. */\nexport const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n emailState: 'Email',\n ownerUid: 'Owner',\n leadSource: 'Lead source',\n leadSourceDirection: 'Lead source direction',\n campaignIds: 'Campaign',\n industry: 'Industry',\n rating: 'Rating',\n}\n\n/** The choices for Status: Open (new, nurturing or working) and each status on its own. */\nexport const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter(\n (option) => option !== 'all',\n).map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : LEAD_FILTER_LABELS[option],\n}))\n\n/** The choices for Email: every verdict, plus the two aggregates. */\nexport const LEAD_EMAIL_FILTER_OPTIONS = LEAD_EMAIL_FILTERS.filter(\n (option) => option !== 'any',\n).map((option) => ({ value: option, label: LEAD_EMAIL_FILTER_LABELS[option] }))\n\n/** The choices for Lead source direction: each group of the Lead source picklist. */\nexport const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS = LEAD_SOURCE_DIRECTIONS.map((direction) => ({\n value: direction,\n label: LEAD_SOURCE_DIRECTION_LABELS[direction],\n}))\n\n/**\n * The Status choices as an org reads them (AGL-3512): still one choice per\n * MEANING — the query asks `status` — each named by the org's values of that\n * meaning, so a list that keeps \"Contacted\" and \"Meeting set\" as Working\n * reads \"Working: Contacted, Meeting set\".\n */\nexport function leadStatusFilterOptions(\n picklist: CrmPicklist,\n): Array<{ value: string; label: string }> {\n const named = (status: CrmLeadStatus): string => {\n const labels = picklist.values\n .filter((value) => value.meaning === status && value.active)\n .map((value) => value.label)\n const meaning = picklistMeaningName(status)\n if (!labels.length) return meaning\n if (labels.length === 1 && labels[0] === meaning) return meaning\n return `${meaning}: ${labels.join(', ')}`\n }\n return LEAD_FILTERS.filter((option) => option !== 'all').map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : named(option),\n }))\n}\n\nconst picklistMeaningName = (status: CrmLeadStatus): string => CRM_LEAD_STATUS_LABELS[status]\n\nconst STATUS_OPEN: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'open' }\nconst STATUS_ALL: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'all' }\n\n/**\n * The clauses the grid shows for a view's stored ones. A stored list with\n * no status clause is Open, which the list opened on before views existed,\n * so it is shown as the clause it means; `status equals all` is every lead\n * and so no clause at all.\n */\nexport function leadClausesForGrid(\n stored: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = stored.find((clause) => clause.field === 'status')\n if (!status) return [STATUS_OPEN, ...stored]\n if (status.op === 'equals' && status.value === 'all') {\n return stored.filter((clause) => clause.field !== 'status')\n }\n return [...stored]\n}\n\n/** The inverse of {@link leadClausesForGrid}: what the view stores. */\nexport function leadClausesToStore(\n shown: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = shown.find((clause) => clause.field === 'status')\n if (!status) return [...shown, STATUS_ALL]\n if (status.op === 'equals' && status.value === 'open') {\n return shown.filter((clause) => clause.field !== 'status')\n }\n return [...shown]\n}\n\n/** Campaign clauses were stored as `contains`; the panel's select says `is`. */\nexport const LEAD_CAMPAIGN_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'contains' || clause.op === 'equals'\n ? { operator: 'is', value: clause.value }\n : null,\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n return clause && clause.op === 'equals' ? { ...clause, op: 'contains' } : null\n },\n}\n\n/** \"No lead source\" is an `isEmpty` clause, shown as a choice of the select. */\nexport const LEAD_SOURCE_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'isEmpty'\n ? { operator: 'is', value: LEAD_SOURCE_FILTER_NONE }\n : listSelectCodec.toItem(clause),\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n if (clause?.op === 'equals' && clause.value === LEAD_SOURCE_FILTER_NONE) {\n return { field: clause.field, op: 'isEmpty', value: '' }\n }\n return clause\n },\n}\n\n/** The Leads fields whose stored clauses translate their own way. */\nexport const LEAD_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>> = {\n campaignIds: LEAD_CAMPAIGN_CODEC,\n leadSource: LEAD_SOURCE_CODEC,\n}\n\n/*==========================================\n * THE LEADS QUERY (AGL-3321).\n *\n * `orgs/{orgId}/leads`, newest seen first, under a site narrowed by the\n * `visibleTo` scope clause. Each grid clause is asked through a field every\n * lead writer stores (`crmLeadListFields` in `@aglyn/aglyn`):\n *\n * status `status`, stored `new` on a lead nobody has touched, so\n * Open is `status in [new, nurturing, working]`\n * emailState `emailStatus`, the verdict's status or `none`; \"Cannot be\n * emailed\" is every verdict but `ok`\n * leadSource `leadSourceKey`, the label as the picklist compares it, or\n * `null` for none\n * leadSourceDirection `leadSourceDirection` (AGL-3577), the group of\n * the lead's value in the org's list, stamped by the writers\n * from that list and rewritten on every lead holding a value\n * the list regroups — so one equality asks a group however\n * many values it holds\n * ownerUid as stored\n * industry, rating `industryKey`, `ratingKey` (AGL-3513), each label\n * as the picklist compares it\n * campaignIds `array-contains` on the lead's own campaigns; under a site,\n * `scopedCampaignIds` — the campaign behind each of the\n * site's scope tokens — which stands in the scope clause's\n * place, so the one array clause answers both\n *\n * and the search box reads `searchTokens` — word prefixes of the name,\n * address, company, title and tags. Every equality rides one\n * `(field, lastSeenAtMs DESC)` composite; the campaign and the search are\n * array clauses, so the two do not stand together (nor under a site, where\n * the scope clause is the query's one array clause, save the search, which\n * folds into it for a reader who may drop it).\n *=========================================*/\n\n/**\n * The Leads list's orders: newest seen first, then each header the query can\n * order by (AGL-3680) — Last seen the other way, the lead's name or address\n * (`nameSortKey`), its company and title (`companyLower`, `jobTitleLower`)\n * and its address verdict (`emailStatus`), each stored on every lead. Each\n * is `alone`: served with no filter or search on beyond the Status the list\n * always asks (`leadStatusBase`), by one `(visibleTo, field)` and one\n * `(status, field)` composite per direction. The other columns — Status and\n * Lead source, Industry and Rating in the org's picklist order, the owner's\n * and campaigns' names, the sites, sources, tags and custom values — sort\n * the page.\n */\nexport const LEAD_LIST_SORTS: readonly ListQuerySort[] = [\n { path: 'lastSeenAtMs', direction: 'desc', column: 'lastSeenAtMs', label: 'Last seen' },\n { path: 'lastSeenAtMs', direction: 'asc', column: 'lastSeenAtMs', label: 'Last seen', alone: true },\n { path: 'nameSortKey', direction: 'asc', column: 'name', label: 'Lead', alone: true },\n { path: 'nameSortKey', direction: 'desc', column: 'name', label: 'Lead', alone: true },\n { path: 'companyLower', direction: 'asc', column: 'company', label: 'Company', alone: true },\n { path: 'companyLower', direction: 'desc', column: 'company', label: 'Company', alone: true },\n { path: 'jobTitleLower', direction: 'asc', column: 'jobTitle', label: 'Title', alone: true },\n { path: 'jobTitleLower', direction: 'desc', column: 'jobTitle', label: 'Title', alone: true },\n { path: 'emailStatus', direction: 'asc', column: 'emailState', label: 'Email', alone: true },\n { path: 'emailStatus', direction: 'desc', column: 'emailState', label: 'Email', alone: true },\n]\n\n/** The fields the Leads query asks — each a stored field, as named. */\nexport const LEAD_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n crmSelectField('emailStatus'),\n crmSelectField('ownerUid'),\n {\n column: 'leadSourceKey',\n kind: 'exact',\n path: 'leadSourceKey',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty'],\n },\n {\n column: 'campaignIds',\n kind: 'exact',\n path: 'campaignIds',\n tokensPath: 'campaignIds',\n operators: ['contains'],\n },\n // The lead source's group (AGL-3577), as stamped on the lead.\n crmSelectField(CRM_LEAD_SOURCE_DIRECTION_FIELD),\n // Industry and Rating (AGL-3513), by the key each writer stores.\n crmSelectField('industryKey'),\n crmSelectField('ratingKey'),\n {\n // A campaign under a site: the lead's campaigns behind its scope.\n column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n kind: 'exact',\n path: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n tokensPath: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n operators: ['isAnyOf'],\n },\n]\n\nexport const LEAD_LIST_DECLARATION: ListQueryDeclaration = {\n fields: LEAD_QUERY_FIELDS,\n sorts: LEAD_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the address, beside the scope clause — the `(visibleTo, email)`\n * composite the Inbox's leads list reads too.\n */\nexport const LEAD_PREFIX_SEARCH = {\n field: {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['startsWith'],\n } satisfies ListFilterField,\n notice: 'Search matches the start of a lead’s address for access limited to specific sites.',\n}\n\n/** Whether an asked clause stands in for the scope clause: a campaign behind the site's scope. */\nexport const leadClauseImpliesScope = (clause: ListFilterRequest): boolean =>\n clause.field === CRM_LEAD_SCOPED_CAMPAIGNS_FIELD\n\n/** The verdicts a member must not email to: every one but `ok`. */\nconst EMAIL_PROBLEMS = EMAIL_STATE_STATUSES.filter((status) => status !== 'ok')\n\n/** Who is asking, for the one clause whose shape depends on it: the Campaign. */\nexport interface LeadQueryReader {\n /** The site's scope tokens, or `null` at the organization level. */\n scopeTokens: readonly string[] | null\n /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */\n foldsScope: boolean\n}\n\nconst ORG_READER: LeadQueryReader = { scopeTokens: null, foldsScope: true }\n\n/**\n * One grid clause as the clause the Leads query asks — see the block above.\n * A value the list does not know is refused by name rather than dropped, so\n * a stored view that names a retired choice says so.\n */\nexport function leadQueryClause(\n clause: CrmViewFilterClause | ListFilterRequest,\n reader: LeadQueryReader = ORG_READER,\n): CrmClauseAsked {\n const values = crmClauseValues(clause)\n switch (clause.field) {\n case 'status': {\n const statuses: string[] = []\n for (const value of values) {\n if (value === 'open') statuses.push(...CRM_LEAD_OPEN_STATUSES)\n else if ((CRM_LEAD_STATUSES as readonly string[]).includes(value)) statuses.push(value)\n else return { refused: `${value} is not a lead status` }\n }\n return crmAnyOf('status', statuses)\n }\n case 'emailState': {\n const keys: string[] = []\n for (const value of values) {\n if (value === 'problem') keys.push(...EMAIL_PROBLEMS)\n else if (value === 'none') keys.push('none')\n else if ((EMAIL_STATE_STATUSES as readonly string[]).includes(value)) keys.push(value)\n else return { refused: `${value} is not an email verdict` }\n }\n return crmAnyOf('emailStatus', keys)\n }\n case 'leadSource': {\n if (clause.op === 'isEmpty' || (values.length === 1 && values[0] === LEAD_SOURCE_FILTER_NONE)) {\n return { field: 'leadSourceKey', op: 'isEmpty', value: '' }\n }\n if (values.includes(LEAD_SOURCE_FILTER_NONE)) {\n return { refused: '\"No lead source\" cannot be picked beside a value' }\n }\n const keys = values.map((value) => crmLeadSourceKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf('leadSourceKey', keys)\n }\n case 'leadSourceDirection': {\n for (const value of values) {\n if (!isLeadSourceDirection(value)) return { refused: `${value} is not a lead source direction` }\n }\n return crmAnyOf(CRM_LEAD_SOURCE_DIRECTION_FIELD, values)\n }\n case 'ownerUid':\n return crmAnyOf('ownerUid', values)\n case 'industry':\n case 'rating': {\n // A choice is the key; a label typed into a stored view keys the same way.\n const keys = values.map((value) => crmPicklistKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf(clause.field === 'industry' ? 'industryKey' : 'ratingKey', keys)\n }\n case 'campaignIds': {\n if (values.length !== 1) return { refused: 'pick one campaign' }\n if (!reader.scopeTokens) return { field: 'campaignIds', op: 'contains', value: values[0] }\n if (!reader.foldsScope) {\n return {\n refused:\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it',\n }\n }\n return {\n field: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n op: 'isAnyOf',\n value: reader.scopeTokens.map((token) => `${token}${SCOPED_SEARCH_JOIN}${values[0]}`).join(','),\n }\n }\n default:\n return { refused: 'this list does not filter by that' }\n }\n}\n\n/*------------------------------------------\n * THE STATUS IS THE LIST'S BASE (AGL-3680).\n *\n * The Leads list always asks a Status — Open unless the reader picks\n * another, or All — so a header order served only with no filter on (an\n * `alone` order) would never be served. The Status clause is therefore put\n * on the query as part of its BASE, beside the scope clause, the way a task\n * view's status is: an `alone` order pairs with it through one\n * `(status, field)` composite, and every other clause still counts as a\n * filter. The clause stays the reader's — the chip, the view and the\n * refusals read it as they always did.\n *-----------------------------------------*/\n\n/** The asked Status clause as the base predicate it stands for, or none. */\nexport function leadStatusBase(clause: ListFilterRequest | undefined): ListQueryFilter[] {\n if (!clause || clause.field !== 'status') return []\n const values = crmClauseValues(clause)\n if (!values.length) return []\n return values.length === 1\n ? [{ path: 'status', op: '==', value: values[0] }]\n : [{ path: 'status', op: 'in', value: values }]\n}\n\n/** The base's shape for `listQueryIndexes`: the status equality. */\nexport const LEAD_LIST_STATUS_BASE_INDEX = [{ path: 'status' }] as const\n\n/**\n * The query shape the Leads list can send beyond its own declaration, for\n * the index pin (AGL-3321): a collaborator's address prefix beside the\n * equalities they may set.\n */\nexport function leadIndexShapes(): ListQueryDeclaration[] {\n const equalities = LEAD_QUERY_FIELDS.filter((field) => !field.tokensPath)\n return [{ fields: [LEAD_PREFIX_SEARCH.field, ...equalities], sorts: [] }]\n}\n"],"names":["CRM_LEAD_STATUS_LABELS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","CRM_LEAD_SOURCE_DIRECTION_FIELD","CRM_LEAD_STATUSES","crmLeadSourceKey","crmPicklistKey","EMAIL_STATE_LABELS","EMAIL_STATE_STATUSES","SCOPED_SEARCH_JOIN","listSelectCodec","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","crmSelectField","isLeadSourceDirection","LEAD_SOURCE_DIRECTION_LABELS","LEAD_SOURCE_DIRECTIONS","LEAD_FILTERS","LEAD_FILTER_LABELS","open","all","new","nurturing","working","qualified","unqualified","LEAD_EMAIL_FILTERS","LEAD_EMAIL_FILTER_LABELS","any","problem","none","LEAD_SOURCE_FILTER_NONE","LEAD_LIST_FILTER_FIELDS","column","kind","path","operators","LEAD_PICKLIST_FILTERS","picklistId","header","LEAD_LIST_FILTER_HEADERS","status","emailState","ownerUid","leadSource","leadSourceDirection","campaignIds","industry","rating","LEAD_STATUS_FILTER_OPTIONS","filter","option","map","value","label","LEAD_EMAIL_FILTER_OPTIONS","LEAD_SOURCE_DIRECTION_FILTER_OPTIONS","direction","leadStatusFilterOptions","picklist","named","labels","values","meaning","active","picklistMeaningName","length","join","STATUS_OPEN","field","op","STATUS_ALL","leadClausesForGrid","stored","find","clause","leadClausesToStore","shown","LEAD_CAMPAIGN_CODEC","toItem","operator","toClause","item","LEAD_SOURCE_CODEC","LEAD_FILTER_CODECS","LEAD_LIST_SORTS","alone","LEAD_QUERY_FIELDS","presence","tokensPath","LEAD_LIST_DECLARATION","fields","sorts","search","LEAD_PREFIX_SEARCH","lowerPath","notice","leadClauseImpliesScope","EMAIL_PROBLEMS","ORG_READER","scopeTokens","foldsScope","leadQueryClause","reader","statuses","push","includes","refused","keys","key","Boolean","token","leadStatusBase","LEAD_LIST_STATUS_BASE_INDEX","leadIndexShapes","equalities"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** Every lead status, in the console's order. */
18
+ export declare const NATIVE_CRM_LEAD_STATUSES: string[];
19
+ /** The statuses a lead still needs working in. */
20
+ export declare const NATIVE_CRM_LEAD_OPEN_STATUSES: string[];
21
+ /** Every contact lifecycle stage, in order. */
22
+ export declare const NATIVE_CONTACT_LIFECYCLE_STAGES: string[];
23
+ /** Every task kind, in order. */
24
+ export declare const NATIVE_CRM_TASK_KINDS: string[];
25
+ /** Every activity kind, in order. */
26
+ export declare const NATIVE_CRM_ACTIVITY_KINDS: string[];
27
+ /** The objects custom fields are kept for. */
28
+ export declare const NATIVE_CRM_FIELD_OBJECTS: string[];
29
+ /** The report periods, in the console's order. */
30
+ export declare const NATIVE_CRM_REPORT_PERIODS: string[];
31
+ /** The directions an activity of each kind can take. */
32
+ export declare const NATIVE_CRM_ACTIVITY_DIRECTIONS: Record<string, string[]>;
33
+ /** One picklist as the native pickers start it: its id, label, object and standard labels. */
34
+ export interface NativeCrmPicklist {
35
+ id: string;
36
+ label: string;
37
+ object: string;
38
+ standardLabels: string[];
39
+ }
40
+ /** Every picklist the CRM keeps, with the labels a workspace starts with. */
41
+ export declare const NATIVE_CRM_PICKLISTS: NativeCrmPicklist[];
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*
17
+ * THE CRM'S VOCABULARIES, FLAT, FOR THE NATIVE APPS (AGL-3669).
18
+ *
19
+ * The console reads these off `as const` tuples and typed definitions,
20
+ * shapes the native contracts generator does not translate. Each value here
21
+ * is the same data re-read as plain arrays and maps; nothing in the web
22
+ * apps imports this file, so it adds nothing to their bundles.
23
+ */ import { CONTACT_LIFECYCLE_STAGES, CRM_ACTIVITY_DIRECTIONS, CRM_ACTIVITY_KINDS, CRM_FIELD_OBJECTS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_STATUSES, CRM_PICKLIST_DEFINITIONS, CRM_TASK_KINDS } from "@aglyn/aglyn/app-utils/crm";
24
+ import { CRM_REPORT_PERIODS } from "./crm-reports.js";
25
+ /** Every lead status, in the console's order. */ export const NATIVE_CRM_LEAD_STATUSES = [
26
+ ...CRM_LEAD_STATUSES
27
+ ];
28
+ /** The statuses a lead still needs working in. */ export const NATIVE_CRM_LEAD_OPEN_STATUSES = [
29
+ ...CRM_LEAD_OPEN_STATUSES
30
+ ];
31
+ /** Every contact lifecycle stage, in order. */ export const NATIVE_CONTACT_LIFECYCLE_STAGES = [
32
+ ...CONTACT_LIFECYCLE_STAGES
33
+ ];
34
+ /** Every task kind, in order. */ export const NATIVE_CRM_TASK_KINDS = [
35
+ ...CRM_TASK_KINDS
36
+ ];
37
+ /** Every activity kind, in order. */ export const NATIVE_CRM_ACTIVITY_KINDS = [
38
+ ...CRM_ACTIVITY_KINDS
39
+ ];
40
+ /** The objects custom fields are kept for. */ export const NATIVE_CRM_FIELD_OBJECTS = [
41
+ ...CRM_FIELD_OBJECTS
42
+ ];
43
+ /** The report periods, in the console's order. */ export const NATIVE_CRM_REPORT_PERIODS = [
44
+ ...CRM_REPORT_PERIODS
45
+ ];
46
+ /** The directions an activity of each kind can take. */ export const NATIVE_CRM_ACTIVITY_DIRECTIONS = Object.fromEntries(Object.entries(CRM_ACTIVITY_DIRECTIONS).map(([kind, directions])=>[
47
+ kind,
48
+ [
49
+ ...directions != null ? directions : []
50
+ ]
51
+ ]));
52
+ /** Every picklist the CRM keeps, with the labels a workspace starts with. */ export const NATIVE_CRM_PICKLISTS = CRM_PICKLIST_DEFINITIONS.map((definition)=>({
53
+ id: definition.id,
54
+ label: definition.label,
55
+ object: definition.object,
56
+ standardLabels: definition.standardValues.map((value)=>value.label)
57
+ }));
58
+
59
+ //# sourceMappingURL=native-vocabulary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/native-vocabulary.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * THE CRM'S VOCABULARIES, FLAT, FOR THE NATIVE APPS (AGL-3669).\n *\n * The console reads these off `as const` tuples and typed definitions,\n * shapes the native contracts generator does not translate. Each value here\n * is the same data re-read as plain arrays and maps; nothing in the web\n * apps imports this file, so it adds nothing to their bundles.\n */\n\nimport {\n CONTACT_LIFECYCLE_STAGES,\n CRM_ACTIVITY_DIRECTIONS,\n CRM_ACTIVITY_KINDS,\n CRM_FIELD_OBJECTS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_STATUSES,\n CRM_PICKLIST_DEFINITIONS,\n CRM_TASK_KINDS,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { CRM_REPORT_PERIODS } from './crm-reports'\n\n/** Every lead status, in the console's order. */\nexport const NATIVE_CRM_LEAD_STATUSES: string[] = [...CRM_LEAD_STATUSES]\n\n/** The statuses a lead still needs working in. */\nexport const NATIVE_CRM_LEAD_OPEN_STATUSES: string[] = [...CRM_LEAD_OPEN_STATUSES]\n\n/** Every contact lifecycle stage, in order. */\nexport const NATIVE_CONTACT_LIFECYCLE_STAGES: string[] = [...CONTACT_LIFECYCLE_STAGES]\n\n/** Every task kind, in order. */\nexport const NATIVE_CRM_TASK_KINDS: string[] = [...CRM_TASK_KINDS]\n\n/** Every activity kind, in order. */\nexport const NATIVE_CRM_ACTIVITY_KINDS: string[] = [...CRM_ACTIVITY_KINDS]\n\n/** The objects custom fields are kept for. */\nexport const NATIVE_CRM_FIELD_OBJECTS: string[] = [...CRM_FIELD_OBJECTS]\n\n/** The report periods, in the console's order. */\nexport const NATIVE_CRM_REPORT_PERIODS: string[] = [...CRM_REPORT_PERIODS]\n\n/** The directions an activity of each kind can take. */\nexport const NATIVE_CRM_ACTIVITY_DIRECTIONS: Record<string, string[]> = Object.fromEntries(\n Object.entries(CRM_ACTIVITY_DIRECTIONS).map(([kind, directions]) => [kind, [...(directions ?? [])]]),\n)\n\n/** One picklist as the native pickers start it: its id, label, object and standard labels. */\nexport interface NativeCrmPicklist {\n id: string\n label: string\n object: string\n standardLabels: string[]\n}\n\n/** Every picklist the CRM keeps, with the labels a workspace starts with. */\nexport const NATIVE_CRM_PICKLISTS: NativeCrmPicklist[] = CRM_PICKLIST_DEFINITIONS.map((definition) => ({\n id: definition.id,\n label: definition.label,\n object: definition.object,\n standardLabels: definition.standardValues.map((value) => value.label),\n}))\n"],"names":["CONTACT_LIFECYCLE_STAGES","CRM_ACTIVITY_DIRECTIONS","CRM_ACTIVITY_KINDS","CRM_FIELD_OBJECTS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_STATUSES","CRM_PICKLIST_DEFINITIONS","CRM_TASK_KINDS","CRM_REPORT_PERIODS","NATIVE_CRM_LEAD_STATUSES","NATIVE_CRM_LEAD_OPEN_STATUSES","NATIVE_CONTACT_LIFECYCLE_STAGES","NATIVE_CRM_TASK_KINDS","NATIVE_CRM_ACTIVITY_KINDS","NATIVE_CRM_FIELD_OBJECTS","NATIVE_CRM_REPORT_PERIODS","NATIVE_CRM_ACTIVITY_DIRECTIONS","Object","fromEntries","entries","map","kind","directions","NATIVE_CRM_PICKLISTS","definition","id","label","object","standardLabels","standardValues","value"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAED,SACEA,wBAAwB,EACxBC,uBAAuB,EACvBC,kBAAkB,EAClBC,iBAAiB,EACjBC,sBAAsB,EACtBC,iBAAiB,EACjBC,wBAAwB,EACxBC,cAAc,QACT,6BAA4B;AACnC,SAASC,kBAAkB,QAAQ,mBAAe;AAElD,+CAA+C,GAC/C,OAAO,MAAMC,2BAAqC;OAAIJ;CAAkB,CAAA;AAExE,gDAAgD,GAChD,OAAO,MAAMK,gCAA0C;OAAIN;CAAuB,CAAA;AAElF,6CAA6C,GAC7C,OAAO,MAAMO,kCAA4C;OAAIX;CAAyB,CAAA;AAEtF,+BAA+B,GAC/B,OAAO,MAAMY,wBAAkC;OAAIL;CAAe,CAAA;AAElE,mCAAmC,GACnC,OAAO,MAAMM,4BAAsC;OAAIX;CAAmB,CAAA;AAE1E,4CAA4C,GAC5C,OAAO,MAAMY,2BAAqC;OAAIX;CAAkB,CAAA;AAExE,gDAAgD,GAChD,OAAO,MAAMY,4BAAsC;OAAIP;CAAmB,CAAA;AAE1E,sDAAsD,GACtD,OAAO,MAAMQ,iCAA2DC,OAAOC,WAAW,CACxFD,OAAOE,OAAO,CAAClB,yBAAyBmB,GAAG,CAAC,CAAC,CAACC,MAAMC,WAAW,GAAK;QAACD;QAAM;eAAKC,qBAAAA,aAAc,EAAE;SAAE;KAAC,GACpG;AAUD,2EAA2E,GAC3E,OAAO,MAAMC,uBAA4CjB,yBAAyBc,GAAG,CAAC,CAACI,aAAgB,CAAA;QACrGC,IAAID,WAAWC,EAAE;QACjBC,OAAOF,WAAWE,KAAK;QACvBC,QAAQH,WAAWG,MAAM;QACzBC,gBAAgBJ,WAAWK,cAAc,CAACT,GAAG,CAAC,CAACU,QAAUA,MAAMJ,KAAK;IACtE,CAAA,GAAG"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type CrmTask, type CrmTaskKind, type CrmTaskPriority, type TaskDueState } from '@aglyn/aglyn';
17
+ import { type CrmTask, type CrmTaskKind, type CrmTaskPriority, type TaskDueState } from '@aglyn/aglyn/app-utils/crm';
18
18
  import type { ListFilterField } from '@aglyn/shared-util-tools/list-query/list-filter';
19
19
  import type { ListQueryDeclaration, ListQueryFilter, ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan';
20
20
  import type { CrmRoutes } from './crm-routes';
@@ -68,8 +68,21 @@ export declare function crmTaskViewPlan(view: CrmTaskView, options: {
68
68
  nowMs: number;
69
69
  uid: string | null | undefined;
70
70
  }): CrmTaskViewPlan;
71
- /** The orders the list takes: soonest first, and Done's most recently due first. */
71
+ /**
72
+ * The orders the list takes: soonest first, and Done's most recently due
73
+ * first — the Due header either way — then each header the query can order
74
+ * by (AGL-3680): the title (`titleLower`) and the priority by its rank
75
+ * (`priorityRank`), each stored on every task. Each
76
+ * is `alone`: served with no filter or search on beyond the view, beside the
77
+ * view's base by one `(status, field)`, `(assigneeUid, field)` and
78
+ * `(visibleTo, field)` composite per direction — and under a dated view
79
+ * (Overdue, Today, Upcoming), whose window orders the list by Due, not at
80
+ * all, which the list says. Type and Status (picklists, in the org's
81
+ * order), Assignee and For sort the page.
82
+ */
72
83
  export declare const TASK_LIST_SORTS: readonly ListQuerySort[];
84
+ /** The view's own order: the Due header, the way the view reads it. */
85
+ export declare function crmTaskViewSort(plan: Pick<CrmTaskViewPlan, 'direction'>): ListQuerySort;
73
86
  /** What the Filters panel may name on a task, besides the view. */
74
87
  export declare const TASK_LIST_QUERY_FIELDS: readonly ListFilterField[];
75
88
  export declare const TASK_LIST_DECLARATION: ListQueryDeclaration;
@@ -13,7 +13,7 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ import { taskDueState } from "@aglyn/aglyn";
16
+ */ import { taskDueState } from "@aglyn/aglyn/app-utils/crm";
17
17
  import { CRM_LIST_SEARCH, crmSelectField } from "./crm-list-query.js";
18
18
  export const CRM_TASK_VIEWS = [
19
19
  {
@@ -130,16 +130,62 @@ export function crmTaskViewPlan(view, options) {
130
130
  * same query, paged by it. Every equality rides one `(field, dueAtMs)`
131
131
  * composite per direction: ascending for the working views, descending for
132
132
  * Done.
133
- *=========================================*/ /** The orders the list takes: soonest first, and Done's most recently due first. */ export const TASK_LIST_SORTS = [
133
+ *=========================================*/ /**
134
+ * The orders the list takes: soonest first, and Done's most recently due
135
+ * first — the Due header either way — then each header the query can order
136
+ * by (AGL-3680): the title (`titleLower`) and the priority by its rank
137
+ * (`priorityRank`), each stored on every task. Each
138
+ * is `alone`: served with no filter or search on beyond the view, beside the
139
+ * view's base by one `(status, field)`, `(assigneeUid, field)` and
140
+ * `(visibleTo, field)` composite per direction — and under a dated view
141
+ * (Overdue, Today, Upcoming), whose window orders the list by Due, not at
142
+ * all, which the list says. Type and Status (picklists, in the org's
143
+ * order), Assignee and For sort the page.
144
+ */ export const TASK_LIST_SORTS = [
134
145
  {
135
146
  path: 'dueAtMs',
136
- direction: 'asc'
147
+ direction: 'asc',
148
+ column: 'dueAtMs',
149
+ label: 'Due'
137
150
  },
138
151
  {
139
152
  path: 'dueAtMs',
140
- direction: 'desc'
153
+ direction: 'desc',
154
+ column: 'dueAtMs',
155
+ label: 'Due'
156
+ },
157
+ {
158
+ path: 'titleLower',
159
+ direction: 'asc',
160
+ column: 'title',
161
+ label: 'Task',
162
+ alone: true
163
+ },
164
+ {
165
+ path: 'titleLower',
166
+ direction: 'desc',
167
+ column: 'title',
168
+ label: 'Task',
169
+ alone: true
170
+ },
171
+ {
172
+ path: 'priorityRank',
173
+ direction: 'asc',
174
+ column: 'priority',
175
+ label: 'Priority',
176
+ alone: true
177
+ },
178
+ {
179
+ path: 'priorityRank',
180
+ direction: 'desc',
181
+ column: 'priority',
182
+ label: 'Priority',
183
+ alone: true
141
184
  }
142
185
  ];
186
+ /** The view's own order: the Due header, the way the view reads it. */ export function crmTaskViewSort(plan) {
187
+ return TASK_LIST_SORTS.find((sort)=>sort.path === 'dueAtMs' && sort.direction === plan.direction);
188
+ }
143
189
  /** What the Filters panel may name on a task, besides the view. */ export const TASK_LIST_QUERY_FIELDS = [
144
190
  crmSelectField('kind'),
145
191
  crmSelectField('priority'),