@aglyn/plugins-crm 1.0.0-beta.246 → 1.0.0-beta.249
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/README.md +1 -1
- package/package.json +14 -14
- package/src/lib/components/contact-custom-columns.d.ts +15 -6
- package/src/lib/components/contact-custom-columns.js +27 -7
- package/src/lib/components/contact-custom-columns.js.map +1 -1
- package/src/lib/components/contact-field-drawer.d.ts +2 -0
- package/src/lib/components/contact-field-drawer.js +25 -1
- package/src/lib/components/contact-field-drawer.js.map +1 -1
- package/src/lib/components/contacts-section.js +9 -3
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-custom-field-control.d.ts +4 -2
- package/src/lib/components/crm-custom-field-control.js +53 -1
- package/src/lib/components/crm-custom-field-control.js.map +1 -1
- package/src/lib/components/fields-section.js +4 -2
- package/src/lib/components/fields-section.js.map +1 -1
- package/src/lib/components/form-contact-fields-card.js +2 -0
- package/src/lib/components/form-contact-fields-card.js.map +1 -1
- package/src/lib/constants/contact-filters.d.ts +7 -0
- package/src/lib/constants/contact-filters.js +8 -1
- package/src/lib/constants/contact-filters.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +2 -0
- package/src/lib/declarations.console-server.js +5 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/model/crm-picklist-setup-content.d.ts +121 -0
- package/src/lib/model/crm-picklist-setup-content.js +262 -0
- package/src/lib/model/crm-picklist-setup-content.js.map +1 -0
- package/src/lib/model/crm-record-routes.js +6 -0
- package/src/lib/model/crm-record-routes.js.map +1 -1
- package/src/lib/model/crm-setup-content.d.ts +4 -1
- package/src/lib/model/crm-setup-content.js +15 -4
- package/src/lib/model/crm-setup-content.js.map +1 -1
- package/src/lib/server/crm-setup-ai-capabilities.d.ts +13 -0
- package/src/lib/server/crm-setup-ai-capabilities.js +94 -2
- package/src/lib/server/crm-setup-ai-capabilities.js.map +1 -1
- package/src/lib/server/crm-setup-writers.server.d.ts +28 -0
- package/src/lib/server/crm-setup-writers.server.js +181 -4
- package/src/lib/server/crm-setup-writers.server.js.map +1 -1
- package/src/lib/server/picklist-regroup.d.ts +30 -0
- package/src/lib/server/picklist-regroup.js +61 -0
- package/src/lib/server/picklist-regroup.js.map +1 -0
- package/src/lib/server/picklist-values.d.ts +2 -14
- package/src/lib/server/picklist-values.js +3 -40
- package/src/lib/server/picklist-values.js.map +1 -1
- package/src/lib/transfer/common.d.ts +4 -2
- package/src/lib/transfer/common.js +14 -3
- package/src/lib/transfer/common.js.map +1 -1
- package/src/lib/transfer/fields.js +10 -1
- package/src/lib/transfer/fields.js.map +1 -1
- package/src/lib/transfer/pipelines.js +4 -2
- package/src/lib/transfer/pipelines.js.map +1 -1
|
@@ -39,15 +39,28 @@ import { type PluginAiCapability, type PluginAiCapabilityArgs } from '@aglyn/agl
|
|
|
39
39
|
/** The operations' names in a build plan. */
|
|
40
40
|
export declare const CRM_PIPELINE_AI_OP = "crm-pipeline";
|
|
41
41
|
export declare const CRM_FIELD_AI_OP = "crm-field";
|
|
42
|
+
export declare const CRM_PICKLIST_VALUES_AI_OP = "crm-picklist-values";
|
|
42
43
|
/** How many of each one build may set up. */
|
|
43
44
|
export declare const CRM_PIPELINE_AI_MAX_PER_PLAN = 3;
|
|
44
45
|
export declare const CRM_FIELD_AI_MAX_PER_PLAN = 12;
|
|
46
|
+
export declare const CRM_PICKLIST_VALUES_AI_MAX_PER_PLAN = 12;
|
|
45
47
|
/** The pipeline writer's content for an item: its arguments, as they are. Pure. */
|
|
46
48
|
export declare function crmPipelineSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>>;
|
|
47
49
|
/** The field writer's content for an item: its arguments, as they are. Pure. */
|
|
48
50
|
export declare function crmFieldSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>>;
|
|
49
51
|
export declare const crmPipelineAiCapability: PluginAiCapability;
|
|
50
52
|
export declare const crmFieldAiCapability: PluginAiCapability;
|
|
53
|
+
/** The picklist values writer's content for an item: its arguments, as they are. Pure. */
|
|
54
|
+
export declare function crmPicklistValuesSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>>;
|
|
55
|
+
/**
|
|
56
|
+
* VALUES ADDED TO A CRM PICKLIST (AGL-3756): "add Nurturing as a lead
|
|
57
|
+
* status", "add Trade show and Podcast as lead sources", "add Renewal as a
|
|
58
|
+
* deal type", "add Enterprise to the Tier choices on companies". Written by
|
|
59
|
+
* the `crm-picklist-values` writer, so the rules are the Fields page's: only
|
|
60
|
+
* adds, matched without capitals, never removes, renames, reorders or
|
|
61
|
+
* reactivates. Live, as the console's Add value is; 0 credits.
|
|
62
|
+
*/
|
|
63
|
+
export declare const crmPicklistValuesAiCapability: PluginAiCapability;
|
|
51
64
|
/**
|
|
52
65
|
* Registers both capabilities; the console-server declarations call it beside
|
|
53
66
|
* the writers, since only the console runs AI jobs (AGL-3026). Idempotent.
|
|
@@ -18,6 +18,7 @@ import { CRM_FIELD_OBJECTS } from "@aglyn/aglyn/app-utils/crm";
|
|
|
18
18
|
import { registerPluginAiCapability } from "@aglyn/aglyn/plugin-manager/plugin-ai-capabilities";
|
|
19
19
|
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
20
20
|
import { CRM_FIELD_LABEL_MAX_LENGTH, CRM_FIELD_OPTION_MAX_LENGTH, CRM_FIELD_OPTIONS_MAX, CRM_FIELD_SETUP_RESOURCE, CRM_PIPELINE_SETUP_RESOURCE, CRM_SETUP_OPEN_STAGES_MAX, CRM_SETUP_STAGE_NAME_MAX } from "../model/crm-setup-content.js";
|
|
21
|
+
import { CRM_PICKLIST_VALUES_LISTS, CRM_PICKLIST_VALUES_SETUP_MAX, CRM_PICKLIST_VALUES_SETUP_RESOURCE } from "../model/crm-picklist-setup-content.js";
|
|
21
22
|
import { PIPELINE_NAME_MAX } from "../model/deal-board-model.js";
|
|
22
23
|
/**
|
|
23
24
|
* WHAT AN AI BUILD CAN SET UP IN THE CRM (AGL-3746): a deal pipeline with its
|
|
@@ -41,8 +42,10 @@ import { PIPELINE_NAME_MAX } from "../model/deal-board-model.js";
|
|
|
41
42
|
* descriptions say so, since a request may use either word.
|
|
42
43
|
*/ /** The operations' names in a build plan. */ export const CRM_PIPELINE_AI_OP = 'crm-pipeline';
|
|
43
44
|
export const CRM_FIELD_AI_OP = 'crm-field';
|
|
45
|
+
export const CRM_PICKLIST_VALUES_AI_OP = 'crm-picklist-values';
|
|
44
46
|
/** How many of each one build may set up. */ export const CRM_PIPELINE_AI_MAX_PER_PLAN = 3;
|
|
45
47
|
export const CRM_FIELD_AI_MAX_PER_PLAN = 12;
|
|
48
|
+
export const CRM_PICKLIST_VALUES_AI_MAX_PER_PLAN = 12;
|
|
46
49
|
/** The CRM is included from Starter (`crm`); Free has none of it. */ const CRM_FEATURE = 'crm';
|
|
47
50
|
function pick(args, keys) {
|
|
48
51
|
return Object.fromEntries(keys.filter((key)=>args[key] !== undefined && args[key] !== '').map((key)=>[
|
|
@@ -66,6 +69,7 @@ function pick(args, keys) {
|
|
|
66
69
|
'label',
|
|
67
70
|
'type',
|
|
68
71
|
'options',
|
|
72
|
+
'currency',
|
|
69
73
|
'required'
|
|
70
74
|
]);
|
|
71
75
|
}
|
|
@@ -133,7 +137,7 @@ export const crmFieldAiCapability = {
|
|
|
133
137
|
noun: 'CRM custom field',
|
|
134
138
|
where: 'CRM → Fields',
|
|
135
139
|
intents: [
|
|
136
|
-
'CRM custom fields on contacts, companies (accounts), deals (opportunities) or leads — "add a Budget field to opportunities", "a Choice field for Industry on companies"; types text, number, date, choice, checkbox and
|
|
140
|
+
'CRM custom fields on contacts, companies (accounts), deals (opportunities) or leads — "add a Budget field to opportunities", "a Choice field for Industry on companies"; types text, number, date, choice, checkbox, link and currency (an amount of money: budget, deal size, revenue)'
|
|
137
141
|
],
|
|
138
142
|
argsSchema: {
|
|
139
143
|
type: 'object',
|
|
@@ -150,7 +154,7 @@ export const crmFieldAiCapability = {
|
|
|
150
154
|
},
|
|
151
155
|
type: {
|
|
152
156
|
type: 'string',
|
|
153
|
-
description: 'What it holds: text; number (
|
|
157
|
+
description: 'What it holds: text; number (a count or a percentage); currency (an amount of money — a budget, a price, revenue — with "currency"); date; select (a Choice from listed options); checkbox (yes or no); url (a link).',
|
|
154
158
|
enum: CONTACT_FIELD_TYPES
|
|
155
159
|
},
|
|
156
160
|
options: {
|
|
@@ -162,6 +166,11 @@ export const crmFieldAiCapability = {
|
|
|
162
166
|
},
|
|
163
167
|
maxItems: CRM_FIELD_OPTIONS_MAX
|
|
164
168
|
},
|
|
169
|
+
currency: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: 'For currency only: the ISO 4217 code its amounts are in, e.g. "EUR", when the request names one. Leave out for US dollars.',
|
|
172
|
+
maxLength: 3
|
|
173
|
+
},
|
|
165
174
|
required: {
|
|
166
175
|
type: 'boolean',
|
|
167
176
|
description: 'true only when the request says the field must always be filled in.'
|
|
@@ -183,6 +192,86 @@ export const crmFieldAiCapability = {
|
|
|
183
192
|
degrade: 'omit',
|
|
184
193
|
draftContent: (item)=>crmFieldSetupContentFromArgs(item.args)
|
|
185
194
|
};
|
|
195
|
+
/** The picklist values writer's content for an item: its arguments, as they are. Pure. */ export function crmPicklistValuesSetupContentFromArgs(args) {
|
|
196
|
+
return pick(args, [
|
|
197
|
+
'list',
|
|
198
|
+
'object',
|
|
199
|
+
'field',
|
|
200
|
+
'values',
|
|
201
|
+
'meaning',
|
|
202
|
+
'group'
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* VALUES ADDED TO A CRM PICKLIST (AGL-3756): "add Nurturing as a lead
|
|
207
|
+
* status", "add Trade show and Podcast as lead sources", "add Renewal as a
|
|
208
|
+
* deal type", "add Enterprise to the Tier choices on companies". Written by
|
|
209
|
+
* the `crm-picklist-values` writer, so the rules are the Fields page's: only
|
|
210
|
+
* adds, matched without capitals, never removes, renames, reorders or
|
|
211
|
+
* reactivates. Live, as the console's Add value is; 0 credits.
|
|
212
|
+
*/ export const crmPicklistValuesAiCapability = {
|
|
213
|
+
op: CRM_PICKLIST_VALUES_AI_OP,
|
|
214
|
+
noun: 'CRM picklist values',
|
|
215
|
+
where: 'CRM → Fields',
|
|
216
|
+
intents: [
|
|
217
|
+
'add values to a CRM picklist — a lead status ("Contacted", meaning Working), a lead source (Inbound or Outbound), a deal (opportunity) type or contact role, a company (account) type, industry, rating or ownership, a salutation, a task status, priority, type or subject — or add choices to a custom Choice field; only adds, never removes or renames'
|
|
218
|
+
],
|
|
219
|
+
argsSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
list: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Which list: leadStatus, leadSource, opportunityType (a deal’s Type), opportunityContactRole, accountType (a company’s Type), industry, rating, ownership, salutation, taskStatus, taskPriority, taskType, taskSubject; or custom for a custom Choice field (then give object and field).',
|
|
225
|
+
enum: CRM_PICKLIST_VALUES_LISTS
|
|
226
|
+
},
|
|
227
|
+
object: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
description: 'For custom only: the record the Choice field is on — contact; company; deal; lead.',
|
|
230
|
+
enum: CRM_FIELD_OBJECTS
|
|
231
|
+
},
|
|
232
|
+
field: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: 'For custom only: the Choice field’s name, e.g. "Tier".',
|
|
235
|
+
maxLength: 80
|
|
236
|
+
},
|
|
237
|
+
values: {
|
|
238
|
+
type: 'array',
|
|
239
|
+
description: 'The values to add, in order, e.g. ["Trade show", "Podcast"]. A value the list already has (any capitals) is left as it is. Never values to remove or rename.',
|
|
240
|
+
items: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
maxLength: 120
|
|
243
|
+
},
|
|
244
|
+
maxItems: CRM_PICKLIST_VALUES_SETUP_MAX
|
|
245
|
+
},
|
|
246
|
+
meaning: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
description: 'Required for leadStatus, taskStatus, taskPriority and taskType: what every value in this item means. leadStatus: new, nurturing, working or unqualified (never qualified); taskStatus: open or done; taskPriority: low, normal or high; taskType: call, email, meeting or todo. Split values of different meanings into separate items.',
|
|
249
|
+
maxLength: 40
|
|
250
|
+
},
|
|
251
|
+
group: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'For leadSource only: inbound (the lead came to them) or outbound (they went to the lead), when the request says or it is plain.',
|
|
254
|
+
enum: [
|
|
255
|
+
'inbound',
|
|
256
|
+
'outbound'
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
required: [
|
|
261
|
+
'list',
|
|
262
|
+
'values'
|
|
263
|
+
],
|
|
264
|
+
additionalProperties: false
|
|
265
|
+
},
|
|
266
|
+
maxPerPlan: CRM_PICKLIST_VALUES_AI_MAX_PER_PLAN,
|
|
267
|
+
freeAllowed: false,
|
|
268
|
+
feature: CRM_FEATURE,
|
|
269
|
+
draftResource: CRM_PICKLIST_VALUES_SETUP_RESOURCE,
|
|
270
|
+
live: true,
|
|
271
|
+
estimateCredits: ()=>0,
|
|
272
|
+
degrade: 'omit',
|
|
273
|
+
draftContent: (item)=>crmPicklistValuesSetupContentFromArgs(item.args)
|
|
274
|
+
};
|
|
186
275
|
/**
|
|
187
276
|
* Registers both capabilities; the console-server declarations call it beside
|
|
188
277
|
* the writers, since only the console runs AI jobs (AGL-3026). Idempotent.
|
|
@@ -193,6 +282,9 @@ export const crmFieldAiCapability = {
|
|
|
193
282
|
registerPluginAiCapability(crmFieldAiCapability, {
|
|
194
283
|
pluginId: BUNDLE_ID
|
|
195
284
|
});
|
|
285
|
+
registerPluginAiCapability(crmPicklistValuesAiCapability, {
|
|
286
|
+
pluginId: BUNDLE_ID
|
|
287
|
+
});
|
|
196
288
|
}
|
|
197
289
|
|
|
198
290
|
//# sourceMappingURL=crm-setup-ai-capabilities.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/crm-setup-ai-capabilities.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 { CONTACT_FIELD_TYPES } from '@aglyn/aglyn/app-utils/contact-custom-fields'\nimport { CRM_FIELD_OBJECTS } from '@aglyn/aglyn/app-utils/crm'\nimport {\n registerPluginAiCapability,\n type PluginAiCapability,\n type PluginAiCapabilityArgs,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n CRM_FIELD_LABEL_MAX_LENGTH,\n CRM_FIELD_OPTION_MAX_LENGTH,\n CRM_FIELD_OPTIONS_MAX,\n CRM_FIELD_SETUP_RESOURCE,\n CRM_PIPELINE_SETUP_RESOURCE,\n CRM_SETUP_OPEN_STAGES_MAX,\n CRM_SETUP_STAGE_NAME_MAX,\n} from '../model/crm-setup-content'\nimport { PIPELINE_NAME_MAX } from '../model/deal-board-model'\n\n/**\n * WHAT AN AI BUILD CAN SET UP IN THE CRM (AGL-3746): a deal pipeline with its\n * stages, and a custom field on a contact, company, deal or lead.\n *\n * The capabilities the AI plugin's build planner offers for \"set up a sales\n * pipeline for my agency: Discovery, Proposal, Negotiation, Won/Lost\" or\n * \"add a Budget field to deals\". Each item is written by this plugin's\n * `crm-pipeline` or `crm-field` writer, so every rule is the writer's and the\n * console's: the `crm` plan feature, an owner, admin or editor with\n * `data.manage`, the scope, `stagesProblem`, the field types and the org's\n * 100-field ceiling. Both are saved live, as the console saves them (`live`):\n * neither has a draft state. Matched by name, so asking again changes\n * nothing twice.\n *\n * The arguments are flat, as the contract requires, and are the setup's own\n * fields; the planner fills them, so no model runs (`estimateCredits` 0). No\n * page block shows either, so neither changes what a visitor sees.\n *\n * A Salesforce \"opportunity\" is a deal here and an \"account\" a company; the\n * descriptions say so, since a request may use either word.\n */\n\n/** The operations' names in a build plan. */\nexport const CRM_PIPELINE_AI_OP = 'crm-pipeline'\nexport const CRM_FIELD_AI_OP = 'crm-field'\n\n/** How many of each one build may set up. */\nexport const CRM_PIPELINE_AI_MAX_PER_PLAN = 3\nexport const CRM_FIELD_AI_MAX_PER_PLAN = 12\n\n/** The CRM is included from Starter (`crm`); Free has none of it. */\nconst CRM_FEATURE = 'crm'\n\nfunction pick(args: PluginAiCapabilityArgs, keys: readonly string[]): Record<string, unknown> {\n return Object.fromEntries(keys.filter((key) => args[key] !== undefined && args[key] !== '').map((key) => [key, args[key]]))\n}\n\n/** The pipeline writer's content for an item: its arguments, as they are. Pure. */\nexport function crmPipelineSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>> {\n return pick(args, ['name', 'renameFrom', 'stages', 'wonStage', 'lostStage', 'makeDefault'])\n}\n\n/** The field writer's content for an item: its arguments, as they are. Pure. */\nexport function crmFieldSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>> {\n return pick(args, ['object', 'label', 'type', 'options', 'required'])\n}\n\nexport const crmPipelineAiCapability: PluginAiCapability = {\n op: CRM_PIPELINE_AI_OP,\n noun: 'sales pipeline',\n where: 'CRM → Deals → Pipelines',\n intents: [\n 'CRM deal pipelines and their stages — \"set up a sales pipeline: Discovery, Proposal, Negotiation, Won/Lost\", rename a pipeline, add or reorder stages, set a stage’s win probability, make a pipeline the default; a Salesforce opportunity pipeline is a deal pipeline',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description:\n 'The pipeline’s name, e.g. \"Agency sales\". An existing pipeline of this name (any case) is updated rather than duplicated; give \"renameFrom\" to rename one.',\n maxLength: PIPELINE_NAME_MAX,\n },\n renameFrom: {\n type: 'string',\n description: 'Only to rename: the current name of the pipeline that becomes \"name\".',\n maxLength: PIPELINE_NAME_MAX,\n },\n stages: {\n type: 'array',\n description:\n 'The OPEN stages a deal moves through, in order, each a name with an optional win probability in parentheses, e.g. \"Discovery (20%)\". Never Won or Lost: every pipeline already has one of each (name them in wonStage and lostStage). Leave out to keep the stages as they are (a new pipeline then gets the standard set). Stages already in the pipeline and not listed are kept.',\n items: { type: 'string', maxLength: CRM_SETUP_STAGE_NAME_MAX + 8 },\n maxItems: CRM_SETUP_OPEN_STAGES_MAX,\n },\n wonStage: {\n type: 'string',\n description: 'What the closing Won stage is called, where the request names it, e.g. \"Won\". Leave out to keep \"Closed Won\".',\n maxLength: CRM_SETUP_STAGE_NAME_MAX,\n },\n lostStage: {\n type: 'string',\n description: 'What the closing Lost stage is called, where the request names it, e.g. \"Lost\". Leave out to keep \"Closed Lost\".',\n maxLength: CRM_SETUP_STAGE_NAME_MAX,\n },\n makeDefault: {\n type: 'boolean',\n description: 'true only when the request says new deals should land in this pipeline (its main or default pipeline).',\n },\n },\n required: ['name'],\n additionalProperties: false,\n },\n maxPerPlan: CRM_PIPELINE_AI_MAX_PER_PLAN,\n freeAllowed: false,\n feature: CRM_FEATURE,\n draftResource: CRM_PIPELINE_SETUP_RESOURCE,\n live: true,\n // Written without a model: the planner already filled the arguments.\n estimateCredits: () => 0,\n degrade: 'omit',\n draftContent: (item) => crmPipelineSetupContentFromArgs(item.args),\n}\n\nexport const crmFieldAiCapability: PluginAiCapability = {\n op: CRM_FIELD_AI_OP,\n noun: 'CRM custom field',\n where: 'CRM → Fields',\n intents: [\n 'CRM custom fields on contacts, companies (accounts), deals (opportunities) or leads — \"add a Budget field to opportunities\", \"a Choice field for Industry on companies\"; types text, number, date, choice, checkbox and link only',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n object: {\n type: 'string',\n description: 'The record it describes: contact; company (an account); deal (an opportunity); lead.',\n enum: CRM_FIELD_OBJECTS,\n },\n label: {\n type: 'string',\n description:\n 'The field’s name as people read it, e.g. \"Budget\". A field of this name on the same record (any case) is left as it is rather than duplicated.',\n maxLength: CRM_FIELD_LABEL_MAX_LENGTH,\n },\n type: {\n type: 'string',\n description:\n 'What it holds: text; number (also an amount of money or a percentage — there is no currency type); date; select (a Choice from listed options); checkbox (yes or no); url (a link).',\n enum: CONTACT_FIELD_TYPES,\n },\n options: {\n type: 'array',\n description: 'For select only: the choices, in order. A select field that exists gains the ones it lacks.',\n items: { type: 'string', maxLength: CRM_FIELD_OPTION_MAX_LENGTH },\n maxItems: CRM_FIELD_OPTIONS_MAX,\n },\n required: {\n type: 'boolean',\n description: 'true only when the request says the field must always be filled in.',\n },\n },\n required: ['object', 'label', 'type'],\n additionalProperties: false,\n },\n maxPerPlan: CRM_FIELD_AI_MAX_PER_PLAN,\n freeAllowed: false,\n feature: CRM_FEATURE,\n draftResource: CRM_FIELD_SETUP_RESOURCE,\n live: true,\n estimateCredits: () => 0,\n degrade: 'omit',\n draftContent: (item) => crmFieldSetupContentFromArgs(item.args),\n}\n\n/**\n * Registers both capabilities; the console-server declarations call it beside\n * the writers, since only the console runs AI jobs (AGL-3026). Idempotent.\n */\nexport function registerCrmSetupAiCapabilities(): void {\n registerPluginAiCapability(crmPipelineAiCapability, { pluginId: BUNDLE_ID })\n registerPluginAiCapability(crmFieldAiCapability, { pluginId: BUNDLE_ID })\n}\n"],"names":["CONTACT_FIELD_TYPES","CRM_FIELD_OBJECTS","registerPluginAiCapability","BUNDLE_ID","CRM_FIELD_LABEL_MAX_LENGTH","CRM_FIELD_OPTION_MAX_LENGTH","CRM_FIELD_OPTIONS_MAX","CRM_FIELD_SETUP_RESOURCE","CRM_PIPELINE_SETUP_RESOURCE","CRM_SETUP_OPEN_STAGES_MAX","CRM_SETUP_STAGE_NAME_MAX","PIPELINE_NAME_MAX","CRM_PIPELINE_AI_OP","CRM_FIELD_AI_OP","CRM_PIPELINE_AI_MAX_PER_PLAN","CRM_FIELD_AI_MAX_PER_PLAN","CRM_FEATURE","pick","args","keys","Object","fromEntries","filter","key","undefined","map","crmPipelineSetupContentFromArgs","crmFieldSetupContentFromArgs","crmPipelineAiCapability","op","noun","where","intents","argsSchema","type","properties","name","description","maxLength","renameFrom","stages","items","maxItems","wonStage","lostStage","makeDefault","required","additionalProperties","maxPerPlan","freeAllowed","feature","draftResource","live","estimateCredits","degrade","draftContent","item","crmFieldAiCapability","object","enum","label","options","registerCrmSetupAiCapabilities","pluginId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/crm-setup-ai-capabilities.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 { CONTACT_FIELD_TYPES } from '@aglyn/aglyn/app-utils/contact-custom-fields'\nimport { CRM_FIELD_OBJECTS } from '@aglyn/aglyn/app-utils/crm'\nimport {\n registerPluginAiCapability,\n type PluginAiCapability,\n type PluginAiCapabilityArgs,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n CRM_FIELD_LABEL_MAX_LENGTH,\n CRM_FIELD_OPTION_MAX_LENGTH,\n CRM_FIELD_OPTIONS_MAX,\n CRM_FIELD_SETUP_RESOURCE,\n CRM_PIPELINE_SETUP_RESOURCE,\n CRM_SETUP_OPEN_STAGES_MAX,\n CRM_SETUP_STAGE_NAME_MAX,\n} from '../model/crm-setup-content'\nimport {\n CRM_PICKLIST_VALUES_LISTS,\n CRM_PICKLIST_VALUES_SETUP_MAX,\n CRM_PICKLIST_VALUES_SETUP_RESOURCE,\n} from '../model/crm-picklist-setup-content'\nimport { PIPELINE_NAME_MAX } from '../model/deal-board-model'\n\n/**\n * WHAT AN AI BUILD CAN SET UP IN THE CRM (AGL-3746): a deal pipeline with its\n * stages, and a custom field on a contact, company, deal or lead.\n *\n * The capabilities the AI plugin's build planner offers for \"set up a sales\n * pipeline for my agency: Discovery, Proposal, Negotiation, Won/Lost\" or\n * \"add a Budget field to deals\". Each item is written by this plugin's\n * `crm-pipeline` or `crm-field` writer, so every rule is the writer's and the\n * console's: the `crm` plan feature, an owner, admin or editor with\n * `data.manage`, the scope, `stagesProblem`, the field types and the org's\n * 100-field ceiling. Both are saved live, as the console saves them (`live`):\n * neither has a draft state. Matched by name, so asking again changes\n * nothing twice.\n *\n * The arguments are flat, as the contract requires, and are the setup's own\n * fields; the planner fills them, so no model runs (`estimateCredits` 0). No\n * page block shows either, so neither changes what a visitor sees.\n *\n * A Salesforce \"opportunity\" is a deal here and an \"account\" a company; the\n * descriptions say so, since a request may use either word.\n */\n\n/** The operations' names in a build plan. */\nexport const CRM_PIPELINE_AI_OP = 'crm-pipeline'\nexport const CRM_FIELD_AI_OP = 'crm-field'\nexport const CRM_PICKLIST_VALUES_AI_OP = 'crm-picklist-values'\n\n/** How many of each one build may set up. */\nexport const CRM_PIPELINE_AI_MAX_PER_PLAN = 3\nexport const CRM_FIELD_AI_MAX_PER_PLAN = 12\nexport const CRM_PICKLIST_VALUES_AI_MAX_PER_PLAN = 12\n\n/** The CRM is included from Starter (`crm`); Free has none of it. */\nconst CRM_FEATURE = 'crm'\n\nfunction pick(args: PluginAiCapabilityArgs, keys: readonly string[]): Record<string, unknown> {\n return Object.fromEntries(keys.filter((key) => args[key] !== undefined && args[key] !== '').map((key) => [key, args[key]]))\n}\n\n/** The pipeline writer's content for an item: its arguments, as they are. Pure. */\nexport function crmPipelineSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>> {\n return pick(args, ['name', 'renameFrom', 'stages', 'wonStage', 'lostStage', 'makeDefault'])\n}\n\n/** The field writer's content for an item: its arguments, as they are. Pure. */\nexport function crmFieldSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>> {\n return pick(args, ['object', 'label', 'type', 'options', 'currency', 'required'])\n}\n\nexport const crmPipelineAiCapability: PluginAiCapability = {\n op: CRM_PIPELINE_AI_OP,\n noun: 'sales pipeline',\n where: 'CRM → Deals → Pipelines',\n intents: [\n 'CRM deal pipelines and their stages — \"set up a sales pipeline: Discovery, Proposal, Negotiation, Won/Lost\", rename a pipeline, add or reorder stages, set a stage’s win probability, make a pipeline the default; a Salesforce opportunity pipeline is a deal pipeline',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description:\n 'The pipeline’s name, e.g. \"Agency sales\". An existing pipeline of this name (any case) is updated rather than duplicated; give \"renameFrom\" to rename one.',\n maxLength: PIPELINE_NAME_MAX,\n },\n renameFrom: {\n type: 'string',\n description: 'Only to rename: the current name of the pipeline that becomes \"name\".',\n maxLength: PIPELINE_NAME_MAX,\n },\n stages: {\n type: 'array',\n description:\n 'The OPEN stages a deal moves through, in order, each a name with an optional win probability in parentheses, e.g. \"Discovery (20%)\". Never Won or Lost: every pipeline already has one of each (name them in wonStage and lostStage). Leave out to keep the stages as they are (a new pipeline then gets the standard set). Stages already in the pipeline and not listed are kept.',\n items: { type: 'string', maxLength: CRM_SETUP_STAGE_NAME_MAX + 8 },\n maxItems: CRM_SETUP_OPEN_STAGES_MAX,\n },\n wonStage: {\n type: 'string',\n description: 'What the closing Won stage is called, where the request names it, e.g. \"Won\". Leave out to keep \"Closed Won\".',\n maxLength: CRM_SETUP_STAGE_NAME_MAX,\n },\n lostStage: {\n type: 'string',\n description: 'What the closing Lost stage is called, where the request names it, e.g. \"Lost\". Leave out to keep \"Closed Lost\".',\n maxLength: CRM_SETUP_STAGE_NAME_MAX,\n },\n makeDefault: {\n type: 'boolean',\n description: 'true only when the request says new deals should land in this pipeline (its main or default pipeline).',\n },\n },\n required: ['name'],\n additionalProperties: false,\n },\n maxPerPlan: CRM_PIPELINE_AI_MAX_PER_PLAN,\n freeAllowed: false,\n feature: CRM_FEATURE,\n draftResource: CRM_PIPELINE_SETUP_RESOURCE,\n live: true,\n // Written without a model: the planner already filled the arguments.\n estimateCredits: () => 0,\n degrade: 'omit',\n draftContent: (item) => crmPipelineSetupContentFromArgs(item.args),\n}\n\nexport const crmFieldAiCapability: PluginAiCapability = {\n op: CRM_FIELD_AI_OP,\n noun: 'CRM custom field',\n where: 'CRM → Fields',\n intents: [\n 'CRM custom fields on contacts, companies (accounts), deals (opportunities) or leads — \"add a Budget field to opportunities\", \"a Choice field for Industry on companies\"; types text, number, date, choice, checkbox, link and currency (an amount of money: budget, deal size, revenue)',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n object: {\n type: 'string',\n description: 'The record it describes: contact; company (an account); deal (an opportunity); lead.',\n enum: CRM_FIELD_OBJECTS,\n },\n label: {\n type: 'string',\n description:\n 'The field’s name as people read it, e.g. \"Budget\". A field of this name on the same record (any case) is left as it is rather than duplicated.',\n maxLength: CRM_FIELD_LABEL_MAX_LENGTH,\n },\n type: {\n type: 'string',\n description:\n 'What it holds: text; number (a count or a percentage); currency (an amount of money — a budget, a price, revenue — with \"currency\"); date; select (a Choice from listed options); checkbox (yes or no); url (a link).',\n enum: CONTACT_FIELD_TYPES,\n },\n options: {\n type: 'array',\n description: 'For select only: the choices, in order. A select field that exists gains the ones it lacks.',\n items: { type: 'string', maxLength: CRM_FIELD_OPTION_MAX_LENGTH },\n maxItems: CRM_FIELD_OPTIONS_MAX,\n },\n currency: {\n type: 'string',\n description:\n 'For currency only: the ISO 4217 code its amounts are in, e.g. \"EUR\", when the request names one. Leave out for US dollars.',\n maxLength: 3,\n },\n required: {\n type: 'boolean',\n description: 'true only when the request says the field must always be filled in.',\n },\n },\n required: ['object', 'label', 'type'],\n additionalProperties: false,\n },\n maxPerPlan: CRM_FIELD_AI_MAX_PER_PLAN,\n freeAllowed: false,\n feature: CRM_FEATURE,\n draftResource: CRM_FIELD_SETUP_RESOURCE,\n live: true,\n estimateCredits: () => 0,\n degrade: 'omit',\n draftContent: (item) => crmFieldSetupContentFromArgs(item.args),\n}\n\n/** The picklist values writer's content for an item: its arguments, as they are. Pure. */\nexport function crmPicklistValuesSetupContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>> {\n return pick(args, ['list', 'object', 'field', 'values', 'meaning', 'group'])\n}\n\n/**\n * VALUES ADDED TO A CRM PICKLIST (AGL-3756): \"add Nurturing as a lead\n * status\", \"add Trade show and Podcast as lead sources\", \"add Renewal as a\n * deal type\", \"add Enterprise to the Tier choices on companies\". Written by\n * the `crm-picklist-values` writer, so the rules are the Fields page's: only\n * adds, matched without capitals, never removes, renames, reorders or\n * reactivates. Live, as the console's Add value is; 0 credits.\n */\nexport const crmPicklistValuesAiCapability: PluginAiCapability = {\n op: CRM_PICKLIST_VALUES_AI_OP,\n noun: 'CRM picklist values',\n where: 'CRM → Fields',\n intents: [\n 'add values to a CRM picklist — a lead status (\"Contacted\", meaning Working), a lead source (Inbound or Outbound), a deal (opportunity) type or contact role, a company (account) type, industry, rating or ownership, a salutation, a task status, priority, type or subject — or add choices to a custom Choice field; only adds, never removes or renames',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n list: {\n type: 'string',\n description:\n 'Which list: leadStatus, leadSource, opportunityType (a deal’s Type), opportunityContactRole, accountType (a company’s Type), industry, rating, ownership, salutation, taskStatus, taskPriority, taskType, taskSubject; or custom for a custom Choice field (then give object and field).',\n enum: CRM_PICKLIST_VALUES_LISTS,\n },\n object: {\n type: 'string',\n description: 'For custom only: the record the Choice field is on — contact; company; deal; lead.',\n enum: CRM_FIELD_OBJECTS,\n },\n field: {\n type: 'string',\n description: 'For custom only: the Choice field’s name, e.g. \"Tier\".',\n maxLength: 80,\n },\n values: {\n type: 'array',\n description:\n 'The values to add, in order, e.g. [\"Trade show\", \"Podcast\"]. A value the list already has (any capitals) is left as it is. Never values to remove or rename.',\n items: { type: 'string', maxLength: 120 },\n maxItems: CRM_PICKLIST_VALUES_SETUP_MAX,\n },\n meaning: {\n type: 'string',\n description:\n 'Required for leadStatus, taskStatus, taskPriority and taskType: what every value in this item means. leadStatus: new, nurturing, working or unqualified (never qualified); taskStatus: open or done; taskPriority: low, normal or high; taskType: call, email, meeting or todo. Split values of different meanings into separate items.',\n maxLength: 40,\n },\n group: {\n type: 'string',\n description: 'For leadSource only: inbound (the lead came to them) or outbound (they went to the lead), when the request says or it is plain.',\n enum: ['inbound', 'outbound'],\n },\n },\n required: ['list', 'values'],\n additionalProperties: false,\n },\n maxPerPlan: CRM_PICKLIST_VALUES_AI_MAX_PER_PLAN,\n freeAllowed: false,\n feature: CRM_FEATURE,\n draftResource: CRM_PICKLIST_VALUES_SETUP_RESOURCE,\n live: true,\n estimateCredits: () => 0,\n degrade: 'omit',\n draftContent: (item) => crmPicklistValuesSetupContentFromArgs(item.args),\n}\n\n/**\n * Registers both capabilities; the console-server declarations call it beside\n * the writers, since only the console runs AI jobs (AGL-3026). Idempotent.\n */\nexport function registerCrmSetupAiCapabilities(): void {\n registerPluginAiCapability(crmPipelineAiCapability, { pluginId: BUNDLE_ID })\n registerPluginAiCapability(crmFieldAiCapability, { pluginId: BUNDLE_ID })\n registerPluginAiCapability(crmPicklistValuesAiCapability, { pluginId: BUNDLE_ID })\n}\n"],"names":["CONTACT_FIELD_TYPES","CRM_FIELD_OBJECTS","registerPluginAiCapability","BUNDLE_ID","CRM_FIELD_LABEL_MAX_LENGTH","CRM_FIELD_OPTION_MAX_LENGTH","CRM_FIELD_OPTIONS_MAX","CRM_FIELD_SETUP_RESOURCE","CRM_PIPELINE_SETUP_RESOURCE","CRM_SETUP_OPEN_STAGES_MAX","CRM_SETUP_STAGE_NAME_MAX","CRM_PICKLIST_VALUES_LISTS","CRM_PICKLIST_VALUES_SETUP_MAX","CRM_PICKLIST_VALUES_SETUP_RESOURCE","PIPELINE_NAME_MAX","CRM_PIPELINE_AI_OP","CRM_FIELD_AI_OP","CRM_PICKLIST_VALUES_AI_OP","CRM_PIPELINE_AI_MAX_PER_PLAN","CRM_FIELD_AI_MAX_PER_PLAN","CRM_PICKLIST_VALUES_AI_MAX_PER_PLAN","CRM_FEATURE","pick","args","keys","Object","fromEntries","filter","key","undefined","map","crmPipelineSetupContentFromArgs","crmFieldSetupContentFromArgs","crmPipelineAiCapability","op","noun","where","intents","argsSchema","type","properties","name","description","maxLength","renameFrom","stages","items","maxItems","wonStage","lostStage","makeDefault","required","additionalProperties","maxPerPlan","freeAllowed","feature","draftResource","live","estimateCredits","degrade","draftContent","item","crmFieldAiCapability","object","enum","label","options","currency","crmPicklistValuesSetupContentFromArgs","crmPicklistValuesAiCapability","list","field","values","meaning","group","registerCrmSetupAiCapabilities","pluginId"],"mappings":"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"}
|
|
@@ -14,6 +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 CrmPicklist, type CrmPicklistId, type CrmPicklistObject, type CrmPicklistTarget } from '@aglyn/aglyn/app-utils/crm';
|
|
17
18
|
import type { PluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
|
|
18
19
|
import { type CrmFieldDefinitionDoc } from '../model/crm-setup-content';
|
|
19
20
|
import type { PipelineDoc } from '../model/deal-board-model';
|
|
@@ -49,6 +50,7 @@ export declare const CRM_SETUP_ROLE_REFUSAL = "Editing the CRM requires the edit
|
|
|
49
50
|
export declare const CRM_SETUP_PERMISSION_REFUSAL = "Your organization role does not allow editing organization data";
|
|
50
51
|
export declare const CRM_SETUP_SCOPE_REFUSAL = "Your access to this organization does not reach that site";
|
|
51
52
|
export declare const CRM_SETUP_FIELD_SCOPE_REFUSAL = "Custom fields are organization-wide, and your access reaches only some sites";
|
|
53
|
+
export declare const CRM_SETUP_PICKLIST_SCOPE_REFUSAL = "Picklists are organization-wide, and your access reaches only some sites";
|
|
52
54
|
/** Who the member is in the org, as the writers ask. */
|
|
53
55
|
export interface CrmSetupMember {
|
|
54
56
|
role: string;
|
|
@@ -73,6 +75,21 @@ export interface CrmSetupStore {
|
|
|
73
75
|
field(orgId: string, id: string): Promise<CrmFieldDefinitionDoc | null>;
|
|
74
76
|
createField(orgId: string, id: string, document: Record<string, unknown>): Promise<void>;
|
|
75
77
|
setFieldOptions(orgId: string, id: string, options: string[], now: Date): Promise<void>;
|
|
78
|
+
/** A picklist's stored document, or `null` for an org that never stored one (AGL-3756). */
|
|
79
|
+
picklist(orgId: string, id: CrmPicklistId): Promise<Readonly<Record<string, unknown>> | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Read a picklist and write it back in one transaction, as the console's
|
|
82
|
+
* route does, so a person editing the list at the same moment is not
|
|
83
|
+
* overwritten. `change` answers the list to write, or `null` for none.
|
|
84
|
+
*/
|
|
85
|
+
changePicklist<T>(orgId: string, id: CrmPicklistId, change: (stored: Readonly<Record<string, unknown>> | null) => {
|
|
86
|
+
write: CrmPicklist | null;
|
|
87
|
+
result: T;
|
|
88
|
+
}, stamp: {
|
|
89
|
+
hostId: string;
|
|
90
|
+
}): Promise<T>;
|
|
91
|
+
/** A value's group onto every record of `object` holding `label` (a lead's `leadSourceDirection`). */
|
|
92
|
+
regroup(orgId: string, object: CrmPicklistObject, target: CrmPicklistTarget, label: string, group: string | null): Promise<number>;
|
|
76
93
|
}
|
|
77
94
|
export interface CrmSetupWriterDeps {
|
|
78
95
|
store?: CrmSetupStore;
|
|
@@ -89,4 +106,15 @@ export declare function createCrmPipelineSetupWriter(deps?: CrmSetupWriterDeps):
|
|
|
89
106
|
export declare function createCrmFieldSetupWriter(deps?: CrmSetupWriterDeps): PluginResourceDraftWriter;
|
|
90
107
|
export declare const crmPipelineSetupWriter: PluginResourceDraftWriter;
|
|
91
108
|
export declare const crmFieldSetupWriter: PluginResourceDraftWriter;
|
|
109
|
+
/**
|
|
110
|
+
* The `crm-picklist-values` writer: adds values to a standard CRM picklist
|
|
111
|
+
* or a custom Choice field's choices, never removing, renaming, reordering
|
|
112
|
+
* or reactivating one. Gated as the Fields page is — the `crm` feature, an
|
|
113
|
+
* owner, admin or editor with `data.manage` — and org-wide, as every
|
|
114
|
+
* picklist and field document is. A lead source added where records
|
|
115
|
+
* already hold its label files them under its group (`leadSourceDirection`),
|
|
116
|
+
* as the console's route does (AGL-3577).
|
|
117
|
+
*/
|
|
118
|
+
export declare function createCrmPicklistValuesSetupWriter(deps?: CrmSetupWriterDeps): PluginResourceDraftWriter;
|
|
119
|
+
export declare const crmPicklistValuesSetupWriter: PluginResourceDraftWriter;
|
|
92
120
|
export {};
|
|
@@ -16,25 +16,36 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { consentGroupForHost } from "@aglyn/aglyn/app-utils/consent-groups";
|
|
18
18
|
import { CONTACT_FIELDS_MAX_PER_ORG } from "@aglyn/aglyn/app-utils/contact-custom-fields";
|
|
19
|
-
import { CRM_COLLECTIONS, crmReadTokens, crmScopeTokens } from "@aglyn/aglyn/app-utils/crm";
|
|
19
|
+
import { CRM_COLLECTIONS, crmPicklistDefinition, crmReadTokens, crmScopeTokens, effectiveCrmPicklist, normalizeCrmPicklist } from "@aglyn/aglyn/app-utils/crm";
|
|
20
20
|
import { memberCanSee } from "@aglyn/aglyn/app-utils/organizations";
|
|
21
21
|
import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
22
|
-
import { ORG_SCOPE_TOKEN } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
22
|
+
import { newResourceScopeFields, ORG_SCOPE_TOKEN } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
23
23
|
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
24
24
|
import { memberHasOrgPermission, resolveOrgIdForHost, resolveOrgMembership } from "@aglyn/tenant-data-admin/server/organizations";
|
|
25
|
-
import { FieldPath } from "firebase-admin/firestore";
|
|
25
|
+
import { FieldPath, FieldValue } from "firebase-admin/firestore";
|
|
26
|
+
import { checkCrmPicklistValuesSetup, CRM_CUSTOM_CHOICE_LIST, crmPicklistValuesName, planCrmCustomChoiceValues, planCrmPicklistValues, readCrmPicklistValuesSetup } from "../model/crm-picklist-setup-content.js";
|
|
26
27
|
import { checkCrmFieldSetup, checkCrmPipelineSetup, crmPipelineSetupFacts, planCrmFieldSetup, planCrmPipelineSetup, readCrmFieldSetup, readCrmPipelineSetup } from "../model/crm-setup-content.js";
|
|
28
|
+
import { picklistKeepsGroups } from "../model/picklist-values.js";
|
|
29
|
+
import { picklistGroupOf, regroupEverywhere } from "./picklist-regroup.js";
|
|
27
30
|
import { crmSuiteRefusalMessage } from "./suite-gate.js";
|
|
28
31
|
/** The refusals, in the console's and the routes' words. */ export const CRM_SETUP_ROLE_REFUSAL = 'Editing the CRM requires the editor role';
|
|
29
32
|
export const CRM_SETUP_PERMISSION_REFUSAL = 'Your organization role does not allow editing organization data';
|
|
30
33
|
export const CRM_SETUP_SCOPE_REFUSAL = 'Your access to this organization does not reach that site';
|
|
31
34
|
export const CRM_SETUP_FIELD_SCOPE_REFUSAL = 'Custom fields are organization-wide, and your access reaches only some sites';
|
|
35
|
+
export const CRM_SETUP_PICKLIST_SCOPE_REFUSAL = 'Picklists are organization-wide, and your access reaches only some sites';
|
|
32
36
|
/** Roles allowed to write the CRM's setup — the rules' `canWriteOrgData()`. */ const WRITER_ROLES = new Set([
|
|
33
37
|
'owner',
|
|
34
38
|
'admin',
|
|
35
39
|
'editor'
|
|
36
40
|
]);
|
|
37
41
|
/** The most pipelines one site's read returns, as the board reads them. */ const PIPELINE_READ_LIMIT = 50;
|
|
42
|
+
/** The org subcollection each picklist target's records live in. */ const TARGET_COLLECTIONS = {
|
|
43
|
+
contact: 'contacts',
|
|
44
|
+
lead: 'leads',
|
|
45
|
+
company: CRM_COLLECTIONS.companies,
|
|
46
|
+
deal: CRM_COLLECTIONS.deals,
|
|
47
|
+
task: CRM_COLLECTIONS.tasks
|
|
48
|
+
};
|
|
38
49
|
async function platformMember(uid, orgId) {
|
|
39
50
|
var _member_role;
|
|
40
51
|
const membership = await resolveOrgMembership(uid, orgId);
|
|
@@ -95,7 +106,39 @@ async function platformMember(uid, orgId) {
|
|
|
95
106
|
options,
|
|
96
107
|
updatedAt: now
|
|
97
108
|
});
|
|
98
|
-
}
|
|
109
|
+
},
|
|
110
|
+
picklist: async (orgId, id)=>{
|
|
111
|
+
var _snap_data;
|
|
112
|
+
const snap = await org(orgId).collection(CRM_COLLECTIONS.picklists).doc(id).get();
|
|
113
|
+
return snap.exists ? (_snap_data = snap.data()) != null ? _snap_data : {} : null;
|
|
114
|
+
},
|
|
115
|
+
changePicklist: async (orgId, id, change, stamp)=>{
|
|
116
|
+
const ref = org(orgId).collection(CRM_COLLECTIONS.picklists).doc(id);
|
|
117
|
+
return firestore().runTransaction(async (transaction)=>{
|
|
118
|
+
var _snap_data;
|
|
119
|
+
const snap = await transaction.get(ref);
|
|
120
|
+
const stored = snap.exists ? (_snap_data = snap.data()) != null ? _snap_data : {} : null;
|
|
121
|
+
const { write, result } = change(stored);
|
|
122
|
+
if (write) {
|
|
123
|
+
// The stamp the console's first write gives an org-wide CRM document.
|
|
124
|
+
const created = normalizeCrmPicklist(stored) === null;
|
|
125
|
+
transaction.set(ref, _extends({
|
|
126
|
+
values: write.values,
|
|
127
|
+
defaultValueId: write.defaultValueId,
|
|
128
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
129
|
+
}, created ? _extends({
|
|
130
|
+
hostId: stamp.hostId,
|
|
131
|
+
createdAt: FieldValue.serverTimestamp()
|
|
132
|
+
}, newResourceScopeFields([
|
|
133
|
+
ORG_SCOPE_TOKEN
|
|
134
|
+
])) : {}), {
|
|
135
|
+
merge: true
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
regroup: (orgId, object, target, label, group)=>regroupEverywhere(firestore(), org(orgId).collection(TARGET_COLLECTIONS[object]), target, label, group)
|
|
99
142
|
};
|
|
100
143
|
}
|
|
101
144
|
/** The scope a pipeline made from this site is stamped with: the board seed's. */ function pipelineScope(context) {
|
|
@@ -129,6 +172,8 @@ function fieldRecord(field) {
|
|
|
129
172
|
type: field.type
|
|
130
173
|
}, field.options ? {
|
|
131
174
|
options: field.options
|
|
175
|
+
} : {}, field.currency ? {
|
|
176
|
+
currency: field.currency
|
|
132
177
|
} : {})
|
|
133
178
|
};
|
|
134
179
|
}
|
|
@@ -311,5 +356,137 @@ function createCrmSetupGate(deps) {
|
|
|
311
356
|
}
|
|
312
357
|
export const crmPipelineSetupWriter = createCrmPipelineSetupWriter();
|
|
313
358
|
export const crmFieldSetupWriter = createCrmFieldSetupWriter();
|
|
359
|
+
/*==========================================
|
|
360
|
+
* PICKLIST VALUES (AGL-3756)
|
|
361
|
+
*=========================================*/ /** The record id a custom Choice field's values are reported under, beside a picklist's own id. */ const CUSTOM_RECORD_PREFIX = `${CRM_CUSTOM_CHOICE_LIST}:`;
|
|
362
|
+
function picklistRecord(definition, picklist, outcome) {
|
|
363
|
+
return {
|
|
364
|
+
id: definition.id,
|
|
365
|
+
name: crmPicklistValuesName(definition),
|
|
366
|
+
versionId: null,
|
|
367
|
+
facts: _extends({
|
|
368
|
+
list: definition.id,
|
|
369
|
+
values: picklist.values.filter((value)=>value.active).map((value)=>value.label)
|
|
370
|
+
}, outcome != null ? outcome : {})
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function choiceRecord(field, outcome) {
|
|
374
|
+
var _field_label, _field_object, _field_options;
|
|
375
|
+
return {
|
|
376
|
+
id: `${CUSTOM_RECORD_PREFIX}${field.$id}`,
|
|
377
|
+
name: String((_field_label = field.label) != null ? _field_label : ''),
|
|
378
|
+
versionId: null,
|
|
379
|
+
facts: _extends({
|
|
380
|
+
list: CRM_CUSTOM_CHOICE_LIST,
|
|
381
|
+
object: (_field_object = field.object) != null ? _field_object : 'contact',
|
|
382
|
+
values: (_field_options = field.options) != null ? _field_options : []
|
|
383
|
+
}, outcome != null ? outcome : {})
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* The `crm-picklist-values` writer: adds values to a standard CRM picklist
|
|
388
|
+
* or a custom Choice field's choices, never removing, renaming, reordering
|
|
389
|
+
* or reactivating one. Gated as the Fields page is — the `crm` feature, an
|
|
390
|
+
* owner, admin or editor with `data.manage` — and org-wide, as every
|
|
391
|
+
* picklist and field document is. A lead source added where records
|
|
392
|
+
* already hold its label files them under its group (`leadSourceDirection`),
|
|
393
|
+
* as the console's route does (AGL-3577).
|
|
394
|
+
*/ export function createCrmPicklistValuesSetupWriter(deps = {}) {
|
|
395
|
+
var _deps_store, _deps_orgIdForHost;
|
|
396
|
+
const store = (_deps_store = deps.store) != null ? _deps_store : adminCrmSetupStore();
|
|
397
|
+
const orgIdForHost = (_deps_orgIdForHost = deps.orgIdForHost) != null ? _deps_orgIdForHost : (hostId)=>resolveOrgIdForHost(hostId);
|
|
398
|
+
const gate = createCrmSetupGate(deps);
|
|
399
|
+
const scope = [
|
|
400
|
+
ORG_SCOPE_TOKEN
|
|
401
|
+
];
|
|
402
|
+
return {
|
|
403
|
+
refusal: (context)=>gate(context, scope, CRM_SETUP_PICKLIST_SCOPE_REFUSAL),
|
|
404
|
+
check: (content)=>checkCrmPicklistValuesSetup(content),
|
|
405
|
+
read: async ({ hostId, id })=>{
|
|
406
|
+
const orgId = await orgIdForHost(hostId);
|
|
407
|
+
if (!orgId) return null;
|
|
408
|
+
if (id.startsWith(CUSTOM_RECORD_PREFIX)) {
|
|
409
|
+
const field = await store.field(orgId, id.slice(CUSTOM_RECORD_PREFIX.length));
|
|
410
|
+
return field ? choiceRecord(field) : null;
|
|
411
|
+
}
|
|
412
|
+
const definition = crmPicklistDefinition(id);
|
|
413
|
+
if (!definition) return null;
|
|
414
|
+
const listId = definition.id;
|
|
415
|
+
return picklistRecord(definition, effectiveCrmPicklist(listId, await store.picklist(orgId, listId)));
|
|
416
|
+
},
|
|
417
|
+
write: async (request)=>{
|
|
418
|
+
const read = readCrmPicklistValuesSetup(request.content);
|
|
419
|
+
if (read.ok === false) return {
|
|
420
|
+
ok: false,
|
|
421
|
+
status: 400,
|
|
422
|
+
error: read.problems[0]
|
|
423
|
+
};
|
|
424
|
+
const refused = await gate(request, scope, CRM_SETUP_PICKLIST_SCOPE_REFUSAL);
|
|
425
|
+
if (refused) return _extends({
|
|
426
|
+
ok: false
|
|
427
|
+
}, refused);
|
|
428
|
+
const setup = read.value;
|
|
429
|
+
if (setup.target.kind === 'custom') {
|
|
430
|
+
const definitions = await store.fields(request.orgId);
|
|
431
|
+
const plan = planCrmCustomChoiceValues(_extends({}, setup, {
|
|
432
|
+
target: setup.target
|
|
433
|
+
}), definitions);
|
|
434
|
+
if (plan.ok === false) return {
|
|
435
|
+
ok: false,
|
|
436
|
+
status: plan.status,
|
|
437
|
+
error: plan.error
|
|
438
|
+
};
|
|
439
|
+
const field = definitions.find((one)=>one.$id === plan.id);
|
|
440
|
+
const { added, existing, inactive } = plan;
|
|
441
|
+
if (added.length) await store.setFieldOptions(request.orgId, plan.id, plan.options, request.now);
|
|
442
|
+
return _extends({
|
|
443
|
+
ok: true,
|
|
444
|
+
replayed: added.length === 0
|
|
445
|
+
}, choiceRecord(_extends({}, field, {
|
|
446
|
+
options: plan.options
|
|
447
|
+
}), {
|
|
448
|
+
added,
|
|
449
|
+
existing,
|
|
450
|
+
inactive
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
const id = setup.target.id;
|
|
454
|
+
const definition = crmPicklistDefinition(id);
|
|
455
|
+
const planned = await store.changePicklist(request.orgId, id, (stored)=>{
|
|
456
|
+
const plan = planCrmPicklistValues(definition, effectiveCrmPicklist(id, stored), setup);
|
|
457
|
+
return {
|
|
458
|
+
write: plan.ok && plan.added.length ? plan.picklist : null,
|
|
459
|
+
result: plan
|
|
460
|
+
};
|
|
461
|
+
}, {
|
|
462
|
+
hostId: request.hostId
|
|
463
|
+
});
|
|
464
|
+
if (planned.ok === false) return {
|
|
465
|
+
ok: false,
|
|
466
|
+
status: 409,
|
|
467
|
+
error: planned.error
|
|
468
|
+
};
|
|
469
|
+
// Records that already hold an added label take its group (AGL-3577).
|
|
470
|
+
if (planned.added.length && picklistKeepsGroups(definition)) {
|
|
471
|
+
for (const target of definition.targets){
|
|
472
|
+
if (!target.groupField || target.facet || target.arrayKey) continue;
|
|
473
|
+
for (const label of planned.added){
|
|
474
|
+
await store.regroup(request.orgId, target.object, target, label, picklistGroupOf(planned.picklist, label));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const { added, existing, inactive } = planned;
|
|
479
|
+
return _extends({
|
|
480
|
+
ok: true,
|
|
481
|
+
replayed: added.length === 0
|
|
482
|
+
}, picklistRecord(definition, planned.picklist, {
|
|
483
|
+
added,
|
|
484
|
+
existing,
|
|
485
|
+
inactive
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
export const crmPicklistValuesSetupWriter = createCrmPicklistValuesSetupWriter();
|
|
314
491
|
|
|
315
492
|
//# sourceMappingURL=crm-setup-writers.server.js.map
|