@aglyn/plugins-crm 1.0.0-beta.224 → 1.0.0-beta.226
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.
- package/package.json +13 -13
- package/src/lib/components/lead-properties-card.js +6 -1
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/leads-section.js +2 -5
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/picklist-values-card.d.ts +5 -4
- package/src/lib/components/picklist-values-card.js +42 -6
- package/src/lib/components/picklist-values-card.js.map +1 -1
- package/src/lib/model/crm-list-query.d.ts +4 -3
- package/src/lib/model/crm-list-query.js +4 -3
- package/src/lib/model/crm-list-query.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +2 -7
- package/src/lib/model/lead-filters.js +10 -28
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/lead-source-direction.d.ts +3 -9
- package/src/lib/model/lead-source-direction.js +4 -18
- package/src/lib/model/lead-source-direction.js.map +1 -1
- package/src/lib/model/picklist-values.d.ts +25 -4
- package/src/lib/model/picklist-values.js +13 -4
- package/src/lib/model/picklist-values.js.map +1 -1
- package/src/lib/server/picklist-values.d.ts +14 -1
- package/src/lib/server/picklist-values.js +119 -20
- package/src/lib/server/picklist-values.js.map +1 -1
- package/src/lib/server/record-origin.js +6 -2
- package/src/lib/server/record-origin.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EMAIL_STATE_LABELS,\n EMAIL_STATE_STATUSES,\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n CRM_LEAD_STATUSES,\n type EmailStateStatus,\n type CrmLeadStatus,\n type CrmPicklist,\n crmLeadSourceKey,\n crmPicklistKey,\n} from '@aglyn/aglyn'\nimport type { CrmViewFilterClause } from '@aglyn/aglyn'\nimport { SCOPED_SEARCH_JOIN } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { type ListGridFilterCodec, listSelectCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n LIST_QUERY_DISJUNCTIONS,\n type ListQueryDeclaration,\n type ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n crmSelectField,\n} from './crm-list-query'\nimport {\n isLeadSourceDirection,\n LEAD_SOURCE_DIRECTION_LABELS,\n LEAD_SOURCE_DIRECTIONS,\n leadSourceLabelsOfDirection,\n STANDARD_LEAD_SOURCES,\n} from './lead-source-direction'\n\n/**\n * What the Leads section's `Show` control offers (AGL-2608): the two\n * aggregate views and each status on its own. `open` is the default — the\n * list opens on the work — asked of the query as\n * `status in [new, nurturing, working]`,\n * which every lead answers because every lead writer stores a status, `new`\n * for one nobody has touched (`crmLeadListFields`, AGL-3321).\n */\nexport type LeadFilter = 'open' | 'all' | CrmLeadStatus\n\nexport const LEAD_FILTERS: readonly LeadFilter[] = [\n 'open',\n 'new',\n 'nurturing',\n 'working',\n 'qualified',\n 'unqualified',\n 'all',\n]\n\nexport const LEAD_FILTER_LABELS: Record<LeadFilter, string> = {\n open: 'Open',\n all: 'All',\n new: CRM_LEAD_STATUS_LABELS.new,\n nurturing: CRM_LEAD_STATUS_LABELS.nurturing,\n working: CRM_LEAD_STATUS_LABELS.working,\n qualified: CRM_LEAD_STATUS_LABELS.qualified,\n unqualified: CRM_LEAD_STATUS_LABELS.unqualified,\n}\n\n/**\n * The Leads section's `Email` control (AGL-3245): every lead, the ones a\n * member must not email — any verdict but `ok` — the ones nothing has been\n * said about, or one verdict on its own. Beside `Show`, not inside it: a\n * bounce does not move a lead out of Open, and a queue of bounced leads is\n * a queue of people to reach some other way.\n */\nexport type LeadEmailFilter = 'any' | 'problem' | 'none' | EmailStateStatus\n\nexport const LEAD_EMAIL_FILTERS: readonly LeadEmailFilter[] = [\n 'any',\n 'problem',\n ...EMAIL_STATE_STATUSES,\n 'none',\n]\n\nexport const LEAD_EMAIL_FILTER_LABELS: Record<LeadEmailFilter, string> = {\n any: 'Any',\n problem: 'Cannot be emailed',\n none: 'Nothing known',\n ...EMAIL_STATE_LABELS,\n}\n\n/**\n * The `Lead source` control's \"no lead source\" choice (AGL-3298). Held in\n * the saved view as an `isEmpty` clause, never as this string; it is only\n * the select's own value for the choice.\n */\nexport const LEAD_SOURCE_FILTER_NONE = '\\u0000none'\n\n/*==========================================\n * THE LEADS GRID'S FILTERS (AGL-3313).\n *\n * The Show, Email, Campaign and Lead source dropdowns became columns of the\n * grid's own Filters panel. Their clauses keep the shape the dropdowns\n * stored, so every saved view of leads reads as it did:\n *\n * status `equals <filter>`; none at all is Open, `all` is every lead\n * emailState `equals <filter>`\n * campaignIds `contains <campaign id>`\n * leadSource `equals <label>`, or `isEmpty` for the leads holding none\n * leadSourceDirection `equals inbound|outbound` (AGL-3511): every lead\n * whose lead source is a value of that group\n * industry, rating `equals <key>` (AGL-3513): a value of the org's\n * Industry or Rating list, by the key the query compares\n *\n * Every one is asked of the Leads query (AGL-3321) through the field a\n * writer stores for it — see {@link leadQueryClause}.\n *=========================================*/\n\n/** The fields the grid's panel offers on Leads. */\nexport const LEAD_LIST_FILTER_FIELDS: readonly ListFilterField[] = [\n { column: 'status', kind: 'exact', path: 'status', operators: ['equals', 'isAnyOf'] },\n { column: 'emailState', kind: 'exact', path: 'emailState', operators: ['equals', 'isAnyOf'] },\n { column: 'ownerUid', kind: 'exact', path: 'ownerUid', operators: ['equals', 'isAnyOf'] },\n { column: 'leadSource', kind: 'exact', path: 'leadSource', operators: ['equals', 'isAnyOf'] },\n {\n column: 'leadSourceDirection',\n kind: 'exact',\n path: 'leadSourceDirection',\n operators: ['equals', 'isAnyOf'],\n },\n { column: 'campaignIds', kind: 'exact', path: 'campaignIds', operators: ['equals'] },\n // Salesforce's Industry and Rating (AGL-3513), the lists companies keep.\n { column: 'industry', kind: 'exact', path: 'industry', operators: ['equals', 'isAnyOf'] },\n { column: 'rating', kind: 'exact', path: 'rating', operators: ['equals', 'isAnyOf'] },\n]\n\n/**\n * The lead picklist columns the list filters by (AGL-3513), each with the\n * list its choices come from: a choice's value is the KEY the query\n * compares, its caption the label.\n */\nexport const LEAD_PICKLIST_FILTERS = [\n { column: 'industry', picklistId: 'industry', header: 'Industry' },\n { column: 'rating', picklistId: 'rating', header: 'Rating' },\n] as const\n\n/** What each filter field reads as on a chip. */\nexport const LEAD_LIST_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n emailState: 'Email',\n ownerUid: 'Owner',\n leadSource: 'Lead source',\n leadSourceDirection: 'Lead source direction',\n campaignIds: 'Campaign',\n industry: 'Industry',\n rating: 'Rating',\n}\n\n/** The choices for Status: Open (new, nurturing or working) and each status on its own. */\nexport const LEAD_STATUS_FILTER_OPTIONS = LEAD_FILTERS.filter(\n (option) => option !== 'all',\n).map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : LEAD_FILTER_LABELS[option],\n}))\n\n/** The choices for Email: every verdict, plus the two aggregates. */\nexport const LEAD_EMAIL_FILTER_OPTIONS = LEAD_EMAIL_FILTERS.filter(\n (option) => option !== 'any',\n).map((option) => ({ value: option, label: LEAD_EMAIL_FILTER_LABELS[option] }))\n\n/** The choices for Lead source direction: each group of the Lead source picklist. */\nexport const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS = LEAD_SOURCE_DIRECTIONS.map((direction) => ({\n value: direction,\n label: LEAD_SOURCE_DIRECTION_LABELS[direction],\n}))\n\n/**\n * The Status choices as an org reads them (AGL-3512): still one choice per\n * MEANING — the query asks `status` — each named by the org's values of that\n * meaning, so a list that keeps \"Contacted\" and \"Meeting set\" as Working\n * reads \"Working: Contacted, Meeting set\".\n */\nexport function leadStatusFilterOptions(\n picklist: CrmPicklist,\n): Array<{ value: string; label: string }> {\n const named = (status: CrmLeadStatus): string => {\n const labels = picklist.values\n .filter((value) => value.meaning === status && value.active)\n .map((value) => value.label)\n const meaning = picklistMeaningName(status)\n if (!labels.length) return meaning\n if (labels.length === 1 && labels[0] === meaning) return meaning\n return `${meaning}: ${labels.join(', ')}`\n }\n return LEAD_FILTERS.filter((option) => option !== 'all').map((option) => ({\n value: option,\n label: option === 'open' ? 'Open (new, nurturing or working)' : named(option),\n }))\n}\n\nconst picklistMeaningName = (status: CrmLeadStatus): string => CRM_LEAD_STATUS_LABELS[status]\n\nconst STATUS_OPEN: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'open' }\nconst STATUS_ALL: CrmViewFilterClause = { field: 'status', op: 'equals', value: 'all' }\n\n/**\n * The clauses the grid shows for a view's stored ones. A stored list with\n * no status clause is Open, which the list opened on before views existed,\n * so it is shown as the clause it means; `status equals all` is every lead\n * and so no clause at all.\n */\nexport function leadClausesForGrid(\n stored: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = stored.find((clause) => clause.field === 'status')\n if (!status) return [STATUS_OPEN, ...stored]\n if (status.op === 'equals' && status.value === 'all') {\n return stored.filter((clause) => clause.field !== 'status')\n }\n return [...stored]\n}\n\n/** The inverse of {@link leadClausesForGrid}: what the view stores. */\nexport function leadClausesToStore(\n shown: readonly CrmViewFilterClause[],\n): CrmViewFilterClause[] {\n const status = shown.find((clause) => clause.field === 'status')\n if (!status) return [...shown, STATUS_ALL]\n if (status.op === 'equals' && status.value === 'open') {\n return shown.filter((clause) => clause.field !== 'status')\n }\n return [...shown]\n}\n\n/** Campaign clauses were stored as `contains`; the panel's select says `is`. */\nexport const LEAD_CAMPAIGN_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'contains' || clause.op === 'equals'\n ? { operator: 'is', value: clause.value }\n : null,\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n return clause && clause.op === 'equals' ? { ...clause, op: 'contains' } : null\n },\n}\n\n/** \"No lead source\" is an `isEmpty` clause, shown as a choice of the select. */\nexport const LEAD_SOURCE_CODEC: ListGridFilterCodec = {\n toItem: (clause) =>\n clause.op === 'isEmpty'\n ? { operator: 'is', value: LEAD_SOURCE_FILTER_NONE }\n : listSelectCodec.toItem(clause),\n toClause: (item) => {\n const clause = listSelectCodec.toClause(item)\n if (clause?.op === 'equals' && clause.value === LEAD_SOURCE_FILTER_NONE) {\n return { field: clause.field, op: 'isEmpty', value: '' }\n }\n return clause\n },\n}\n\n/** The Leads fields whose stored clauses translate their own way. */\nexport const LEAD_FILTER_CODECS: Readonly<Record<string, ListGridFilterCodec>> = {\n campaignIds: LEAD_CAMPAIGN_CODEC,\n leadSource: LEAD_SOURCE_CODEC,\n}\n\n/*==========================================\n * THE LEADS QUERY (AGL-3321).\n *\n * `orgs/{orgId}/leads`, newest seen first, under a site narrowed by the\n * `visibleTo` scope clause. Each grid clause is asked through a field every\n * lead writer stores (`crmLeadListFields` in `@aglyn/aglyn`):\n *\n * status `status`, stored `new` on a lead nobody has touched, so\n * Open is `status in [new, nurturing, working]`\n * emailState `emailStatus`, the verdict's status or `none`; \"Cannot be\n * emailed\" is every verdict but `ok`\n * leadSource `leadSourceKey`, the label as the picklist compares it, or\n * `null` for none\n * leadSourceDirection `leadSourceKey in [...]` over the key of every\n * value the org's list files under that group, read when\n * the clause is asked — a direction is never stored on a\n * lead, so regrouping a value moves its leads with it. One\n * `in` holds thirty keys, and a group holding more is\n * refused by name rather than half asked\n * ownerUid as stored\n * industry, rating `industryKey`, `ratingKey` (AGL-3513), each label\n * as the picklist compares it\n * campaignIds `array-contains` on the lead's own campaigns; under a site,\n * `scopedCampaignIds` — the campaign behind each of the\n * site's scope tokens — which stands in the scope clause's\n * place, so the one array clause answers both\n *\n * and the search box reads `searchTokens` — word prefixes of the name,\n * address, company, title and tags. Every equality rides one\n * `(field, lastSeenAtMs DESC)` composite; the campaign and the search are\n * array clauses, so the two do not stand together (nor under a site, where\n * the scope clause is the query's one array clause, save the search, which\n * folds into it for a reader who may drop it).\n *=========================================*/\n\n/** The one order the Leads list takes: newest seen first. */\nexport const LEAD_LIST_SORTS: readonly ListQuerySort[] = [{ path: 'lastSeenAtMs', direction: 'desc' }]\n\n/** The fields the Leads query asks — each a stored field, as named. */\nexport const LEAD_QUERY_FIELDS: readonly ListFilterField[] = [\n crmSelectField('status'),\n crmSelectField('emailStatus'),\n crmSelectField('ownerUid'),\n {\n column: 'leadSourceKey',\n kind: 'exact',\n path: 'leadSourceKey',\n presence: 'nullable',\n operators: ['equals', 'isAnyOf', 'isEmpty'],\n },\n {\n column: 'campaignIds',\n kind: 'exact',\n path: 'campaignIds',\n tokensPath: 'campaignIds',\n operators: ['contains'],\n },\n // Industry and Rating (AGL-3513), by the key each writer stores.\n crmSelectField('industryKey'),\n crmSelectField('ratingKey'),\n {\n // A campaign under a site: the lead's campaigns behind its scope.\n column: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n kind: 'exact',\n path: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n tokensPath: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n operators: ['isAnyOf'],\n },\n]\n\nexport const LEAD_LIST_DECLARATION: ListQueryDeclaration = {\n fields: LEAD_QUERY_FIELDS,\n sorts: LEAD_LIST_SORTS,\n search: CRM_LIST_SEARCH,\n}\n\n/**\n * A collaborator's search (see `prefixSearch` in `useCrmListQuery`): the\n * start of the address, beside the scope clause — the `(visibleTo, email)`\n * composite the Inbox's leads list reads too.\n */\nexport const LEAD_PREFIX_SEARCH = {\n field: {\n column: 'email',\n kind: 'text',\n path: 'email',\n lowerPath: 'email',\n presence: 'always',\n operators: ['startsWith'],\n } satisfies ListFilterField,\n notice: 'Search matches the start of a lead’s address for access limited to specific sites.',\n}\n\n/** Whether an asked clause stands in for the scope clause: a campaign behind the site's scope. */\nexport const leadClauseImpliesScope = (clause: ListFilterRequest): boolean =>\n clause.field === CRM_LEAD_SCOPED_CAMPAIGNS_FIELD\n\n/** The verdicts a member must not email to: every one but `ok`. */\nconst EMAIL_PROBLEMS = EMAIL_STATE_STATUSES.filter((status) => status !== 'ok')\n\n/** Who is asking, for the one clause whose shape depends on it: the Campaign. */\nexport interface LeadQueryReader {\n /** The site's scope tokens, or `null` at the organization level. */\n scopeTokens: readonly string[] | null\n /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */\n foldsScope: boolean\n /**\n * The org's Lead source list, which a direction is expanded through;\n * the standard values alone while it has not been read.\n */\n leadSources?: CrmPicklist\n}\n\nconst ORG_READER: LeadQueryReader = { scopeTokens: null, foldsScope: true }\n\n/**\n * One grid clause as the clause the Leads query asks — see the block above.\n * A value the list does not know is refused by name rather than dropped, so\n * a stored view that names a retired choice says so.\n */\nexport function leadQueryClause(\n clause: CrmViewFilterClause | ListFilterRequest,\n reader: LeadQueryReader = ORG_READER,\n): CrmClauseAsked {\n const values = crmClauseValues(clause)\n switch (clause.field) {\n case 'status': {\n const statuses: string[] = []\n for (const value of values) {\n if (value === 'open') statuses.push(...CRM_LEAD_OPEN_STATUSES)\n else if ((CRM_LEAD_STATUSES as readonly string[]).includes(value)) statuses.push(value)\n else return { refused: `${value} is not a lead status` }\n }\n return crmAnyOf('status', statuses)\n }\n case 'emailState': {\n const keys: string[] = []\n for (const value of values) {\n if (value === 'problem') keys.push(...EMAIL_PROBLEMS)\n else if (value === 'none') keys.push('none')\n else if ((EMAIL_STATE_STATUSES as readonly string[]).includes(value)) keys.push(value)\n else return { refused: `${value} is not an email verdict` }\n }\n return crmAnyOf('emailStatus', keys)\n }\n case 'leadSource': {\n if (clause.op === 'isEmpty' || (values.length === 1 && values[0] === LEAD_SOURCE_FILTER_NONE)) {\n return { field: 'leadSourceKey', op: 'isEmpty', value: '' }\n }\n if (values.includes(LEAD_SOURCE_FILTER_NONE)) {\n return { refused: '\"No lead source\" cannot be picked beside a value' }\n }\n const keys = values.map((value) => crmLeadSourceKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf('leadSourceKey', keys)\n }\n case 'leadSourceDirection': {\n const picklist = reader.leadSources ?? STANDARD_LEAD_SOURCES\n const keys: string[] = []\n for (const value of values) {\n if (!isLeadSourceDirection(value)) return { refused: `${value} is not a lead source direction` }\n for (const label of leadSourceLabelsOfDirection(picklist, value)) {\n const key = crmLeadSourceKey(label)\n if (key) keys.push(key)\n }\n }\n const unique = [...new Set(keys)]\n if (!unique.length) return { refused: 'no lead source is in that direction yet' }\n if (unique.length > LIST_QUERY_DISJUNCTIONS) {\n return {\n refused: `more than ${LIST_QUERY_DISJUNCTIONS} lead sources are in that direction — filter by Lead source instead`,\n }\n }\n return crmAnyOf('leadSourceKey', unique)\n }\n case 'ownerUid':\n return crmAnyOf('ownerUid', values)\n case 'industry':\n case 'rating': {\n // A choice is the key; a label typed into a stored view keys the same way.\n const keys = values.map((value) => crmPicklistKey(value)).filter((key): key is string => Boolean(key))\n return crmAnyOf(clause.field === 'industry' ? 'industryKey' : 'ratingKey', keys)\n }\n case 'campaignIds': {\n if (values.length !== 1) return { refused: 'pick one campaign' }\n if (!reader.scopeTokens) return { field: 'campaignIds', op: 'contains', value: values[0] }\n if (!reader.foldsScope) {\n return {\n refused:\n 'your access is limited to specific sites, which this filter cannot be combined with — an organization administrator can use it',\n }\n }\n return {\n field: CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n op: 'isAnyOf',\n value: reader.scopeTokens.map((token) => `${token}${SCOPED_SEARCH_JOIN}${values[0]}`).join(','),\n }\n }\n default:\n return { refused: 'this list does not filter by that' }\n }\n}\n\n/**\n * The query shape the Leads list can send beyond its own declaration, for\n * the index pin (AGL-3321): a collaborator's address prefix beside the\n * equalities they may set.\n */\nexport function leadIndexShapes(): ListQueryDeclaration[] {\n const equalities = LEAD_QUERY_FIELDS.filter((field) => !field.tokensPath)\n return [{ fields: [LEAD_PREFIX_SEARCH.field, ...equalities], sorts: [] }]\n}\n"],"names":["EMAIL_STATE_LABELS","EMAIL_STATE_STATUSES","CRM_LEAD_STATUS_LABELS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","CRM_LEAD_STATUSES","crmLeadSourceKey","crmPicklistKey","SCOPED_SEARCH_JOIN","listSelectCodec","LIST_QUERY_DISJUNCTIONS","CRM_LIST_SEARCH","crmAnyOf","crmClauseValues","crmSelectField","isLeadSourceDirection","LEAD_SOURCE_DIRECTION_LABELS","LEAD_SOURCE_DIRECTIONS","leadSourceLabelsOfDirection","STANDARD_LEAD_SOURCES","LEAD_FILTERS","LEAD_FILTER_LABELS","open","all","new","nurturing","working","qualified","unqualified","LEAD_EMAIL_FILTERS","LEAD_EMAIL_FILTER_LABELS","any","problem","none","LEAD_SOURCE_FILTER_NONE","LEAD_LIST_FILTER_FIELDS","column","kind","path","operators","LEAD_PICKLIST_FILTERS","picklistId","header","LEAD_LIST_FILTER_HEADERS","status","emailState","ownerUid","leadSource","leadSourceDirection","campaignIds","industry","rating","LEAD_STATUS_FILTER_OPTIONS","filter","option","map","value","label","LEAD_EMAIL_FILTER_OPTIONS","LEAD_SOURCE_DIRECTION_FILTER_OPTIONS","direction","leadStatusFilterOptions","picklist","named","labels","values","meaning","active","picklistMeaningName","length","join","STATUS_OPEN","field","op","STATUS_ALL","leadClausesForGrid","stored","find","clause","leadClausesToStore","shown","LEAD_CAMPAIGN_CODEC","toItem","operator","toClause","item","LEAD_SOURCE_CODEC","LEAD_FILTER_CODECS","LEAD_LIST_SORTS","LEAD_QUERY_FIELDS","presence","tokensPath","LEAD_LIST_DECLARATION","fields","sorts","search","LEAD_PREFIX_SEARCH","lowerPath","notice","leadClauseImpliesScope","EMAIL_PROBLEMS","ORG_READER","scopeTokens","foldsScope","leadQueryClause","reader","statuses","push","includes","refused","keys","key","Boolean","leadSources","unique","Set","token","leadIndexShapes","equalities"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-filters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EMAIL_STATE_LABELS,\n EMAIL_STATE_STATUSES,\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_OPEN_STATUSES,\n CRM_LEAD_SCOPED_CAMPAIGNS_FIELD,\n CRM_LEAD_SOURCE_DIRECTION_FIELD,\n CRM_LEAD_STATUSES,\n type EmailStateStatus,\n type CrmLeadStatus,\n type CrmPicklist,\n crmLeadSourceKey,\n crmPicklistKey,\n} from '@aglyn/aglyn'\nimport type { CrmViewFilterClause } from '@aglyn/aglyn'\nimport { SCOPED_SEARCH_JOIN } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { type ListGridFilterCodec, listSelectCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n CRM_LIST_SEARCH,\n type CrmClauseAsked,\n crmAnyOf,\n crmClauseValues,\n crmSelectField,\n} from './crm-list-query'\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":["EMAIL_STATE_LABELS","EMAIL_STATE_STATUSES","CRM_LEAD_STATUS_LABELS","CRM_LEAD_OPEN_STATUSES","CRM_LEAD_SCOPED_CAMPAIGNS_FIELD","CRM_LEAD_SOURCE_DIRECTION_FIELD","CRM_LEAD_STATUSES","crmLeadSourceKey","crmPicklistKey","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":"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"}
|
|
@@ -14,25 +14,19 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type CrmPicklist } from '@aglyn/aglyn/app-utils/crm';
|
|
17
|
+
import { type CrmLeadSourceDirection, type CrmPicklist, isCrmLeadSourceDirection } from '@aglyn/aglyn/app-utils/crm';
|
|
18
18
|
/** The directions a lead source can have: the Lead source picklist's groups. */
|
|
19
|
-
export type LeadSourceDirection =
|
|
19
|
+
export type LeadSourceDirection = CrmLeadSourceDirection;
|
|
20
20
|
export declare const LEAD_SOURCE_DIRECTIONS: readonly LeadSourceDirection[];
|
|
21
21
|
/** How each direction reads, from the picklist's own group labels. */
|
|
22
22
|
export declare const LEAD_SOURCE_DIRECTION_LABELS: Readonly<Record<LeadSourceDirection, string>>;
|
|
23
23
|
/** A lead with no lead source, or one in no group. */
|
|
24
24
|
export declare const LEAD_SOURCE_UNSPECIFIED_LABEL = "Unspecified";
|
|
25
|
-
export declare
|
|
25
|
+
export declare const isLeadSourceDirection: typeof isCrmLeadSourceDirection;
|
|
26
26
|
/**
|
|
27
27
|
* The direction a lead source label has in `picklist` — `null` for no label,
|
|
28
28
|
* a value in no group, or a label the list does not hold.
|
|
29
29
|
*/
|
|
30
30
|
export declare function leadSourceDirectionOf(picklist: CrmPicklist, label: unknown): LeadSourceDirection | null;
|
|
31
|
-
/**
|
|
32
|
-
* Every label in `picklist` whose value is in `direction` — the inactive
|
|
33
|
-
* ones too, since a record keeps a value after it is deactivated and a
|
|
34
|
-
* direction asked of the records must still find it.
|
|
35
|
-
*/
|
|
36
|
-
export declare function leadSourceLabelsOfDirection(picklist: CrmPicklist, direction: LeadSourceDirection): string[];
|
|
37
31
|
/** The standard values alone — the answer before an organization's own list is read. */
|
|
38
32
|
export declare const STANDARD_LEAD_SOURCES: CrmPicklist;
|
|
@@ -13,11 +13,8 @@
|
|
|
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 { CRM_LEAD_SOURCE_PICKLIST,
|
|
17
|
-
export const LEAD_SOURCE_DIRECTIONS =
|
|
18
|
-
'inbound',
|
|
19
|
-
'outbound'
|
|
20
|
-
];
|
|
16
|
+
*/ import { CRM_LEAD_SOURCE_DIRECTIONS, CRM_LEAD_SOURCE_PICKLIST, crmLeadSourceDirection, crmPicklistDefinition, effectiveCrmLeadSourcePicklist, isCrmLeadSourceDirection } from "@aglyn/aglyn/app-utils/crm";
|
|
17
|
+
export const LEAD_SOURCE_DIRECTIONS = CRM_LEAD_SOURCE_DIRECTIONS;
|
|
21
18
|
/** How each direction reads, from the picklist's own group labels. */ export const LEAD_SOURCE_DIRECTION_LABELS = {
|
|
22
19
|
inbound: groupLabel('inbound', 'Inbound'),
|
|
23
20
|
outbound: groupLabel('outbound', 'Outbound')
|
|
@@ -29,23 +26,12 @@ function groupLabel(id, fallback) {
|
|
|
29
26
|
const groups = (_ref = (_crmPicklistDefinition = crmPicklistDefinition(CRM_LEAD_SOURCE_PICKLIST)) == null ? void 0 : _crmPicklistDefinition.groups) != null ? _ref : [];
|
|
30
27
|
return (_ref1 = (_groups_find = groups.find((group)=>group.id === id)) == null ? void 0 : _groups_find.label) != null ? _ref1 : fallback;
|
|
31
28
|
}
|
|
32
|
-
export
|
|
33
|
-
return LEAD_SOURCE_DIRECTIONS.includes(value);
|
|
34
|
-
}
|
|
29
|
+
export const isLeadSourceDirection = isCrmLeadSourceDirection;
|
|
35
30
|
/**
|
|
36
31
|
* The direction a lead source label has in `picklist` — `null` for no label,
|
|
37
32
|
* a value in no group, or a label the list does not hold.
|
|
38
33
|
*/ export function leadSourceDirectionOf(picklist, label) {
|
|
39
|
-
|
|
40
|
-
const group = (_crmPicklistValueByLabel = crmPicklistValueByLabel(picklist, label)) == null ? void 0 : _crmPicklistValueByLabel.group;
|
|
41
|
-
return isLeadSourceDirection(group) ? group : null;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Every label in `picklist` whose value is in `direction` — the inactive
|
|
45
|
-
* ones too, since a record keeps a value after it is deactivated and a
|
|
46
|
-
* direction asked of the records must still find it.
|
|
47
|
-
*/ export function leadSourceLabelsOfDirection(picklist, direction) {
|
|
48
|
-
return picklist.values.filter((value)=>value.group === direction).map((value)=>value.label);
|
|
34
|
+
return crmLeadSourceDirection(picklist, label);
|
|
49
35
|
}
|
|
50
36
|
/** The standard values alone — the answer before an organization's own list is read. */ export const STANDARD_LEAD_SOURCES = effectiveCrmLeadSourcePicklist(null);
|
|
51
37
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-source-direction.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_SOURCE_PICKLIST,\n type CrmPicklist,\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/lead-source-direction.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_SOURCE_DIRECTIONS,\n CRM_LEAD_SOURCE_PICKLIST,\n type CrmLeadSourceDirection,\n type CrmPicklist,\n crmLeadSourceDirection,\n crmPicklistDefinition,\n effectiveCrmLeadSourcePicklist,\n isCrmLeadSourceDirection,\n} from '@aglyn/aglyn/app-utils/crm'\n\n/*==========================================\n * A LEAD SOURCE'S DIRECTION (AGL-3511).\n *\n * Every lead source value sits in a group of the Lead source picklist —\n * Inbound for a person who came to the organization, Outbound for one the\n * organization went to — or in none. A record stores the label, and the\n * label's group is read off the organization's list: a sharing rule and\n * the Lead sources report read it when they are asked, and a lead also\n * carries it as `leadSourceDirection` (AGL-3577), the field the Leads\n * filter asks, which every move of the list that changes a label's group\n * rewrites (`crm/picklist-values`). Moving a value between groups moves\n * every record that holds it, in all three alike.\n *\n * The resolution itself is core's (`crmLeadSourceDirection`), because the\n * list-field writers that stamp a lead live there.\n *=========================================*/\n\n/** The directions a lead source can have: the Lead source picklist's groups. */\nexport type LeadSourceDirection = CrmLeadSourceDirection\n\nexport const LEAD_SOURCE_DIRECTIONS: readonly LeadSourceDirection[] = CRM_LEAD_SOURCE_DIRECTIONS\n\n/** How each direction reads, from the picklist's own group labels. */\nexport const LEAD_SOURCE_DIRECTION_LABELS: Readonly<Record<LeadSourceDirection, string>> = {\n inbound: groupLabel('inbound', 'Inbound'),\n outbound: groupLabel('outbound', 'Outbound'),\n}\n\n/** A lead with no lead source, or one in no group. */\nexport const LEAD_SOURCE_UNSPECIFIED_LABEL = 'Unspecified'\n\nfunction groupLabel(id: LeadSourceDirection, fallback: string): string {\n const groups = crmPicklistDefinition(CRM_LEAD_SOURCE_PICKLIST)?.groups ?? []\n return groups.find((group) => group.id === id)?.label ?? fallback\n}\n\nexport const isLeadSourceDirection = isCrmLeadSourceDirection\n\n/**\n * The direction a lead source label has in `picklist` — `null` for no label,\n * a value in no group, or a label the list does not hold.\n */\nexport function leadSourceDirectionOf(\n picklist: CrmPicklist,\n label: unknown,\n): LeadSourceDirection | null {\n return crmLeadSourceDirection(picklist, label)\n}\n\n/** The standard values alone — the answer before an organization's own list is read. */\nexport const STANDARD_LEAD_SOURCES: CrmPicklist = effectiveCrmLeadSourcePicklist(null)\n"],"names":["CRM_LEAD_SOURCE_DIRECTIONS","CRM_LEAD_SOURCE_PICKLIST","crmLeadSourceDirection","crmPicklistDefinition","effectiveCrmLeadSourcePicklist","isCrmLeadSourceDirection","LEAD_SOURCE_DIRECTIONS","LEAD_SOURCE_DIRECTION_LABELS","inbound","groupLabel","outbound","LEAD_SOURCE_UNSPECIFIED_LABEL","id","fallback","groups","find","group","label","isLeadSourceDirection","leadSourceDirectionOf","picklist","STANDARD_LEAD_SOURCES"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,0BAA0B,EAC1BC,wBAAwB,EAGxBC,sBAAsB,EACtBC,qBAAqB,EACrBC,8BAA8B,EAC9BC,wBAAwB,QACnB,6BAA4B;AAsBnC,OAAO,MAAMC,yBAAyDN,2BAA0B;AAEhG,oEAAoE,GACpE,OAAO,MAAMO,+BAA8E;IACzFC,SAASC,WAAW,WAAW;IAC/BC,UAAUD,WAAW,YAAY;AACnC,EAAC;AAED,oDAAoD,GACpD,OAAO,MAAME,gCAAgC,cAAa;AAE1D,SAASF,WAAWG,EAAuB,EAAEC,QAAgB;;QAC5CV,wBACRW;IADP,MAAMA,kBAASX,yBAAAA,sBAAsBF,8CAAtBE,uBAAiDW,MAAM,mBAAI,EAAE;IAC5E,iBAAOA,eAAAA,OAAOC,IAAI,CAAC,CAACC,QAAUA,MAAMJ,EAAE,KAAKA,wBAApCE,aAAyCG,KAAK,oBAAIJ;AAC3D;AAEA,OAAO,MAAMK,wBAAwBb,yBAAwB;AAE7D;;;CAGC,GACD,OAAO,SAASc,sBACdC,QAAqB,EACrBH,KAAc;IAEd,OAAOf,uBAAuBkB,UAAUH;AAC1C;AAEA,sFAAsF,GACtF,OAAO,MAAMI,wBAAqCjB,+BAA+B,MAAK"}
|
|
@@ -20,10 +20,12 @@ import { type CrmPicklist, type CrmPicklistDefinition, type CrmPicklistId, type
|
|
|
20
20
|
*
|
|
21
21
|
* The Fields page applies the ones that touch only the list — add, reorder,
|
|
22
22
|
* sort, activate, deactivate, group, default — as one client write of the
|
|
23
|
-
* whole document. The
|
|
24
|
-
* delete with its replacement
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* whole document. The ones that change what RECORDS hold — a rename and a
|
|
24
|
+
* delete with its replacement, and, on a list whose records keep the
|
|
25
|
+
* value's group ({@link picklistKeepsGroups}: a lead's lead source
|
|
26
|
+
* direction, AGL-3577), a regroup and an add — go to `crm/picklist-values`,
|
|
27
|
+
* which rewrites the list and every record the definition's targets name in
|
|
28
|
+
* one request; this module is where both sides agree what each move does.
|
|
27
29
|
*
|
|
28
30
|
* Each move that depends on the field takes its definition: a standard
|
|
29
31
|
* value cannot be deleted, an added value's id is minted clear of every
|
|
@@ -48,7 +50,26 @@ export type PicklistValuesAction = {
|
|
|
48
50
|
valueId: string;
|
|
49
51
|
/** An active value's label the records move to, or `null` to clear them. */
|
|
50
52
|
replaceWith: string | null;
|
|
53
|
+
}
|
|
54
|
+
/** File a value under a group, or none with `null` — a list that {@link picklistKeepsGroups}. */
|
|
55
|
+
| {
|
|
56
|
+
action: 'group';
|
|
57
|
+
valueId: string;
|
|
58
|
+
group: string | null;
|
|
59
|
+
}
|
|
60
|
+
/** Add a value — on a list that {@link picklistKeepsGroups}, where records may already hold it. */
|
|
61
|
+
| {
|
|
62
|
+
action: 'add';
|
|
63
|
+
label: string;
|
|
64
|
+
group?: string | null;
|
|
65
|
+
meaning?: string | null;
|
|
51
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Whether records keep a value's GROUP beside its label (AGL-3577) — a
|
|
69
|
+
* lead's `leadSourceDirection` — so a regroup or an add changes records and
|
|
70
|
+
* goes to the route rather than being one client write of the list.
|
|
71
|
+
*/
|
|
72
|
+
export declare function picklistKeepsGroups(definition: CrmPicklistDefinition): boolean;
|
|
52
73
|
/** What the route is asked. `hostId` or `orgId` names the org, as every CRM route's scope does. */
|
|
53
74
|
export type PicklistValuesRequest = {
|
|
54
75
|
hostId?: string | null;
|
|
@@ -21,10 +21,12 @@ import { isStandardPicklistValueId } from "@aglyn/aglyn/app-utils/picklists";
|
|
|
21
21
|
*
|
|
22
22
|
* The Fields page applies the ones that touch only the list — add, reorder,
|
|
23
23
|
* sort, activate, deactivate, group, default — as one client write of the
|
|
24
|
-
* whole document. The
|
|
25
|
-
* delete with its replacement
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* whole document. The ones that change what RECORDS hold — a rename and a
|
|
25
|
+
* delete with its replacement, and, on a list whose records keep the
|
|
26
|
+
* value's group ({@link picklistKeepsGroups}: a lead's lead source
|
|
27
|
+
* direction, AGL-3577), a regroup and an add — go to `crm/picklist-values`,
|
|
28
|
+
* which rewrites the list and every record the definition's targets name in
|
|
29
|
+
* one request; this module is where both sides agree what each move does.
|
|
28
30
|
*
|
|
29
31
|
* Each move that depends on the field takes its definition: a standard
|
|
30
32
|
* value cannot be deleted, an added value's id is minted clear of every
|
|
@@ -36,6 +38,13 @@ import { isStandardPicklistValueId } from "@aglyn/aglyn/app-utils/picklists";
|
|
|
36
38
|
* picklist shared one; a request to it needs no `picklistId`.
|
|
37
39
|
*/ export const CRM_LEAD_SOURCE_VALUES_ROUTE = 'crm/lead-source-values';
|
|
38
40
|
export const crmPicklistValuesRouteUrl = ()=>`/api/${CRM_PICKLIST_VALUES_ROUTE}`;
|
|
41
|
+
/**
|
|
42
|
+
* Whether records keep a value's GROUP beside its label (AGL-3577) — a
|
|
43
|
+
* lead's `leadSourceDirection` — so a regroup or an add changes records and
|
|
44
|
+
* goes to the route rather than being one client write of the list.
|
|
45
|
+
*/ export function picklistKeepsGroups(definition) {
|
|
46
|
+
return definition.targets.some((target)=>Boolean(target.groupField));
|
|
47
|
+
}
|
|
39
48
|
const DUPLICATE = (label)=>`“${label}” is already in the list.`;
|
|
40
49
|
/**
|
|
41
50
|
* Append an added value. On a definition with groups it is filed under
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/picklist-values.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 type CrmPicklist,\n type CrmPicklistDefinition,\n type CrmPicklistId,\n type CrmPicklistObject,\n type CrmPicklistValue,\n CRM_PICKLIST_VALUES_MAX,\n crmPicklistValueByLabel,\n crmPicklistValueId,\n normalizeCrmPicklistLabel,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { isStandardPicklistValueId } from '@aglyn/aglyn/app-utils/picklists'\n\n/**\n * A PICKLIST'S EDITS (AGL-3298, AGL-3510), as pure moves over a list.\n *\n * The Fields page applies the ones that touch only the list — add, reorder,\n * sort, activate, deactivate, group, default — as one client write of the\n * whole document. The two that change what RECORDS hold — a rename and a\n * delete with its replacement — go to `crm/picklist-values`, which rewrites\n * the list and every record the definition's targets name in one request;\n * this module is where both sides agree what each move does.\n *\n * Each move that depends on the field takes its definition: a standard\n * value cannot be deleted, an added value's id is minted clear of every\n * standard id, and on a definition with meanings a delete's replacement\n * must mean the same.\n */\n\n/** The route the two record-changing moves are posted to. */\nexport const CRM_PICKLIST_VALUES_ROUTE = 'crm/picklist-values' as const\n\n/**\n * The lead source's own address for the same route, from before every\n * picklist shared one; a request to it needs no `picklistId`.\n */\nexport const CRM_LEAD_SOURCE_VALUES_ROUTE = 'crm/lead-source-values' as const\n\nexport const crmPicklistValuesRouteUrl = (): string => `/api/${CRM_PICKLIST_VALUES_ROUTE}`\n\n/** The record-changing move a request asks for. */\nexport type PicklistValuesAction =\n | { action: 'rename'; valueId: string; label: string }\n | {\n action: 'delete'\n valueId: string\n /** An active value's label the records move to, or `null` to clear them. */\n replaceWith: string | null\n }\n\n/** What the route is asked. `hostId` or `orgId` names the org, as every CRM route's scope does. */\nexport type PicklistValuesRequest = {\n hostId?: string | null\n orgId?: string\n picklistId: CrmPicklistId\n} & PicklistValuesAction\n\n/** What the route answers. */\nexport interface PicklistValuesResponse {\n ok: true\n /**\n * How many records each target object had rewritten. A contact counts\n * once per FACET rewritten — one per holder that held the old label, so\n * a person two sites both filed under it counts twice.\n */\n updated: Partial<Record<CrmPicklistObject, number>>\n}\n\n/** Why a move is refused, in the sentence the page shows. */\nexport type PicklistMove = { ok: true; picklist: CrmPicklist } | { ok: false; error: string }\n\nconst DUPLICATE = (label: string) => `“${label}” is already in the list.`\n\n/**\n * Append an added value. On a definition with groups it is filed under\n * `group` (or none); on one with meanings it must name one.\n */\nexport function addPicklistValue(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n rawLabel: string,\n options: { group?: string | null; meaning?: string | null } = {},\n): PicklistMove {\n const label = normalizeCrmPicklistLabel(rawLabel)\n if (!label) return { ok: false, error: 'Enter a name for the value.' }\n const existing = crmPicklistValueByLabel(picklist, label)\n if (existing) {\n return {\n ok: false,\n error: existing.active\n ? DUPLICATE(existing.label)\n : `“${existing.label}” is in the list, inactive — activate it instead.`,\n }\n }\n if (picklist.values.length >= CRM_PICKLIST_VALUES_MAX) {\n return { ok: false, error: `A list holds at most ${CRM_PICKLIST_VALUES_MAX} values.` }\n }\n const meanings = definition.meanings ?? []\n if (meanings.length && !meanings.includes(options.meaning ?? '')) {\n return { ok: false, error: 'Pick what the value means.' }\n }\n if (definition.reservedMeanings?.includes(options.meaning ?? '')) {\n const name = picklistMeaningLabel(definition, options.meaning as string)\n return { ok: false, error: `Only the platform sets ${name}; pick another meaning.` }\n }\n const groups = definition.groups ?? []\n const value: CrmPicklistValue = {\n id: crmPicklistValueId(label, [\n ...picklist.values.map((entry) => entry.id),\n ...definition.standardValues.map((entry) => entry.id),\n ]),\n label,\n active: true,\n ...(groups.length\n ? { group: groups.some((group) => group.id === options.group) ? (options.group as string) : null }\n : {}),\n ...(meanings.length ? { meaning: options.meaning as string } : {}),\n }\n return { ok: true, picklist: { ...picklist, values: [...picklist.values, value] } }\n}\n\n/** Rename a value, keeping its id — the list half of the route's rename. */\nexport function renamePicklistValue(\n picklist: CrmPicklist,\n valueId: string,\n rawLabel: string,\n): PicklistMove {\n const label = normalizeCrmPicklistLabel(rawLabel)\n if (!label) return { ok: false, error: 'Enter a name for the value.' }\n if (!picklist.values.some((value) => value.id === valueId)) {\n return { ok: false, error: 'That value is no longer in the list.' }\n }\n const clash = crmPicklistValueByLabel(picklist, label)\n if (clash && clash.id !== valueId) return { ok: false, error: DUPLICATE(clash.label) }\n return {\n ok: true,\n picklist: {\n ...picklist,\n values: picklist.values.map((value) => (value.id === valueId ? { ...value, label } : value)),\n },\n }\n}\n\n/**\n * Remove an added value — the list half of the route's delete. A standard\n * value is refused: every org has it, so it can be deactivated and not\n * removed. The replacement, when there is one, must be another ACTIVE\n * value — records are not moved onto a value the pickers no longer offer —\n * and, on a definition with meanings, one of the SAME meaning, so a record\n * never changes what it means by losing a label. A default that named the\n * deleted value is cleared.\n */\nexport function deletePicklistValue(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n valueId: string,\n replaceWith: string | null,\n): PicklistMove {\n const value = picklist.values.find((entry) => entry.id === valueId)\n if (!value) return { ok: false, error: 'That value is no longer in the list.' }\n if (isStandardPicklistValueId(definition, valueId)) {\n return {\n ok: false,\n error: `“${value.label}” is a standard value and cannot be deleted. Deactivate it instead.`,\n }\n }\n // On a definition with meanings a record's meaning must survive the\n // delete, so its label moves to another value of that meaning — never off.\n if (replaceWith === null && definition.meanings?.length) {\n return {\n ok: false,\n error: `Pick a value that means the same as “${value.label}” to move its records to.`,\n }\n }\n if (replaceWith !== null) {\n const target = crmPicklistValueByLabel(picklist, replaceWith)\n if (!target || target.id === valueId || !target.active) {\n return { ok: false, error: 'Pick an active value to move its records to, or none.' }\n }\n if (definition.meanings?.length && (target.meaning ?? null) !== (value.meaning ?? null)) {\n return {\n ok: false,\n error: `Pick a value that means the same as “${value.label}” to move its records to.`,\n }\n }\n }\n return {\n ok: true,\n picklist: {\n values: picklist.values.filter((entry) => entry.id !== valueId),\n defaultValueId: picklist.defaultValueId === valueId ? null : picklist.defaultValueId,\n },\n }\n}\n\n/**\n * How a meaning reads on screen: the definition's own label for it (lead\n * status's \"Nurturing\"), else the label of the one standard value that\n * carries it (a task's \"Completed\", \"To-do\"), else the meaning capitalized\n * (\"Open\").\n */\nexport function picklistMeaningLabel(definition: CrmPicklistDefinition, meaning: string): string {\n const own = definition.meaningLabels?.[meaning]\n if (own) return own\n const holders = definition.standardValues.filter((value) => value.meaning === meaning)\n return holders.length === 1 && holders[0]\n ? holders[0].label\n : meaning.charAt(0).toUpperCase() + meaning.slice(1)\n}\n\n/** The meanings an org-added value may take: every one but those the platform reserves. */\nexport function picklistAddableMeanings(definition: CrmPicklistDefinition): string[] {\n return (definition.meanings ?? []).filter((meaning) => !definition.reservedMeanings?.includes(meaning))\n}\n\n/**\n * The values a deleted value's records may move to: every other active\n * value, of the same meaning on a definition with meanings.\n */\nexport function picklistReplacements(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n valueId: string,\n): CrmPicklistValue[] {\n const deleted = picklist.values.find((entry) => entry.id === valueId)\n return picklist.values.filter(\n (entry) =>\n entry.active &&\n entry.id !== valueId &&\n (!definition.meanings?.length || (entry.meaning ?? null) === (deleted?.meaning ?? null)),\n )\n}\n\n/** Turn a value on or off. Deactivating the default clears the default. */\nexport function setPicklistValueActive(\n picklist: CrmPicklist,\n valueId: string,\n active: boolean,\n): CrmPicklist {\n return {\n values: picklist.values.map((value) => (value.id === valueId ? { ...value, active } : value)),\n defaultValueId:\n !active && picklist.defaultValueId === valueId ? null : picklist.defaultValueId,\n }\n}\n\n/** File a value under one of the definition's groups, or none with `null`. */\nexport function setPicklistValueGroup(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n valueId: string,\n group: string | null,\n): CrmPicklist {\n const known = definition.groups?.some((entry) => entry.id === group) ? group : null\n return {\n ...picklist,\n values: picklist.values.map((value) =>\n value.id === valueId ? { ...value, group: known } : value,\n ),\n }\n}\n\n/** Make a value the default for new records, or clear the default with `null`. */\nexport function setPicklistDefault(picklist: CrmPicklist, valueId: string | null): CrmPicklist {\n const value = picklist.values.find((entry) => entry.id === valueId)\n return { ...picklist, defaultValueId: value?.active ? value.id : null }\n}\n\n/** Move the value at `from` to `to`, the drag and the arrows alike. */\nexport function movePicklistValue(picklist: CrmPicklist, from: number, to: number): CrmPicklist {\n const size = picklist.values.length\n if (from === to || from < 0 || to < 0 || from >= size || to >= size) return picklist\n const values = [...picklist.values]\n const [moved] = values.splice(from, 1)\n values.splice(to, 0, moved)\n return { ...picklist, values }\n}\n\n/** Every value in A–Z order by label, as Salesforce's \"Sort alphabetically\" does. */\nexport function sortPicklistValues(picklist: CrmPicklist): CrmPicklist {\n return {\n ...picklist,\n values: [...picklist.values].sort((a, b) =>\n a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }),\n ),\n }\n}\n"],"names":["CRM_PICKLIST_VALUES_MAX","crmPicklistValueByLabel","crmPicklistValueId","normalizeCrmPicklistLabel","isStandardPicklistValueId","CRM_PICKLIST_VALUES_ROUTE","CRM_LEAD_SOURCE_VALUES_ROUTE","crmPicklistValuesRouteUrl","DUPLICATE","label","addPicklistValue","definition","picklist","rawLabel","options","ok","error","existing","active","values","length","meanings","includes","meaning","reservedMeanings","name","picklistMeaningLabel","groups","value","id","map","entry","standardValues","group","some","renamePicklistValue","valueId","clash","deletePicklistValue","replaceWith","find","target","filter","defaultValueId","own","meaningLabels","holders","charAt","toUpperCase","slice","picklistAddableMeanings","picklistReplacements","deleted","setPicklistValueActive","setPicklistValueGroup","known","setPicklistDefault","movePicklistValue","from","to","size","moved","splice","sortPicklistValues","sort","a","b","localeCompare","undefined","sensitivity"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/picklist-values.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 type CrmPicklist,\n type CrmPicklistDefinition,\n type CrmPicklistId,\n type CrmPicklistObject,\n type CrmPicklistValue,\n CRM_PICKLIST_VALUES_MAX,\n crmPicklistValueByLabel,\n crmPicklistValueId,\n normalizeCrmPicklistLabel,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { isStandardPicklistValueId } from '@aglyn/aglyn/app-utils/picklists'\n\n/**\n * A PICKLIST'S EDITS (AGL-3298, AGL-3510), as pure moves over a list.\n *\n * The Fields page applies the ones that touch only the list — add, reorder,\n * sort, activate, deactivate, group, default — as one client write of the\n * whole document. The ones that change what RECORDS hold — a rename and a\n * delete with its replacement, and, on a list whose records keep the\n * value's group ({@link picklistKeepsGroups}: a lead's lead source\n * direction, AGL-3577), a regroup and an add — go to `crm/picklist-values`,\n * which rewrites the list and every record the definition's targets name in\n * one request; this module is where both sides agree what each move does.\n *\n * Each move that depends on the field takes its definition: a standard\n * value cannot be deleted, an added value's id is minted clear of every\n * standard id, and on a definition with meanings a delete's replacement\n * must mean the same.\n */\n\n/** The route the two record-changing moves are posted to. */\nexport const CRM_PICKLIST_VALUES_ROUTE = 'crm/picklist-values' as const\n\n/**\n * The lead source's own address for the same route, from before every\n * picklist shared one; a request to it needs no `picklistId`.\n */\nexport const CRM_LEAD_SOURCE_VALUES_ROUTE = 'crm/lead-source-values' as const\n\nexport const crmPicklistValuesRouteUrl = (): string => `/api/${CRM_PICKLIST_VALUES_ROUTE}`\n\n/** The record-changing move a request asks for. */\nexport type PicklistValuesAction =\n | { action: 'rename'; valueId: string; label: string }\n | {\n action: 'delete'\n valueId: string\n /** An active value's label the records move to, or `null` to clear them. */\n replaceWith: string | null\n }\n /** File a value under a group, or none with `null` — a list that {@link picklistKeepsGroups}. */\n | { action: 'group'; valueId: string; group: string | null }\n /** Add a value — on a list that {@link picklistKeepsGroups}, where records may already hold it. */\n | { action: 'add'; label: string; group?: string | null; meaning?: string | null }\n\n/**\n * Whether records keep a value's GROUP beside its label (AGL-3577) — a\n * lead's `leadSourceDirection` — so a regroup or an add changes records and\n * goes to the route rather than being one client write of the list.\n */\nexport function picklistKeepsGroups(definition: CrmPicklistDefinition): boolean {\n return definition.targets.some((target) => Boolean(target.groupField))\n}\n\n/** What the route is asked. `hostId` or `orgId` names the org, as every CRM route's scope does. */\nexport type PicklistValuesRequest = {\n hostId?: string | null\n orgId?: string\n picklistId: CrmPicklistId\n} & PicklistValuesAction\n\n/** What the route answers. */\nexport interface PicklistValuesResponse {\n ok: true\n /**\n * How many records each target object had rewritten. A contact counts\n * once per FACET rewritten — one per holder that held the old label, so\n * a person two sites both filed under it counts twice.\n */\n updated: Partial<Record<CrmPicklistObject, number>>\n}\n\n/** Why a move is refused, in the sentence the page shows. */\nexport type PicklistMove = { ok: true; picklist: CrmPicklist } | { ok: false; error: string }\n\nconst DUPLICATE = (label: string) => `“${label}” is already in the list.`\n\n/**\n * Append an added value. On a definition with groups it is filed under\n * `group` (or none); on one with meanings it must name one.\n */\nexport function addPicklistValue(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n rawLabel: string,\n options: { group?: string | null; meaning?: string | null } = {},\n): PicklistMove {\n const label = normalizeCrmPicklistLabel(rawLabel)\n if (!label) return { ok: false, error: 'Enter a name for the value.' }\n const existing = crmPicklistValueByLabel(picklist, label)\n if (existing) {\n return {\n ok: false,\n error: existing.active\n ? DUPLICATE(existing.label)\n : `“${existing.label}” is in the list, inactive — activate it instead.`,\n }\n }\n if (picklist.values.length >= CRM_PICKLIST_VALUES_MAX) {\n return { ok: false, error: `A list holds at most ${CRM_PICKLIST_VALUES_MAX} values.` }\n }\n const meanings = definition.meanings ?? []\n if (meanings.length && !meanings.includes(options.meaning ?? '')) {\n return { ok: false, error: 'Pick what the value means.' }\n }\n if (definition.reservedMeanings?.includes(options.meaning ?? '')) {\n const name = picklistMeaningLabel(definition, options.meaning as string)\n return { ok: false, error: `Only the platform sets ${name}; pick another meaning.` }\n }\n const groups = definition.groups ?? []\n const value: CrmPicklistValue = {\n id: crmPicklistValueId(label, [\n ...picklist.values.map((entry) => entry.id),\n ...definition.standardValues.map((entry) => entry.id),\n ]),\n label,\n active: true,\n ...(groups.length\n ? { group: groups.some((group) => group.id === options.group) ? (options.group as string) : null }\n : {}),\n ...(meanings.length ? { meaning: options.meaning as string } : {}),\n }\n return { ok: true, picklist: { ...picklist, values: [...picklist.values, value] } }\n}\n\n/** Rename a value, keeping its id — the list half of the route's rename. */\nexport function renamePicklistValue(\n picklist: CrmPicklist,\n valueId: string,\n rawLabel: string,\n): PicklistMove {\n const label = normalizeCrmPicklistLabel(rawLabel)\n if (!label) return { ok: false, error: 'Enter a name for the value.' }\n if (!picklist.values.some((value) => value.id === valueId)) {\n return { ok: false, error: 'That value is no longer in the list.' }\n }\n const clash = crmPicklistValueByLabel(picklist, label)\n if (clash && clash.id !== valueId) return { ok: false, error: DUPLICATE(clash.label) }\n return {\n ok: true,\n picklist: {\n ...picklist,\n values: picklist.values.map((value) => (value.id === valueId ? { ...value, label } : value)),\n },\n }\n}\n\n/**\n * Remove an added value — the list half of the route's delete. A standard\n * value is refused: every org has it, so it can be deactivated and not\n * removed. The replacement, when there is one, must be another ACTIVE\n * value — records are not moved onto a value the pickers no longer offer —\n * and, on a definition with meanings, one of the SAME meaning, so a record\n * never changes what it means by losing a label. A default that named the\n * deleted value is cleared.\n */\nexport function deletePicklistValue(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n valueId: string,\n replaceWith: string | null,\n): PicklistMove {\n const value = picklist.values.find((entry) => entry.id === valueId)\n if (!value) return { ok: false, error: 'That value is no longer in the list.' }\n if (isStandardPicklistValueId(definition, valueId)) {\n return {\n ok: false,\n error: `“${value.label}” is a standard value and cannot be deleted. Deactivate it instead.`,\n }\n }\n // On a definition with meanings a record's meaning must survive the\n // delete, so its label moves to another value of that meaning — never off.\n if (replaceWith === null && definition.meanings?.length) {\n return {\n ok: false,\n error: `Pick a value that means the same as “${value.label}” to move its records to.`,\n }\n }\n if (replaceWith !== null) {\n const target = crmPicklistValueByLabel(picklist, replaceWith)\n if (!target || target.id === valueId || !target.active) {\n return { ok: false, error: 'Pick an active value to move its records to, or none.' }\n }\n if (definition.meanings?.length && (target.meaning ?? null) !== (value.meaning ?? null)) {\n return {\n ok: false,\n error: `Pick a value that means the same as “${value.label}” to move its records to.`,\n }\n }\n }\n return {\n ok: true,\n picklist: {\n values: picklist.values.filter((entry) => entry.id !== valueId),\n defaultValueId: picklist.defaultValueId === valueId ? null : picklist.defaultValueId,\n },\n }\n}\n\n/**\n * How a meaning reads on screen: the definition's own label for it (lead\n * status's \"Nurturing\"), else the label of the one standard value that\n * carries it (a task's \"Completed\", \"To-do\"), else the meaning capitalized\n * (\"Open\").\n */\nexport function picklistMeaningLabel(definition: CrmPicklistDefinition, meaning: string): string {\n const own = definition.meaningLabels?.[meaning]\n if (own) return own\n const holders = definition.standardValues.filter((value) => value.meaning === meaning)\n return holders.length === 1 && holders[0]\n ? holders[0].label\n : meaning.charAt(0).toUpperCase() + meaning.slice(1)\n}\n\n/** The meanings an org-added value may take: every one but those the platform reserves. */\nexport function picklistAddableMeanings(definition: CrmPicklistDefinition): string[] {\n return (definition.meanings ?? []).filter((meaning) => !definition.reservedMeanings?.includes(meaning))\n}\n\n/**\n * The values a deleted value's records may move to: every other active\n * value, of the same meaning on a definition with meanings.\n */\nexport function picklistReplacements(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n valueId: string,\n): CrmPicklistValue[] {\n const deleted = picklist.values.find((entry) => entry.id === valueId)\n return picklist.values.filter(\n (entry) =>\n entry.active &&\n entry.id !== valueId &&\n (!definition.meanings?.length || (entry.meaning ?? null) === (deleted?.meaning ?? null)),\n )\n}\n\n/** Turn a value on or off. Deactivating the default clears the default. */\nexport function setPicklistValueActive(\n picklist: CrmPicklist,\n valueId: string,\n active: boolean,\n): CrmPicklist {\n return {\n values: picklist.values.map((value) => (value.id === valueId ? { ...value, active } : value)),\n defaultValueId:\n !active && picklist.defaultValueId === valueId ? null : picklist.defaultValueId,\n }\n}\n\n/** File a value under one of the definition's groups, or none with `null`. */\nexport function setPicklistValueGroup(\n definition: CrmPicklistDefinition,\n picklist: CrmPicklist,\n valueId: string,\n group: string | null,\n): CrmPicklist {\n const known = definition.groups?.some((entry) => entry.id === group) ? group : null\n return {\n ...picklist,\n values: picklist.values.map((value) =>\n value.id === valueId ? { ...value, group: known } : value,\n ),\n }\n}\n\n/** Make a value the default for new records, or clear the default with `null`. */\nexport function setPicklistDefault(picklist: CrmPicklist, valueId: string | null): CrmPicklist {\n const value = picklist.values.find((entry) => entry.id === valueId)\n return { ...picklist, defaultValueId: value?.active ? value.id : null }\n}\n\n/** Move the value at `from` to `to`, the drag and the arrows alike. */\nexport function movePicklistValue(picklist: CrmPicklist, from: number, to: number): CrmPicklist {\n const size = picklist.values.length\n if (from === to || from < 0 || to < 0 || from >= size || to >= size) return picklist\n const values = [...picklist.values]\n const [moved] = values.splice(from, 1)\n values.splice(to, 0, moved)\n return { ...picklist, values }\n}\n\n/** Every value in A–Z order by label, as Salesforce's \"Sort alphabetically\" does. */\nexport function sortPicklistValues(picklist: CrmPicklist): CrmPicklist {\n return {\n ...picklist,\n values: [...picklist.values].sort((a, b) =>\n a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }),\n ),\n }\n}\n"],"names":["CRM_PICKLIST_VALUES_MAX","crmPicklistValueByLabel","crmPicklistValueId","normalizeCrmPicklistLabel","isStandardPicklistValueId","CRM_PICKLIST_VALUES_ROUTE","CRM_LEAD_SOURCE_VALUES_ROUTE","crmPicklistValuesRouteUrl","picklistKeepsGroups","definition","targets","some","target","Boolean","groupField","DUPLICATE","label","addPicklistValue","picklist","rawLabel","options","ok","error","existing","active","values","length","meanings","includes","meaning","reservedMeanings","name","picklistMeaningLabel","groups","value","id","map","entry","standardValues","group","renamePicklistValue","valueId","clash","deletePicklistValue","replaceWith","find","filter","defaultValueId","own","meaningLabels","holders","charAt","toUpperCase","slice","picklistAddableMeanings","picklistReplacements","deleted","setPicklistValueActive","setPicklistValueGroup","known","setPicklistDefault","movePicklistValue","from","to","size","moved","splice","sortPicklistValues","sort","a","b","localeCompare","undefined","sensitivity"],"mappings":"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"}
|
|
@@ -14,7 +14,20 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
17
|
+
import { type CrmPicklist, type CrmPicklistTarget, type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/** The group a label is filed under in `picklist` — `null` for none, or a label it does not hold. */
|
|
19
|
+
export declare function picklistGroupOf(picklist: CrmPicklist, label: string | null): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Rewrite a target's GROUP field (AGL-3577) — a lead's
|
|
22
|
+
* `leadSourceDirection` — on every record holding `label` in any spelling
|
|
23
|
+
* (by its `keyField`) whose group is not `group` already. Answers how many
|
|
24
|
+
* were written. The field is derived, like the list keys a restamp writes,
|
|
25
|
+
* so `updatedAt` is left alone.
|
|
26
|
+
*
|
|
27
|
+
* Paged by document id rather than re-run: a record already holding the
|
|
28
|
+
* group stays in the query's answer.
|
|
29
|
+
*/
|
|
30
|
+
export declare function regroupEverywhere(firestore: FirebaseFirestore.Firestore, records: FirebaseFirestore.CollectionReference, target: CrmPicklistTarget, label: string, group: string | null): Promise<number>;
|
|
18
31
|
/** `POST crm/picklist-values` — any registered picklist, named by `picklistId`. */
|
|
19
32
|
export declare const crmPicklistValuesHandler: PluginApiHandler;
|
|
20
33
|
/** `POST crm/lead-source-values` — the same route with the picklist fixed to the lead source. */
|