@wukongcrm/mcp-server 0.1.3
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 +219 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.js +155 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/modules.d.ts +17 -0
- package/dist/modules.js +193 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +78 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.js +858 -0
- package/package.json +46 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
import { CrmClient } from "./client.js";
|
|
2
|
+
import { CRM_MODULES, ensureWritableModule, getModuleByKey } from "./modules.js";
|
|
3
|
+
export function createToolHandlers(config = {}) {
|
|
4
|
+
const client = new CrmClient(config);
|
|
5
|
+
return {
|
|
6
|
+
crm_auth_status: async () => client.authStatus(),
|
|
7
|
+
crm_list_modules: () => ({
|
|
8
|
+
modules: CRM_MODULES.map(({ aliases: _aliases, ...moduleDefinition }) => moduleDefinition)
|
|
9
|
+
}),
|
|
10
|
+
crm_list_customer_pools: async (args) => listCustomerPools(client, args),
|
|
11
|
+
crm_get_module_schema: async (args) => {
|
|
12
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
13
|
+
if (isPoolModule(moduleDefinition)) {
|
|
14
|
+
const poolContext = await resolvePoolContext(client, moduleDefinition, args);
|
|
15
|
+
const [fields, listHead] = await Promise.all([
|
|
16
|
+
client.post(`${moduleDefinition.basePath}/queryPoolField`, undefined, cleanQuery({ poolId: poolContext.poolId, isFlat: args.isFlat ?? args.flat })),
|
|
17
|
+
client.post(`${moduleDefinition.basePath}/queryPoolListHead`, undefined, { poolId: poolContext.poolId })
|
|
18
|
+
]);
|
|
19
|
+
return { module: moduleDefinition.key, moduleName: moduleDefinition.name, pool: poolContext.pool, fields, listHead };
|
|
20
|
+
}
|
|
21
|
+
const id = args.id ?? args.recordId;
|
|
22
|
+
const [fields, listHead] = await Promise.all([
|
|
23
|
+
id
|
|
24
|
+
? client.post(`${moduleDefinition.basePath}/field/${id}`, undefined, cleanQuery({ type: args.type, poolId: args.poolId }))
|
|
25
|
+
: client.post(`${moduleDefinition.basePath}/field`, undefined, cleanQuery({ type: args.type })),
|
|
26
|
+
client.post(`/crmField/queryListHead/${moduleDefinition.label}`, undefined, cleanQuery({ isHide: args.isHide ?? 0 }))
|
|
27
|
+
]);
|
|
28
|
+
return { module: moduleDefinition.key, moduleName: moduleDefinition.name, fields, listHead };
|
|
29
|
+
},
|
|
30
|
+
crm_validate_field: async (args) => {
|
|
31
|
+
const path = args.contactDuplicateCheck ? "/crmField/contactDuplicateCheck" : "/crmField/verify";
|
|
32
|
+
return {
|
|
33
|
+
data: await client.post(path, args),
|
|
34
|
+
endpoint: path
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
crm_search_records: async (args) => {
|
|
38
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
39
|
+
const poolContext = isPoolModule(moduleDefinition) ? await resolvePoolContext(client, moduleDefinition, args) : undefined;
|
|
40
|
+
const searchList = buildSearchList(args, moduleDefinition);
|
|
41
|
+
const body = {
|
|
42
|
+
page: args.page ?? 1,
|
|
43
|
+
limit: args.limit ?? 15,
|
|
44
|
+
pageType: 1,
|
|
45
|
+
label: moduleDefinition.label,
|
|
46
|
+
poolId: poolContext?.poolId ?? args.poolId,
|
|
47
|
+
sortIds: args.sortIds,
|
|
48
|
+
searchList
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
module: moduleDefinition.key,
|
|
52
|
+
moduleLabel: moduleDefinition.label,
|
|
53
|
+
pool: poolContext?.pool,
|
|
54
|
+
searchMode: searchList.length > 0 ? "advancedFilter" : "none",
|
|
55
|
+
data: await client.post(`${moduleDefinition.basePath}/queryPageList`, cleanObject(body))
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
crm_get_record: async (args) => {
|
|
59
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
60
|
+
const poolContext = isPoolModule(moduleDefinition) ? await resolvePoolContext(client, moduleDefinition, args) : undefined;
|
|
61
|
+
const recordModule = poolContext ? getPoolRecordModule(moduleDefinition) : moduleDefinition;
|
|
62
|
+
return {
|
|
63
|
+
module: moduleDefinition.key,
|
|
64
|
+
pool: poolContext?.pool,
|
|
65
|
+
data: await client.post(`${recordModule.basePath}/queryById/${args.id}`, undefined, cleanQuery({ poolId: poolContext?.poolId ?? args.poolId }))
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
crm_get_record_information: async (args) => {
|
|
69
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
70
|
+
const poolContext = isPoolModule(moduleDefinition) ? await resolvePoolContext(client, moduleDefinition, args) : undefined;
|
|
71
|
+
const recordModule = poolContext ? getPoolRecordModule(moduleDefinition) : moduleDefinition;
|
|
72
|
+
return {
|
|
73
|
+
module: moduleDefinition.key,
|
|
74
|
+
pool: poolContext?.pool,
|
|
75
|
+
data: await client.post(`${recordModule.basePath}/information/${args.id}`, undefined, cleanQuery({ poolId: poolContext?.poolId ?? args.poolId }))
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
crm_get_related_records: async (args) => {
|
|
79
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
80
|
+
return callRelatedRecords(client, moduleDefinition, args);
|
|
81
|
+
},
|
|
82
|
+
crm_get_record_timeline: async (args) => {
|
|
83
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
84
|
+
const poolContext = isPoolModule(moduleDefinition) ? await resolvePoolContext(client, moduleDefinition, args) : undefined;
|
|
85
|
+
const activityModule = poolContext ? getPoolRecordModule(moduleDefinition) : moduleDefinition;
|
|
86
|
+
const body = {
|
|
87
|
+
page: args.page ?? 1,
|
|
88
|
+
limit: args.limit ?? 15,
|
|
89
|
+
pageType: 1,
|
|
90
|
+
label: activityModule.label,
|
|
91
|
+
typeId: args.id,
|
|
92
|
+
activityType: activityModule.label,
|
|
93
|
+
activityTypeId: args.id,
|
|
94
|
+
...safeExtra(args.extra)
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
module: moduleDefinition.key,
|
|
98
|
+
pool: poolContext?.pool,
|
|
99
|
+
data: await client.post("/crmActivity/queryActivityList", cleanObject(body))
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
crm_get_action_records: async (args) => {
|
|
103
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
104
|
+
const actionModule = isPoolModule(moduleDefinition) ? getPoolRecordModule(moduleDefinition) : moduleDefinition;
|
|
105
|
+
return {
|
|
106
|
+
module: moduleDefinition.key,
|
|
107
|
+
data: await client.post("/crmActionRecord/queryRecordList", undefined, {
|
|
108
|
+
actionId: args.id,
|
|
109
|
+
types: args.types ?? actionModule.label
|
|
110
|
+
})
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
crm_list_files: async (args) => {
|
|
114
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
115
|
+
const poolContext = isPoolModule(moduleDefinition) ? await resolvePoolContext(client, moduleDefinition, args) : undefined;
|
|
116
|
+
const fileModule = poolContext ? getPoolRecordModule(moduleDefinition) : moduleDefinition;
|
|
117
|
+
return {
|
|
118
|
+
module: moduleDefinition.key,
|
|
119
|
+
pool: poolContext?.pool,
|
|
120
|
+
readonly: true,
|
|
121
|
+
data: await client.post(`${fileModule.basePath}/queryFileList`, undefined, cleanQuery({ id: args.id, fileType: args.fileType }))
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
crm_list_users: async (args) => ({
|
|
125
|
+
data: await client.post("/adminUser/queryUserList", cleanObject({
|
|
126
|
+
page: args.page ?? 1,
|
|
127
|
+
limit: args.limit ?? 15,
|
|
128
|
+
pageType: args.pageType ?? 1,
|
|
129
|
+
realname: args.realname,
|
|
130
|
+
username: args.username,
|
|
131
|
+
deptId: args.deptId,
|
|
132
|
+
status: args.status
|
|
133
|
+
}))
|
|
134
|
+
}),
|
|
135
|
+
crm_find_user_id: async (args) => {
|
|
136
|
+
const mode = args.mode ?? "realname";
|
|
137
|
+
const path = mode === "username" ? "/adminUser/getIdByUserName" : "/adminUser/getIdByRealName";
|
|
138
|
+
return {
|
|
139
|
+
mode,
|
|
140
|
+
data: await client.post(path, undefined, { userName: args.userName ?? args.name, companyId: args.companyId })
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
crm_list_departments: async (args) => ({
|
|
144
|
+
data: await client.post("/adminDept/queryDeptTree", cleanObject(args ?? {}))
|
|
145
|
+
}),
|
|
146
|
+
crm_find_dept_id: async (args) => ({
|
|
147
|
+
data: await client.post("/adminDept/getIdByDeptName", undefined, { deptName: args.deptName ?? args.name, companyId: args.companyId })
|
|
148
|
+
}),
|
|
149
|
+
crm_preview_write: (args) => previewWrite(args),
|
|
150
|
+
crm_update_customer: async (args) => {
|
|
151
|
+
const moduleDefinition = getModuleByKey("customer");
|
|
152
|
+
const targetPath = `${moduleDefinition.basePath}/updateInformation`;
|
|
153
|
+
const directId = args.customerId ?? args.id;
|
|
154
|
+
if (!args.confirm) {
|
|
155
|
+
return previewWrite({
|
|
156
|
+
...args,
|
|
157
|
+
module: "customer",
|
|
158
|
+
id: directId ?? "<需要通过客户查询唯一命中后填充>",
|
|
159
|
+
operation: "updateInformation",
|
|
160
|
+
targetPath
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const resolvedRecord = directId ? undefined : await resolveUniqueRecord(client, moduleDefinition, args);
|
|
164
|
+
const id = directId ?? resolvedRecord?.[moduleDefinition.primaryKey] ?? resolvedRecord?.id;
|
|
165
|
+
const body = await buildUpdateInformationBody(client, moduleDefinition, { ...args, id });
|
|
166
|
+
const data = await client.post(targetPath, body);
|
|
167
|
+
const verification = args.verify === false ? undefined : await verifyInformationUpdate(client, moduleDefinition, body);
|
|
168
|
+
return cleanObject({
|
|
169
|
+
module: moduleDefinition.key,
|
|
170
|
+
targetPath,
|
|
171
|
+
resolvedRecord,
|
|
172
|
+
body,
|
|
173
|
+
data,
|
|
174
|
+
verification
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
crm_create_record: async (args) => {
|
|
178
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
179
|
+
ensureWritableModule(moduleDefinition);
|
|
180
|
+
const targetPath = `${moduleDefinition.basePath}/add`;
|
|
181
|
+
if (!args.confirm) {
|
|
182
|
+
return previewWrite({ ...args, operation: "create", targetPath });
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
module: moduleDefinition.key,
|
|
186
|
+
targetPath,
|
|
187
|
+
data: await client.post(targetPath, buildModelSaveBody(args))
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
crm_update_record_fields: async (args) => {
|
|
191
|
+
const moduleDefinition = getModuleByKey(args.module);
|
|
192
|
+
ensureWritableModule(moduleDefinition);
|
|
193
|
+
const targetPath = `${moduleDefinition.basePath}/updateInformation`;
|
|
194
|
+
if (!args.confirm) {
|
|
195
|
+
return previewWrite({ ...args, operation: "updateInformation", targetPath });
|
|
196
|
+
}
|
|
197
|
+
const body = await buildUpdateInformationBody(client, moduleDefinition, args);
|
|
198
|
+
const data = await client.post(targetPath, body);
|
|
199
|
+
const verification = args.verify === false ? undefined : await verifyInformationUpdate(client, moduleDefinition, body);
|
|
200
|
+
return {
|
|
201
|
+
module: moduleDefinition.key,
|
|
202
|
+
targetPath,
|
|
203
|
+
body,
|
|
204
|
+
data,
|
|
205
|
+
verification
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
crm_add_followup: async (args) => {
|
|
209
|
+
const targetPath = "/crmActivity/add";
|
|
210
|
+
if (!args.confirm) {
|
|
211
|
+
return previewWrite({ ...args, operation: "addFollowup", targetPath });
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
module: "activity",
|
|
215
|
+
targetPath,
|
|
216
|
+
data: await client.post(targetPath, buildFollowupSaveBody(args))
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
crm_update_followup: async (args) => {
|
|
220
|
+
const targetPath = args.useInformationUpdate ? "/crmActivity/updateInformation" : "/crmActivity/update";
|
|
221
|
+
if (!args.confirm) {
|
|
222
|
+
return previewWrite({ ...args, operation: "updateFollowup", targetPath });
|
|
223
|
+
}
|
|
224
|
+
const body = args.useInformationUpdate
|
|
225
|
+
? cleanObject({ id: args.id, label: 19, list: args.list ?? args.fields ?? [], batchId: args.batchId })
|
|
226
|
+
: buildFollowupSaveBody(args);
|
|
227
|
+
return {
|
|
228
|
+
module: "activity",
|
|
229
|
+
targetPath,
|
|
230
|
+
data: await client.post(targetPath, body)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const FIELD_SEARCH_CONTAINS = 3;
|
|
236
|
+
const QUICK_SEARCH_FIELDS = {
|
|
237
|
+
leads: [
|
|
238
|
+
{ fieldName: "leadsName", formType: "text" },
|
|
239
|
+
{ fieldName: "mobile", formType: "mobile" },
|
|
240
|
+
{ fieldName: "telephone", formType: "text" }
|
|
241
|
+
],
|
|
242
|
+
leadsPool: [
|
|
243
|
+
{ fieldName: "leadsName", formType: "text" },
|
|
244
|
+
{ fieldName: "mobile", formType: "mobile" },
|
|
245
|
+
{ fieldName: "telephone", formType: "text" }
|
|
246
|
+
],
|
|
247
|
+
customer: [
|
|
248
|
+
{ fieldName: "customerName", formType: "text" },
|
|
249
|
+
{ fieldName: "mobile", formType: "mobile" },
|
|
250
|
+
{ fieldName: "telephone", formType: "text" }
|
|
251
|
+
],
|
|
252
|
+
customerPool: [
|
|
253
|
+
{ fieldName: "customerName", formType: "text" },
|
|
254
|
+
{ fieldName: "mobile", formType: "mobile" },
|
|
255
|
+
{ fieldName: "telephone", formType: "text" }
|
|
256
|
+
],
|
|
257
|
+
contacts: [
|
|
258
|
+
{ fieldName: "name", formType: "text" },
|
|
259
|
+
{ fieldName: "mainFieldValue", formType: "text" },
|
|
260
|
+
{ fieldName: "mobile", formType: "mobile" },
|
|
261
|
+
{ fieldName: "telephone", formType: "text" },
|
|
262
|
+
{ fieldName: "email", formType: "email" }
|
|
263
|
+
],
|
|
264
|
+
business: [{ fieldName: "businessName", formType: "text" }],
|
|
265
|
+
product: [{ fieldName: "name", formType: "text" }],
|
|
266
|
+
contract: [
|
|
267
|
+
{ fieldName: "num", formType: "text" },
|
|
268
|
+
{ fieldName: "name", formType: "text" },
|
|
269
|
+
{ fieldName: "customerName", formType: "text" }
|
|
270
|
+
],
|
|
271
|
+
receivables: [
|
|
272
|
+
{ fieldName: "number", formType: "text" },
|
|
273
|
+
{ fieldName: "customerName", formType: "text" }
|
|
274
|
+
],
|
|
275
|
+
receivablesPlan: [
|
|
276
|
+
{ fieldName: "customerName", formType: "text" },
|
|
277
|
+
{ fieldName: "contractNum", formType: "text" }
|
|
278
|
+
],
|
|
279
|
+
invoice: [
|
|
280
|
+
{ fieldName: "invoiceApplyNumber", formType: "text" },
|
|
281
|
+
{ fieldName: "invoiceNumber", formType: "text" },
|
|
282
|
+
{ fieldName: "customerName", formType: "text" }
|
|
283
|
+
],
|
|
284
|
+
activity: [{ fieldName: "content", formType: "text" }],
|
|
285
|
+
quotation: [
|
|
286
|
+
{ fieldName: "name", formType: "text" },
|
|
287
|
+
{ fieldName: "num", formType: "text" },
|
|
288
|
+
{ fieldName: "customerName", formType: "text" }
|
|
289
|
+
],
|
|
290
|
+
returnVisit: [
|
|
291
|
+
{ fieldName: "customerName", formType: "text" },
|
|
292
|
+
{ fieldName: "contractNum", formType: "text" }
|
|
293
|
+
],
|
|
294
|
+
visitPlan: [
|
|
295
|
+
{ fieldName: "customerName", formType: "text" },
|
|
296
|
+
{ fieldName: "contactsName", formType: "text" }
|
|
297
|
+
]
|
|
298
|
+
};
|
|
299
|
+
const NAME_FIELD_BY_MODULE = {
|
|
300
|
+
leads: { fieldName: "leadsName", formType: "text" },
|
|
301
|
+
leadsPool: { fieldName: "leadsName", formType: "text" },
|
|
302
|
+
customer: { fieldName: "customerName", formType: "text" },
|
|
303
|
+
customerPool: { fieldName: "customerName", formType: "text" },
|
|
304
|
+
contacts: { fieldName: "name", formType: "text" },
|
|
305
|
+
business: { fieldName: "businessName", formType: "text" },
|
|
306
|
+
product: { fieldName: "name", formType: "text" },
|
|
307
|
+
contract: { fieldName: "name", formType: "text" },
|
|
308
|
+
quotation: { fieldName: "name", formType: "text" }
|
|
309
|
+
};
|
|
310
|
+
function buildSearchList(args, moduleDefinition) {
|
|
311
|
+
const explicitSearchList = toArray(args.filters ?? args.searchList);
|
|
312
|
+
const semanticSearchList = buildSemanticSearchList(args, moduleDefinition);
|
|
313
|
+
if (explicitSearchList.length > 0 && semanticSearchList.length > 0) {
|
|
314
|
+
return [...explicitSearchList, ...semanticSearchList];
|
|
315
|
+
}
|
|
316
|
+
if (explicitSearchList.length > 0) {
|
|
317
|
+
return explicitSearchList;
|
|
318
|
+
}
|
|
319
|
+
return semanticSearchList;
|
|
320
|
+
}
|
|
321
|
+
function buildSemanticSearchList(args, moduleDefinition) {
|
|
322
|
+
const keyword = firstTextValue(args.keyword, args.query, args.q, args.search, args.searchText, args.text, args.term);
|
|
323
|
+
if (keyword) {
|
|
324
|
+
return containsFilters(QUICK_SEARCH_FIELDS[moduleDefinition.key] ?? [], keyword, 1);
|
|
325
|
+
}
|
|
326
|
+
const phone = firstTextValue(args.phone, args.mobile, args.telephone);
|
|
327
|
+
if (phone) {
|
|
328
|
+
const fields = (QUICK_SEARCH_FIELDS[moduleDefinition.key] ?? []).filter((field) => ["mobile", "telephone", "phone"].includes(field.fieldName));
|
|
329
|
+
return containsFilters(fields, phone, 1);
|
|
330
|
+
}
|
|
331
|
+
const name = firstTextValue(args.name, args.customerName, args.leadsName, args.contactsName, args.businessName, args.productName, args.contractName, args.quotationName);
|
|
332
|
+
const nameField = NAME_FIELD_BY_MODULE[moduleDefinition.key];
|
|
333
|
+
if (name && nameField) {
|
|
334
|
+
return containsFilters([nameField], name, 0);
|
|
335
|
+
}
|
|
336
|
+
const email = firstTextValue(args.email);
|
|
337
|
+
if (email) {
|
|
338
|
+
return containsFilters([{ fieldName: "email", formType: "email" }], email, 0);
|
|
339
|
+
}
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
function containsFilters(fields, value, startGroupId) {
|
|
343
|
+
return fields.map((field, index) => ({
|
|
344
|
+
fieldName: field.fieldName,
|
|
345
|
+
formType: field.formType,
|
|
346
|
+
type: FIELD_SEARCH_CONTAINS,
|
|
347
|
+
searchEnum: FIELD_SEARCH_CONTAINS,
|
|
348
|
+
values: [value],
|
|
349
|
+
groupId: fields.length > 1 ? startGroupId + index : startGroupId
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
function firstTextValue(...values) {
|
|
353
|
+
for (const value of values) {
|
|
354
|
+
if (value === undefined || value === null) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const text = String(value).trim();
|
|
358
|
+
if (text) {
|
|
359
|
+
return text;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
function toArray(value) {
|
|
365
|
+
if (Array.isArray(value)) {
|
|
366
|
+
return value;
|
|
367
|
+
}
|
|
368
|
+
if (value && typeof value === "object") {
|
|
369
|
+
return [value];
|
|
370
|
+
}
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
async function resolveUniqueRecord(client, moduleDefinition, args) {
|
|
374
|
+
const searchList = buildSearchList(args, moduleDefinition);
|
|
375
|
+
if (searchList.length === 0) {
|
|
376
|
+
throw new Error(`请提供可定位${moduleDefinition.name}的 customerId/id、phone/mobile、name/customerName 或 keyword。`);
|
|
377
|
+
}
|
|
378
|
+
const page = await client.post(`${moduleDefinition.basePath}/queryPageList`, cleanObject({
|
|
379
|
+
page: 1,
|
|
380
|
+
limit: 2,
|
|
381
|
+
pageType: 1,
|
|
382
|
+
label: moduleDefinition.label,
|
|
383
|
+
searchList
|
|
384
|
+
}));
|
|
385
|
+
const list = Array.isArray(page.list) ? page.list : [];
|
|
386
|
+
if (list.length !== 1) {
|
|
387
|
+
throw new Error(`需要唯一命中的${moduleDefinition.name}才能写入,当前命中 ${page.totalRow ?? list.length} 条,请先缩小查询条件或直接传入 ID。`);
|
|
388
|
+
}
|
|
389
|
+
return list[0];
|
|
390
|
+
}
|
|
391
|
+
async function buildUpdateInformationBody(client, moduleDefinition, args) {
|
|
392
|
+
const rawBody = buildUpdateInformationPreviewBody(moduleDefinition, args);
|
|
393
|
+
const fields = await fetchModuleFields(client, moduleDefinition, args);
|
|
394
|
+
const list = hydrateUpdateList(rawBody.list ?? [], fields);
|
|
395
|
+
const batchId = rawBody.batchId ?? await resolveUpdateBatchId(client, moduleDefinition, rawBody, list);
|
|
396
|
+
return {
|
|
397
|
+
...rawBody,
|
|
398
|
+
batchId,
|
|
399
|
+
list
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function buildUpdateInformationPreviewBody(moduleDefinition, args) {
|
|
403
|
+
return cleanObject({
|
|
404
|
+
id: args.id ?? args[moduleDefinition.primaryKey],
|
|
405
|
+
label: moduleDefinition.label,
|
|
406
|
+
list: rawUpdateList(args),
|
|
407
|
+
batchId: args.batchId,
|
|
408
|
+
idList: args.idList,
|
|
409
|
+
ownerUserSave: args.ownerUserSave
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function rawUpdateList(args) {
|
|
413
|
+
const raw = args.list ?? args.fields ?? args.field;
|
|
414
|
+
const list = toArray(raw);
|
|
415
|
+
if (list.length > 0) {
|
|
416
|
+
return list;
|
|
417
|
+
}
|
|
418
|
+
const updates = args.updates;
|
|
419
|
+
if (!updates || typeof updates !== "object" || Array.isArray(updates)) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
return Object.entries(updates).map(([key, value]) => looksLikeFieldName(key) ? { fieldName: key, value } : { name: key, value });
|
|
423
|
+
}
|
|
424
|
+
function looksLikeFieldName(value) {
|
|
425
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
|
|
426
|
+
}
|
|
427
|
+
async function fetchModuleFields(client, moduleDefinition, args) {
|
|
428
|
+
const id = args.id ?? args[moduleDefinition.primaryKey];
|
|
429
|
+
const path = id ? `${moduleDefinition.basePath}/field/${id}` : `${moduleDefinition.basePath}/field`;
|
|
430
|
+
const data = await client.post(path, undefined, cleanQuery({ type: args.type, poolId: args.poolId }));
|
|
431
|
+
return flattenFieldDefinitions(data);
|
|
432
|
+
}
|
|
433
|
+
function flattenFieldDefinitions(input) {
|
|
434
|
+
const output = [];
|
|
435
|
+
const seen = new Set();
|
|
436
|
+
const visit = (value) => {
|
|
437
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
seen.add(value);
|
|
441
|
+
if (Array.isArray(value)) {
|
|
442
|
+
value.forEach(visit);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const record = value;
|
|
446
|
+
if (record.fieldName || record.fieldId || (record.name && (record.fieldType !== undefined || record.type !== undefined || record.formType))) {
|
|
447
|
+
output.push(record);
|
|
448
|
+
}
|
|
449
|
+
for (const child of Object.values(record)) {
|
|
450
|
+
if (child && typeof child === "object") {
|
|
451
|
+
visit(child);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
visit(input);
|
|
456
|
+
return output;
|
|
457
|
+
}
|
|
458
|
+
function hydrateUpdateList(list, fields) {
|
|
459
|
+
return list.map((item) => hydrateUpdateItem(item, fields));
|
|
460
|
+
}
|
|
461
|
+
function hydrateUpdateItem(item, fields) {
|
|
462
|
+
const field = findFieldDefinition(item, fields);
|
|
463
|
+
if (!field && hasUpdateMetadata(item)) {
|
|
464
|
+
return normalizeUpdateItem(item);
|
|
465
|
+
}
|
|
466
|
+
if (!field) {
|
|
467
|
+
const fieldLabel = item.name ?? item.fieldName ?? item.fieldId ?? "<unknown>";
|
|
468
|
+
throw new Error(`未在字段 schema 中找到可写字段:${fieldLabel}。请先调用 crm_get_module_schema 确认字段名。`);
|
|
469
|
+
}
|
|
470
|
+
const merged = cleanObject({
|
|
471
|
+
fieldId: item.fieldId ?? field.fieldId ?? field.id,
|
|
472
|
+
fieldName: item.fieldName ?? field.fieldName,
|
|
473
|
+
name: item.name ?? field.name,
|
|
474
|
+
fieldType: item.fieldType ?? field.fieldType,
|
|
475
|
+
type: item.type ?? field.type,
|
|
476
|
+
formType: item.formType ?? field.formType,
|
|
477
|
+
value: normalizeOptionValue(item.value, field)
|
|
478
|
+
});
|
|
479
|
+
return normalizeUpdateItem(merged);
|
|
480
|
+
}
|
|
481
|
+
function hasUpdateMetadata(item) {
|
|
482
|
+
return Boolean(item.fieldName && item.fieldType !== undefined && item.type !== undefined);
|
|
483
|
+
}
|
|
484
|
+
function normalizeUpdateItem(item) {
|
|
485
|
+
return cleanObject({
|
|
486
|
+
fieldId: item.fieldId,
|
|
487
|
+
fieldName: item.fieldName,
|
|
488
|
+
name: item.name,
|
|
489
|
+
fieldType: item.fieldType,
|
|
490
|
+
type: item.type,
|
|
491
|
+
formType: item.formType,
|
|
492
|
+
value: item.value
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
async function resolveUpdateBatchId(client, moduleDefinition, body, list) {
|
|
496
|
+
if (body.batchId || !body.id || !updateNeedsBatchId(list)) {
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
const data = await client.post(`${moduleDefinition.basePath}/queryById/${body.id}`);
|
|
500
|
+
return findFirstValueByKey(data, "batchId");
|
|
501
|
+
}
|
|
502
|
+
function updateNeedsBatchId(list) {
|
|
503
|
+
return list.some((item) => {
|
|
504
|
+
const fieldType = Number(item.fieldType);
|
|
505
|
+
return fieldType === 0 || fieldType === 2;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function findFirstValueByKey(input, key) {
|
|
509
|
+
const seen = new Set();
|
|
510
|
+
const visit = (value) => {
|
|
511
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
seen.add(value);
|
|
515
|
+
if (Array.isArray(value)) {
|
|
516
|
+
for (const item of value) {
|
|
517
|
+
const found = visit(item);
|
|
518
|
+
if (found !== undefined && found !== null && found !== "") {
|
|
519
|
+
return found;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return undefined;
|
|
523
|
+
}
|
|
524
|
+
const record = value;
|
|
525
|
+
if (record[key] !== undefined && record[key] !== null && record[key] !== "") {
|
|
526
|
+
return record[key];
|
|
527
|
+
}
|
|
528
|
+
for (const child of Object.values(record)) {
|
|
529
|
+
const found = visit(child);
|
|
530
|
+
if (found !== undefined && found !== null && found !== "") {
|
|
531
|
+
return found;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return undefined;
|
|
535
|
+
};
|
|
536
|
+
return visit(input);
|
|
537
|
+
}
|
|
538
|
+
function findFieldDefinition(item, fields) {
|
|
539
|
+
const fieldId = item.fieldId !== undefined ? String(item.fieldId) : undefined;
|
|
540
|
+
const fieldName = normalizeText(item.fieldName);
|
|
541
|
+
const name = normalizeText(item.name);
|
|
542
|
+
return fields.find((field) => {
|
|
543
|
+
if (fieldId && String(field.fieldId ?? field.id) === fieldId) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
if (fieldName && normalizeText(field.fieldName) === fieldName) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
return Boolean(name && normalizeText(field.name) === name);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function normalizeOptionValue(value, field) {
|
|
553
|
+
const options = parseFieldOptions(field.options ?? field.setting ?? field.optionsData);
|
|
554
|
+
if (options.length === 0 || value === undefined || value === null || value === "") {
|
|
555
|
+
return value;
|
|
556
|
+
}
|
|
557
|
+
const valueText = normalizeText(value);
|
|
558
|
+
const match = options.find((option) => [option.value, option.name, option.label].some((candidate) => normalizeText(candidate) === valueText));
|
|
559
|
+
if (!match) {
|
|
560
|
+
const available = options.map((option) => option.name ?? option.label ?? option.value).filter((option) => option !== undefined);
|
|
561
|
+
throw new Error(`字段“${field.name ?? field.fieldName}”的可选值不包含“${value}”。可选值:${available.join("、")}`);
|
|
562
|
+
}
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
function parseFieldOptions(input) {
|
|
566
|
+
if (!input) {
|
|
567
|
+
return [];
|
|
568
|
+
}
|
|
569
|
+
if (Array.isArray(input)) {
|
|
570
|
+
return input.map((option) => (typeof option === "object" && option ? option : { value: option, name: option }));
|
|
571
|
+
}
|
|
572
|
+
if (typeof input === "string") {
|
|
573
|
+
const text = input.trim();
|
|
574
|
+
if (!text) {
|
|
575
|
+
return [];
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
return parseFieldOptions(JSON.parse(text));
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return text.split(",").map((option) => option.trim()).filter(Boolean).map((option) => ({ value: option, name: option }));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (typeof input === "object") {
|
|
585
|
+
const object = input;
|
|
586
|
+
return parseFieldOptions(object.options ?? object.list ?? object.data ?? []);
|
|
587
|
+
}
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
async function verifyInformationUpdate(client, moduleDefinition, body) {
|
|
591
|
+
const endpoint = `${moduleDefinition.basePath}/information/${body.id}`;
|
|
592
|
+
const data = await client.post(endpoint);
|
|
593
|
+
const checked = (body.list ?? []).map((item) => {
|
|
594
|
+
const actualValue = findInformationValue(data, item);
|
|
595
|
+
return {
|
|
596
|
+
fieldName: item.fieldName,
|
|
597
|
+
name: item.name,
|
|
598
|
+
expectedValue: item.value,
|
|
599
|
+
actualValue,
|
|
600
|
+
matched: valuesMatch(item.value, actualValue)
|
|
601
|
+
};
|
|
602
|
+
});
|
|
603
|
+
return {
|
|
604
|
+
endpoint,
|
|
605
|
+
ok: checked.every((item) => item.matched),
|
|
606
|
+
checked
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function findInformationValue(data, field) {
|
|
610
|
+
const direct = findDirectInformationValue(data, field);
|
|
611
|
+
if (direct.found) {
|
|
612
|
+
return direct.value;
|
|
613
|
+
}
|
|
614
|
+
const seen = new Set();
|
|
615
|
+
const wantedFieldName = normalizeText(field.fieldName);
|
|
616
|
+
const wantedName = normalizeText(field.name);
|
|
617
|
+
const visit = (value) => {
|
|
618
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
619
|
+
return { found: false };
|
|
620
|
+
}
|
|
621
|
+
seen.add(value);
|
|
622
|
+
if (Array.isArray(value)) {
|
|
623
|
+
for (const item of value) {
|
|
624
|
+
const result = visit(item);
|
|
625
|
+
if (result.found) {
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return { found: false };
|
|
630
|
+
}
|
|
631
|
+
const record = value;
|
|
632
|
+
const recordFieldName = normalizeText(record.fieldName);
|
|
633
|
+
const recordName = normalizeText(record.name);
|
|
634
|
+
if ((wantedFieldName && recordFieldName === wantedFieldName) || (wantedName && recordName === wantedName)) {
|
|
635
|
+
return { found: true, value: extractInformationValue(record) };
|
|
636
|
+
}
|
|
637
|
+
for (const child of Object.values(record)) {
|
|
638
|
+
const result = visit(child);
|
|
639
|
+
if (result.found) {
|
|
640
|
+
return result;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return { found: false };
|
|
644
|
+
};
|
|
645
|
+
return visit(data).value;
|
|
646
|
+
}
|
|
647
|
+
function findDirectInformationValue(data, field) {
|
|
648
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
649
|
+
return { found: false };
|
|
650
|
+
}
|
|
651
|
+
const record = data;
|
|
652
|
+
if (field.fieldName && Object.prototype.hasOwnProperty.call(record, field.fieldName)) {
|
|
653
|
+
return { found: true, value: record[field.fieldName] };
|
|
654
|
+
}
|
|
655
|
+
return { found: false };
|
|
656
|
+
}
|
|
657
|
+
function extractInformationValue(record) {
|
|
658
|
+
if (Object.prototype.hasOwnProperty.call(record, "value")) {
|
|
659
|
+
return record.value;
|
|
660
|
+
}
|
|
661
|
+
if (Object.prototype.hasOwnProperty.call(record, "valueDesc")) {
|
|
662
|
+
return record.valueDesc;
|
|
663
|
+
}
|
|
664
|
+
if (Object.prototype.hasOwnProperty.call(record, "fieldValue")) {
|
|
665
|
+
return record.fieldValue;
|
|
666
|
+
}
|
|
667
|
+
return undefined;
|
|
668
|
+
}
|
|
669
|
+
function valuesMatch(expected, actual) {
|
|
670
|
+
return normalizeComparableValue(expected) === normalizeComparableValue(actual);
|
|
671
|
+
}
|
|
672
|
+
function normalizeComparableValue(value) {
|
|
673
|
+
if (value === undefined || value === null) {
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
if (typeof value === "object") {
|
|
677
|
+
return JSON.stringify(value);
|
|
678
|
+
}
|
|
679
|
+
return String(value).trim();
|
|
680
|
+
}
|
|
681
|
+
function normalizeText(value) {
|
|
682
|
+
return value === undefined || value === null ? "" : String(value).trim().toLowerCase();
|
|
683
|
+
}
|
|
684
|
+
async function listCustomerPools(client, args) {
|
|
685
|
+
const pools = await client.post("/crmCustomerPool/queryPoolNameListByAuth");
|
|
686
|
+
const poolList = Array.isArray(pools) ? pools : [];
|
|
687
|
+
const includeSamples = Boolean(args.includeSamples);
|
|
688
|
+
const limit = includeSamples ? positiveInteger(args.sampleLimit ?? args.limit, 3) : 1;
|
|
689
|
+
const searchList = args.filters ?? args.searchList ?? [];
|
|
690
|
+
const poolSummaries = await Promise.all(poolList.map(async (pool) => {
|
|
691
|
+
const poolId = pool.poolId ?? pool.id;
|
|
692
|
+
if (!poolId) {
|
|
693
|
+
throw new Error("客户公海授权列表缺少 poolId,无法统计池内客户数量。");
|
|
694
|
+
}
|
|
695
|
+
const page = await client.post("/crmCustomerPool/queryPageList", cleanObject({
|
|
696
|
+
page: 1,
|
|
697
|
+
limit,
|
|
698
|
+
pageType: 1,
|
|
699
|
+
label: 9,
|
|
700
|
+
poolId,
|
|
701
|
+
searchList
|
|
702
|
+
}));
|
|
703
|
+
const summary = {
|
|
704
|
+
poolId,
|
|
705
|
+
poolName: pool.poolName ?? pool.name,
|
|
706
|
+
customerCount: page.totalRow ?? page.total ?? 0
|
|
707
|
+
};
|
|
708
|
+
if (includeSamples) {
|
|
709
|
+
const list = Array.isArray(page.list) ? page.list : [];
|
|
710
|
+
summary.samples = list.map((record) => ({
|
|
711
|
+
customerId: record.customerId ?? record.id,
|
|
712
|
+
customerName: record.customerName ?? record.name,
|
|
713
|
+
ownerUserName: record.ownerUserName,
|
|
714
|
+
createTime: record.createTime
|
|
715
|
+
}));
|
|
716
|
+
}
|
|
717
|
+
return summary;
|
|
718
|
+
}));
|
|
719
|
+
return {
|
|
720
|
+
module: "customerPool",
|
|
721
|
+
readonly: true,
|
|
722
|
+
poolCount: poolSummaries.length,
|
|
723
|
+
pools: poolSummaries
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function positiveInteger(value, fallback) {
|
|
727
|
+
const numberValue = Number(value);
|
|
728
|
+
if (!Number.isInteger(numberValue) || numberValue < 1) {
|
|
729
|
+
return fallback;
|
|
730
|
+
}
|
|
731
|
+
return numberValue;
|
|
732
|
+
}
|
|
733
|
+
function isPoolModule(moduleDefinition) {
|
|
734
|
+
return moduleDefinition.key === "customerPool" || moduleDefinition.key === "leadsPool";
|
|
735
|
+
}
|
|
736
|
+
function getPoolRecordModule(moduleDefinition) {
|
|
737
|
+
return getModuleByKey(moduleDefinition.key === "customerPool" ? "customer" : "leads");
|
|
738
|
+
}
|
|
739
|
+
async function resolvePoolContext(client, moduleDefinition, args) {
|
|
740
|
+
if (!isPoolModule(moduleDefinition)) {
|
|
741
|
+
throw new Error(`Module ${moduleDefinition.key} is not a pool module.`);
|
|
742
|
+
}
|
|
743
|
+
if (args.poolId) {
|
|
744
|
+
return { poolId: args.poolId, pool: cleanObject({ poolId: args.poolId, poolName: args.poolName }) };
|
|
745
|
+
}
|
|
746
|
+
const pools = await client.post(`${moduleDefinition.basePath}/queryPoolNameListByAuth`);
|
|
747
|
+
const poolList = Array.isArray(pools) ? pools : [];
|
|
748
|
+
const selectedPool = args.poolName
|
|
749
|
+
? poolList.find((pool) => String(pool.poolName ?? pool.name ?? "").trim() === String(args.poolName).trim())
|
|
750
|
+
: poolList[0];
|
|
751
|
+
if (!selectedPool) {
|
|
752
|
+
throw new Error(`当前账号没有可查询的${moduleDefinition.name},请确认公海/线索池权限或传入 poolId。`);
|
|
753
|
+
}
|
|
754
|
+
const poolId = selectedPool.poolId ?? selectedPool.id;
|
|
755
|
+
if (!poolId) {
|
|
756
|
+
throw new Error(`${moduleDefinition.name}授权列表缺少 poolId,无法查询池内数据。`);
|
|
757
|
+
}
|
|
758
|
+
return { poolId, pool: selectedPool };
|
|
759
|
+
}
|
|
760
|
+
async function callRelatedRecords(client, moduleDefinition, args) {
|
|
761
|
+
const relation = String(args.relation ?? args.type ?? "").trim();
|
|
762
|
+
const pageBody = cleanObject({ page: args.page ?? 1, limit: args.limit ?? 15, pageType: 1, id: args.id, ...safeExtra(args.extra) });
|
|
763
|
+
if (moduleDefinition.key === "business" && relation === "product") {
|
|
764
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmBusiness/queryProduct", pageBody) };
|
|
765
|
+
}
|
|
766
|
+
if (moduleDefinition.key === "business" && relation === "contacts") {
|
|
767
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmBusiness/queryContacts", pageBody) };
|
|
768
|
+
}
|
|
769
|
+
if (moduleDefinition.key === "contacts" && relation === "business") {
|
|
770
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmContacts/queryBusiness", pageBody) };
|
|
771
|
+
}
|
|
772
|
+
if (moduleDefinition.key === "contract" && relation === "product") {
|
|
773
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmContract/queryProductListByContractId", pageBody) };
|
|
774
|
+
}
|
|
775
|
+
if (moduleDefinition.key === "contract" && relation === "receivablesPlan") {
|
|
776
|
+
return {
|
|
777
|
+
module: moduleDefinition.key,
|
|
778
|
+
relation,
|
|
779
|
+
data: await client.post("/crmContract/queryReceivablesPlansByContractId", undefined, {
|
|
780
|
+
contractId: args.id,
|
|
781
|
+
receivablesId: args.receivablesId,
|
|
782
|
+
productId: args.productId
|
|
783
|
+
})
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
if (moduleDefinition.key === "receivables" && relation === "product") {
|
|
787
|
+
return { module: moduleDefinition.key, relation, data: await client.post(`/crmReceivables/queryProductList/${args.id}`) };
|
|
788
|
+
}
|
|
789
|
+
if (moduleDefinition.key === "quotation" && relation === "product") {
|
|
790
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmQuotation/queryProductList", pageBody) };
|
|
791
|
+
}
|
|
792
|
+
if (moduleDefinition.key === "product" && relation === "saleProduct") {
|
|
793
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmProduct/querySaleProductPageList", pageBody) };
|
|
794
|
+
}
|
|
795
|
+
if (moduleDefinition.key === "receivablesPlan" && relation === "contractCustomer") {
|
|
796
|
+
return { module: moduleDefinition.key, relation, data: await client.post("/crmReceivablesPlan/queryByContractAndCustomer", pageBody) };
|
|
797
|
+
}
|
|
798
|
+
throw new Error(`当前 MCP 未配置“${moduleDefinition.name} / ${relation}”关联查询白名单。`);
|
|
799
|
+
}
|
|
800
|
+
function previewWrite(args) {
|
|
801
|
+
const moduleDefinition = args.module ? getModuleByKey(args.module) : undefined;
|
|
802
|
+
const targetPath = args.targetPath ?? (moduleDefinition ? `${moduleDefinition.basePath}/${args.operation === "create" ? "add" : "updateInformation"}` : "/crmActivity/add");
|
|
803
|
+
const body = args.updateBody
|
|
804
|
+
?? (args.operation === "updateInformation" && moduleDefinition
|
|
805
|
+
? buildUpdateInformationPreviewBody(moduleDefinition, args)
|
|
806
|
+
: args.useInformationUpdate
|
|
807
|
+
? cleanObject({ id: args.id, label: 19, list: rawUpdateList(args), batchId: args.batchId })
|
|
808
|
+
: (args.operation === "addFollowup" || args.operation === "updateFollowup")
|
|
809
|
+
? buildFollowupSaveBody(args)
|
|
810
|
+
: buildModelSaveBody(args));
|
|
811
|
+
return {
|
|
812
|
+
preview: true,
|
|
813
|
+
message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
|
|
814
|
+
module: moduleDefinition?.key ?? args.module ?? "activity",
|
|
815
|
+
moduleName: moduleDefinition?.name ?? "跟进记录",
|
|
816
|
+
targetPath,
|
|
817
|
+
body
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function buildModelSaveBody(args) {
|
|
821
|
+
return cleanObject({
|
|
822
|
+
entity: args.entity ?? {},
|
|
823
|
+
field: args.field ?? args.fields ?? [],
|
|
824
|
+
saveType: args.saveType,
|
|
825
|
+
fileId: args.fileId,
|
|
826
|
+
aiAnalysis: args.aiAnalysis,
|
|
827
|
+
member: args.member
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
function buildFollowupSaveBody(args) {
|
|
831
|
+
const body = buildModelSaveBody(args);
|
|
832
|
+
const sourceEntity = body.entity && typeof body.entity === "object" && !Array.isArray(body.entity)
|
|
833
|
+
? body.entity
|
|
834
|
+
: {};
|
|
835
|
+
const entity = { ...sourceEntity };
|
|
836
|
+
if (!Array.isArray(entity.relateDataList)) {
|
|
837
|
+
entity.relateDataList = [];
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
...body,
|
|
841
|
+
entity
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function cleanQuery(input) {
|
|
845
|
+
return cleanObject(input);
|
|
846
|
+
}
|
|
847
|
+
function cleanObject(input) {
|
|
848
|
+
const output = {};
|
|
849
|
+
for (const [key, value] of Object.entries(input)) {
|
|
850
|
+
if (value !== undefined && value !== null) {
|
|
851
|
+
output[key] = value;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return output;
|
|
855
|
+
}
|
|
856
|
+
function safeExtra(extra) {
|
|
857
|
+
return extra && typeof extra === "object" && !Array.isArray(extra) ? extra : {};
|
|
858
|
+
}
|