@wukongcrm/mcp-server 0.1.3 → 0.2.0

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/dist/tools.js CHANGED
@@ -1,5 +1,337 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { CrmClient } from "./client.js";
2
3
  import { CRM_MODULES, ensureWritableModule, getModuleByKey } from "./modules.js";
4
+ const FINANCE_MODULES = [
5
+ {
6
+ key: "certificate",
7
+ name: "凭证",
8
+ basePath: "/financeCertificate",
9
+ supportedOperations: ["列表查询", "详情", "汇总", "新增", "编辑", "审核", "冲销", "删除", "凭证整理", "域内固定路径写入"],
10
+ unsupportedOperations: ["文件流下载", "multipart 文件上传代理"]
11
+ },
12
+ {
13
+ key: "ledger",
14
+ name: "账簿",
15
+ basePath: "/financeCertificate",
16
+ supportedOperations: ["明细账", "总账", "科目余额表", "多栏账", "核算项目账", "数量金额账"],
17
+ unsupportedOperations: ["导出", "打印"]
18
+ },
19
+ {
20
+ key: "report",
21
+ name: "报表",
22
+ basePath: "/financeReport",
23
+ supportedOperations: ["资产负债表", "利润表", "现金流量表", "平衡检查", "公式编辑", "扩展数据编辑", "域内固定路径写入"],
24
+ unsupportedOperations: ["文件流导出"]
25
+ },
26
+ {
27
+ key: "subject",
28
+ name: "科目",
29
+ basePath: "/financeSubject",
30
+ supportedOperations: ["列表查询", "新增", "编辑", "删除", "启用禁用", "域内固定路径写入"],
31
+ unsupportedOperations: ["文件流导入导出"]
32
+ },
33
+ {
34
+ key: "voucherWord",
35
+ name: "凭证字",
36
+ basePath: "/financeVoucher",
37
+ supportedOperations: ["列表查询", "新增", "编辑", "删除", "排序", "域内固定路径写入"],
38
+ unsupportedOperations: []
39
+ },
40
+ {
41
+ key: "currency",
42
+ name: "币别",
43
+ basePath: "/financeCurrency",
44
+ supportedOperations: ["列表查询", "新增", "编辑", "删除", "域内固定路径写入"],
45
+ unsupportedOperations: []
46
+ },
47
+ {
48
+ key: "dashboard",
49
+ name: "财务仪表盘",
50
+ basePath: "/financeDashboard",
51
+ supportedOperations: ["利润表统计", "配置查询", "指标列表", "指标统计", "配置修改", "域内固定路径写入"],
52
+ unsupportedOperations: []
53
+ }
54
+ ];
55
+ const FINANCE_LEDGER_QUERIES = [
56
+ {
57
+ key: "detail",
58
+ name: "明细账",
59
+ targetPath: "/financeCertificate/queryDetailAccount",
60
+ aliases: ["detail", "subLedger", "detailedLedger", "明细账"]
61
+ },
62
+ {
63
+ key: "general",
64
+ name: "总账",
65
+ targetPath: "/financeCertificate/queryGeneralLedger",
66
+ aliases: ["general", "generalLedger", "allLedger", "总账"]
67
+ },
68
+ {
69
+ key: "balance",
70
+ name: "科目余额表",
71
+ targetPath: "/financeCertificate/queryDetailBalanceAccount",
72
+ aliases: ["balance", "accountBalance", "subjectBalance", "科目余额表", "余额表"]
73
+ },
74
+ {
75
+ key: "multiColumn",
76
+ name: "多栏账",
77
+ targetPath: "/financeCertificate/queryDiversification",
78
+ aliases: ["multiColumn", "diversification", "multi", "多栏账"]
79
+ },
80
+ {
81
+ key: "itemsDetail",
82
+ name: "核算项目明细账",
83
+ targetPath: "/financeCertificate/queryItemsDetailAccount",
84
+ aliases: ["itemsDetail", "auxiliaryDetail", "accountingProjectDetail", "核算项目明细账"]
85
+ },
86
+ {
87
+ key: "itemsBalance",
88
+ name: "核算项目余额表",
89
+ targetPath: "/financeCertificate/queryItemsDetailBalanceAccount",
90
+ aliases: ["itemsBalance", "auxiliaryBalance", "accountingProjectBalance", "核算项目余额表"]
91
+ },
92
+ {
93
+ key: "amountDetail",
94
+ name: "数量金额明细账",
95
+ targetPath: "/financeCertificate/queryAmountDetailAccount",
96
+ aliases: ["amountDetail", "numberMoney", "quantityAmountDetail", "数量金额明细账"]
97
+ },
98
+ {
99
+ key: "amountGeneral",
100
+ name: "数量金额总账",
101
+ targetPath: "/financeCertificate/queryAmountDetailUpAccount",
102
+ aliases: ["amountGeneral", "totalNumberMoney", "quantityAmountGeneral", "数量金额总账"]
103
+ }
104
+ ];
105
+ const FINANCE_REPORT_QUERIES = [
106
+ {
107
+ key: "balanceSheet",
108
+ name: "资产负债表",
109
+ targetPath: "/financeReport/balanceSheetReport",
110
+ aliases: ["balanceSheet", "balance", "资产负债表"]
111
+ },
112
+ {
113
+ key: "incomeStatement",
114
+ name: "利润表",
115
+ targetPath: "/financeReport/incomeStatementReport",
116
+ aliases: ["incomeStatement", "profit", "income", "利润表"]
117
+ },
118
+ {
119
+ key: "cashFlow",
120
+ name: "现金流量表",
121
+ targetPath: "/financeReport/cashFlowStatementReport",
122
+ aliases: ["cashFlow", "cash", "现金流量表"]
123
+ }
124
+ ];
125
+ const FINANCE_DASHBOARD_QUERIES = [
126
+ {
127
+ key: "incomeStatement",
128
+ name: "利润表统计",
129
+ targetPath: "/financeDashboard/incomeStatement",
130
+ aliases: ["incomeStatement", "income", "利润表统计"]
131
+ },
132
+ {
133
+ key: "config",
134
+ name: "仪表盘配置",
135
+ targetPath: "/financeDashboard/config",
136
+ aliases: ["config", "dashboardConfig", "配置"]
137
+ },
138
+ {
139
+ key: "indicator",
140
+ name: "指标列表",
141
+ targetPath: "/financeDashboard/indicator",
142
+ aliases: ["indicator", "indicators", "指标"]
143
+ },
144
+ {
145
+ key: "statistics",
146
+ name: "指标统计",
147
+ targetPath: "/financeDashboard/statistics",
148
+ aliases: ["statistics", "stat", "stats", "指标统计"]
149
+ },
150
+ {
151
+ key: "fieldLanguage",
152
+ name: "字段语言包",
153
+ targetPath: "/financeDashboard/getAllFieldLanguageRel",
154
+ aliases: ["fieldLanguage", "language", "fieldRel", "字段语言包"]
155
+ }
156
+ ];
157
+ const JXC_MODULES = [
158
+ {
159
+ key: "product",
160
+ name: "产品",
161
+ label: 1,
162
+ basePath: "/jxcProduct",
163
+ primaryKey: "productId",
164
+ aliases: [1, "1", "product", "jxcProduct", "产品"]
165
+ },
166
+ {
167
+ key: "supplier",
168
+ name: "供应商",
169
+ label: 8,
170
+ basePath: "/jxcSupplier",
171
+ primaryKey: "supplierId",
172
+ aliases: [8, "8", "supplier", "jxcSupplier", "供应商"]
173
+ },
174
+ {
175
+ key: "warehouse",
176
+ name: "仓库",
177
+ label: 13,
178
+ basePath: "/jxcWarehouse",
179
+ primaryKey: "warehouseId",
180
+ aliases: [13, "13", "warehouse", "jxcWarehouse", "仓库"]
181
+ }
182
+ ];
183
+ const OA_EXAMINE_LIST_ENDPOINTS = [
184
+ {
185
+ key: "upcomingTodo",
186
+ name: "待办-待我审批",
187
+ targetPath: "/examineSuperExamine/todo/me",
188
+ aliases: ["upcoming", "upcoming.todo", "upcoming0", "todo", "todoMe", "待办", "待我审批"]
189
+ },
190
+ {
191
+ key: "upcomingCopy",
192
+ name: "待办-抄送给我",
193
+ targetPath: "/examineSuperExamine/copy/me",
194
+ aliases: ["upcoming.copy", "upcoming1", "copy", "copyMe", "ccMe", "抄送给我"]
195
+ },
196
+ {
197
+ key: "trackMe",
198
+ name: "跟踪-我申请的",
199
+ targetPath: "/examineSuperExamine/track/me",
200
+ aliases: ["track", "track.me", "track0", "trackMe", "myApplication", "跟踪", "我申请的"]
201
+ },
202
+ {
203
+ key: "trackDo",
204
+ name: "跟踪-经我审批",
205
+ targetPath: "/examineSuperExamine/track/do",
206
+ aliases: ["track.do", "track1", "trackDo", "approvedByMe", "经我审批"]
207
+ },
208
+ {
209
+ key: "trackCopy",
210
+ name: "跟踪-抄送给我",
211
+ targetPath: "/examineSuperExamine/track/copy",
212
+ aliases: ["track.copy", "track2", "trackCopy", "trackCcMe", "跟踪抄送"]
213
+ },
214
+ {
215
+ key: "archiveMe",
216
+ name: "归档-我的申请",
217
+ targetPath: "/examineSuperExamine/end/me",
218
+ aliases: ["archive", "archive.me", "archive0", "end.me", "endMe", "归档", "归档我的申请"]
219
+ },
220
+ {
221
+ key: "archiveAudit",
222
+ name: "归档-经我审批",
223
+ targetPath: "/examineSuperExamine/end/audit",
224
+ aliases: ["archive.audit", "archive1", "end.audit", "endAudit", "归档经我审批"]
225
+ },
226
+ {
227
+ key: "archiveFollow",
228
+ name: "归档-我的关注",
229
+ targetPath: "/examineSuperExamine/end/follow",
230
+ aliases: ["archive.follow", "archive2", "end.follow", "endFollow", "我的关注"]
231
+ },
232
+ {
233
+ key: "archiveTovoid",
234
+ name: "归档-作废",
235
+ targetPath: "/examineSuperExamine/end/tovoid",
236
+ aliases: ["archive.tovoid", "archive3", "end.tovoid", "endTovoid", "void", "作废"]
237
+ },
238
+ {
239
+ key: "archiveCopy",
240
+ name: "归档-抄送给我",
241
+ targetPath: "/examineSuperExamine/end/copy",
242
+ aliases: ["archive.copy", "archive4", "end.copy", "endCopy", "归档抄送"]
243
+ },
244
+ {
245
+ key: "draft",
246
+ name: "草稿",
247
+ targetPath: "/oaSuperExamine/draft",
248
+ aliases: ["draft", "草稿"]
249
+ },
250
+ {
251
+ key: "all",
252
+ name: "全部审批",
253
+ targetPath: "/examine/examineSuperExamine/examine/queryAll",
254
+ aliases: ["all", "all0", "全部审批"]
255
+ },
256
+ {
257
+ key: "legacyWaiting",
258
+ name: "旧版 OA 待审列表",
259
+ targetPath: "/examineWaiting/queryOaExamineList",
260
+ aliases: ["legacyWaiting", "waiting", "oaWaiting", "oldWaiting", "旧版待办"]
261
+ },
262
+ {
263
+ key: "legacyRecord",
264
+ name: "旧版审批记录列表",
265
+ targetPath: "/examineRecord/queryExamineRecordList",
266
+ aliases: ["legacyRecord", "record", "records", "examineRecord", "oldRecord", "旧版记录"]
267
+ },
268
+ {
269
+ key: "myInitiate",
270
+ name: "我发起的审批",
271
+ targetPath: "/oaExamine/myInitiate",
272
+ aliases: ["myInitiate", "myCreate", "initiated", "我发起", "我发起的审批"]
273
+ },
274
+ {
275
+ key: "myOaExamine",
276
+ name: "我审批的",
277
+ targetPath: "/oaExamine/myOaExamine",
278
+ aliases: ["myOaExamine", "myExamine", "myAudit", "我审批", "我审批的"]
279
+ }
280
+ ];
281
+ const CRM_DOCUMENT_WRITE_DEFINITIONS = [
282
+ {
283
+ key: "contract",
284
+ name: "合同",
285
+ basePath: "/crmContract",
286
+ primaryKey: "contractId",
287
+ requiresApprovalFlow: true
288
+ },
289
+ {
290
+ key: "receivables",
291
+ name: "回款",
292
+ basePath: "/crmReceivables",
293
+ primaryKey: "receivablesId",
294
+ requiresApprovalFlow: true
295
+ },
296
+ {
297
+ key: "receivablesPlan",
298
+ name: "回款计划",
299
+ basePath: "/crmReceivablesPlan",
300
+ primaryKey: "receivablesPlanId",
301
+ requiresApprovalFlow: false
302
+ },
303
+ {
304
+ key: "invoice",
305
+ name: "发票",
306
+ basePath: "/crmInvoice",
307
+ primaryKey: "invoiceId",
308
+ requiresApprovalFlow: true
309
+ },
310
+ {
311
+ key: "quotation",
312
+ name: "报价单",
313
+ basePath: "/crmQuotation",
314
+ primaryKey: "quotationId",
315
+ requiresApprovalFlow: true
316
+ }
317
+ ];
318
+ const WRITE_DOMAINS = {
319
+ hrm: {
320
+ key: "hrm",
321
+ name: "人力资源 HRM",
322
+ allowedPrefixes: ["/hrm"]
323
+ },
324
+ jxc: {
325
+ key: "jxc",
326
+ name: "进销存 JXC",
327
+ allowedPrefixes: ["/jxc"]
328
+ },
329
+ finance: {
330
+ key: "finance",
331
+ name: "独立财务 FM",
332
+ allowedPrefixes: ["/finance"]
333
+ }
334
+ };
3
335
  export function createToolHandlers(config = {}) {
4
336
  const client = new CrmClient(config);
5
337
  return {
@@ -37,7 +369,7 @@ export function createToolHandlers(config = {}) {
37
369
  crm_search_records: async (args) => {
38
370
  const moduleDefinition = getModuleByKey(args.module);
39
371
  const poolContext = isPoolModule(moduleDefinition) ? await resolvePoolContext(client, moduleDefinition, args) : undefined;
40
- const searchList = buildSearchList(args, moduleDefinition);
372
+ const searchList = await buildResolvedSearchList(client, args, moduleDefinition);
41
373
  const body = {
42
374
  page: args.page ?? 1,
43
375
  limit: args.limit ?? 15,
@@ -121,6 +453,89 @@ export function createToolHandlers(config = {}) {
121
453
  data: await client.post(`${fileModule.basePath}/queryFileList`, undefined, cleanQuery({ id: args.id, fileType: args.fileType }))
122
454
  };
123
455
  },
456
+ crm_search_products: async (args) => {
457
+ const targetPath = "/crmProduct/queryPageList";
458
+ return {
459
+ module: "product",
460
+ moduleName: "产品",
461
+ readonly: true,
462
+ targetPath,
463
+ data: await client.post(targetPath, await buildCrmProductSearchBody(client, args, false))
464
+ };
465
+ },
466
+ crm_search_sale_products: async (args) => {
467
+ const targetPath = "/crmProduct/querySaleProductPageList";
468
+ return {
469
+ module: "product",
470
+ moduleName: "上架产品",
471
+ readonly: true,
472
+ targetPath,
473
+ data: await client.post(targetPath, await buildCrmProductSearchBody(client, args, true))
474
+ };
475
+ },
476
+ crm_get_product: async (args) => {
477
+ const productId = requireCrmProductId(args, "查询 CRM 产品详情请提供 productId 或 id。");
478
+ const targetPath = `/crmProduct/queryById/${productId}`;
479
+ return {
480
+ module: "product",
481
+ moduleName: "产品",
482
+ readonly: true,
483
+ targetPath,
484
+ data: await client.post(targetPath)
485
+ };
486
+ },
487
+ crm_get_product_information: async (args) => {
488
+ const productId = requireCrmProductId(args, "查询 CRM 产品详情页字段请提供 productId 或 id。");
489
+ const targetPath = `/crmProduct/information/${productId}`;
490
+ return {
491
+ module: "product",
492
+ moduleName: "产品",
493
+ readonly: true,
494
+ targetPath,
495
+ data: await client.post(targetPath)
496
+ };
497
+ },
498
+ crm_get_product_schema: async (args) => {
499
+ const productId = args.productId ?? args.id;
500
+ const targetPath = productId ? `/crmProduct/field/${productId}` : "/crmProduct/field";
501
+ return {
502
+ module: "product",
503
+ moduleName: "产品",
504
+ readonly: true,
505
+ targetPath,
506
+ data: await client.post(targetPath, undefined, cleanQuery({ type: args.type }))
507
+ };
508
+ },
509
+ crm_list_product_files: async (args) => {
510
+ const targetPath = "/crmProduct/queryFileList";
511
+ return {
512
+ module: "product",
513
+ moduleName: "产品",
514
+ readonly: true,
515
+ targetPath,
516
+ data: await client.post(targetPath, undefined, cleanQuery({ id: requireCrmProductId(args, "查询 CRM 产品附件请提供 productId 或 id。"), fileType: args.fileType }))
517
+ };
518
+ },
519
+ crm_get_product_num: async (args) => {
520
+ const targetPath = "/crmProduct/num";
521
+ return {
522
+ module: "product",
523
+ moduleName: "产品",
524
+ readonly: true,
525
+ targetPath,
526
+ data: await client.post(targetPath, undefined, cleanQuery({ id: requireCrmProductId(args, "查询 CRM 产品数量请提供 productId 或 id。") }))
527
+ };
528
+ },
529
+ crm_get_simple_products: async (args) => {
530
+ const targetPath = "/crmProduct/querySimpleEntity";
531
+ return {
532
+ module: "product",
533
+ moduleName: "产品",
534
+ readonly: true,
535
+ targetPath,
536
+ data: await client.post(targetPath, requireIdArray(args.productIds ?? args.ids, "查询 CRM 简要产品请提供 productIds 或 ids 数组。"))
537
+ };
538
+ },
124
539
  crm_list_users: async (args) => ({
125
540
  data: await client.post("/adminUser/queryUserList", cleanObject({
126
541
  page: args.page ?? 1,
@@ -134,10 +549,19 @@ export function createToolHandlers(config = {}) {
134
549
  }),
135
550
  crm_find_user_id: async (args) => {
136
551
  const mode = args.mode ?? "realname";
137
- const path = mode === "username" ? "/adminUser/getIdByUserName" : "/adminUser/getIdByRealName";
552
+ const userName = args.userName ?? args.name;
553
+ if (isBlank(userName)) {
554
+ throw new Error("请提供 userName 或 name。");
555
+ }
556
+ if (mode === "realname") {
557
+ return {
558
+ mode,
559
+ data: await resolveUserIdByNickname(client, String(userName), new Map(), args.companyId)
560
+ };
561
+ }
138
562
  return {
139
563
  mode,
140
- data: await client.post(path, undefined, { userName: args.userName ?? args.name, companyId: args.companyId })
564
+ data: await client.post("/adminUser/getIdByUserName", undefined, { userName, companyId: args.companyId })
141
565
  };
142
566
  },
143
567
  crm_list_departments: async (args) => ({
@@ -146,656 +570,3589 @@ export function createToolHandlers(config = {}) {
146
570
  crm_find_dept_id: async (args) => ({
147
571
  data: await client.post("/adminDept/getIdByDeptName", undefined, { deptName: args.deptName ?? args.name, companyId: args.companyId })
148
572
  }),
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,
573
+ crm_list_address_book_contacts: async (args) => {
574
+ const targetPath = "/adminUser/queryListName";
575
+ return {
576
+ module: "addressBook",
577
+ moduleName: "通讯录",
578
+ readonly: true,
579
+ targetPath,
580
+ data: await client.post(targetPath, buildAddressBookListBody(args))
581
+ };
582
+ },
583
+ crm_get_address_book_contact: async (args) => {
584
+ const targetPath = "/adminUser/queryUserInfo";
585
+ return {
586
+ module: "addressBook",
587
+ moduleName: "通讯录",
588
+ readonly: true,
589
+ targetPath,
590
+ data: await client.post(targetPath, undefined, cleanQuery({ userId: requireAddressBookUserId(args, "查询通讯录联系人详情请提供 userId 或 id。") }))
591
+ };
592
+ },
593
+ crm_list_address_book_departments: async (args) => {
594
+ const targetPath = "/adminDept/queryDeptTree";
595
+ return {
596
+ module: "addressBook",
597
+ moduleName: "通讯录",
598
+ readonly: true,
599
+ targetPath,
600
+ data: await client.post(targetPath, buildAddressBookDepartmentBody(args))
601
+ };
602
+ },
603
+ crm_list_address_book_auth_departments: async (args) => {
604
+ const targetPath = "/adminDept/queryDeptByAuth";
605
+ return {
606
+ module: "addressBook",
607
+ moduleName: "通讯录",
608
+ readonly: true,
609
+ targetPath,
610
+ data: await client.post(targetPath, cleanObject(args ?? {}))
611
+ };
612
+ },
613
+ crm_list_address_book_dept_user_ids: async (args) => {
614
+ const targetPath = "/adminUser/queryUserByDeptIds";
615
+ const body = addressBookDeptIds(args);
616
+ return {
617
+ module: "addressBook",
618
+ moduleName: "通讯录",
619
+ readonly: true,
170
620
  targetPath,
171
- resolvedRecord,
172
621
  body,
173
- data,
174
- verification
175
- });
622
+ data: await client.post(targetPath, body)
623
+ };
176
624
  },
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
- }
625
+ crm_list_address_book_dept_users: async (args) => {
626
+ const targetPath = "/adminUser/queryListByDeptIds";
627
+ const body = addressBookDeptIds(args);
184
628
  return {
185
- module: moduleDefinition.key,
629
+ module: "addressBook",
630
+ moduleName: "通讯录",
631
+ readonly: true,
186
632
  targetPath,
187
- data: await client.post(targetPath, buildModelSaveBody(args))
633
+ body,
634
+ data: await client.post(targetPath, body)
188
635
  };
189
636
  },
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);
637
+ crm_list_address_book_auth_users: async () => {
638
+ const targetPath = "/adminUser/queryAuthUserList";
200
639
  return {
201
- module: moduleDefinition.key,
640
+ module: "addressBook",
641
+ moduleName: "通讯录",
642
+ readonly: true,
643
+ targetPath,
644
+ data: await client.post(targetPath)
645
+ };
646
+ },
647
+ crm_get_address_book_user_dept_role_info: async (args) => {
648
+ const targetPath = "/adminUser/queryUserDeptOrRoleInfo";
649
+ const body = buildAddressBookUserDeptRoleBody(args);
650
+ return {
651
+ module: "addressBook",
652
+ moduleName: "通讯录",
653
+ readonly: true,
202
654
  targetPath,
203
655
  body,
204
- data,
205
- verification
656
+ data: await client.post(targetPath, body)
206
657
  };
207
658
  },
208
- crm_add_followup: async (args) => {
209
- const targetPath = "/crmActivity/add";
210
- if (!args.confirm) {
211
- return previewWrite({ ...args, operation: "addFollowup", targetPath });
212
- }
659
+ crm_get_address_book_organization: async (args) => {
660
+ const targetPath = "/adminUser/queryOrganizationInfo";
213
661
  return {
214
- module: "activity",
662
+ module: "addressBook",
663
+ moduleName: "通讯录",
664
+ readonly: true,
215
665
  targetPath,
216
- data: await client.post(targetPath, buildFollowupSaveBody(args))
666
+ data: await client.post(targetPath, undefined, cleanQuery({ companyId: args.companyId }))
217
667
  };
218
668
  },
219
- crm_update_followup: async (args) => {
220
- const targetPath = args.useInformationUpdate ? "/crmActivity/updateInformation" : "/crmActivity/update";
669
+ crm_get_address_book_user_count: async (args) => {
670
+ const targetPath = "/adminUser/queryUserNumInfo";
671
+ return {
672
+ module: "addressBook",
673
+ moduleName: "通讯录",
674
+ readonly: true,
675
+ targetPath,
676
+ data: await client.post(targetPath, undefined, cleanQuery({ username: requireAddressBookUsername(args) }))
677
+ };
678
+ },
679
+ crm_toggle_address_book_attention: async (args) => {
680
+ const targetPath = "/adminUser/attention";
681
+ const body = buildAddressBookAttentionBody(args);
221
682
  if (!args.confirm) {
222
- return previewWrite({ ...args, operation: "updateFollowup", targetPath });
683
+ return previewOaWrite("toggleAddressBookAttention", "addressBook", "通讯录", targetPath, body);
223
684
  }
224
- const body = args.useInformationUpdate
225
- ? cleanObject({ id: args.id, label: 19, list: args.list ?? args.fields ?? [], batchId: args.batchId })
226
- : buildFollowupSaveBody(args);
227
685
  return {
228
- module: "activity",
686
+ module: "addressBook",
687
+ moduleName: "通讯录",
229
688
  targetPath,
230
- data: await client.post(targetPath, body)
689
+ body,
690
+ data: await client.post(targetPath, undefined, cleanQuery(body))
231
691
  };
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);
692
+ },
693
+ crm_list_hrm_employees: async (args) => {
694
+ const targetPath = "/hrmEmployee/queryPageList";
695
+ return {
696
+ module: "hrmEmployee",
697
+ readonly: true,
698
+ targetPath,
699
+ data: await client.post(targetPath, buildHrmEmployeeListBody(args))
700
+ };
701
+ },
702
+ crm_get_hrm_employee: async (args) => {
703
+ const employeeId = requireHrmEmployeeId(args);
704
+ const targetPath = `/hrmEmployee/queryById/${employeeId}`;
705
+ return {
706
+ module: "hrmEmployee",
707
+ readonly: true,
708
+ targetPath,
709
+ data: await client.post(targetPath)
710
+ };
711
+ },
712
+ crm_list_hrm_employee_fields: async () => {
713
+ const targetPath = "/hrmEmployeeField/queryListHeads";
714
+ return {
715
+ module: "hrmEmployee",
716
+ readonly: true,
717
+ targetPath,
718
+ data: await client.post(targetPath)
719
+ };
720
+ },
721
+ crm_list_hrm_departments: async (args) => {
722
+ const targetPath = "/hrmDept/queryTreeList";
723
+ return {
724
+ module: "hrmDept",
725
+ readonly: true,
726
+ targetPath,
727
+ data: await client.post(targetPath, buildHrmDepartmentListBody(args))
728
+ };
729
+ },
730
+ crm_get_hrm_department: async (args) => {
731
+ const deptId = requireHrmDeptId(args);
732
+ const targetPath = `/hrmDept/queryById/${deptId}`;
733
+ return {
734
+ module: "hrmDept",
735
+ readonly: true,
736
+ targetPath,
737
+ data: await client.post(targetPath)
738
+ };
739
+ },
740
+ crm_list_hrm_department_employees: async (args) => {
741
+ const targetPath = "/hrmDept/queryEmployeeByDeptId";
742
+ return {
743
+ module: "hrmDept",
744
+ readonly: true,
745
+ targetPath,
746
+ data: await client.post(targetPath, buildHrmDepartmentEmployeesBody(args))
747
+ };
748
+ },
749
+ crm_get_hrm_employee_post: async (args) => {
750
+ const employeeId = requireHrmEmployeeId(args);
751
+ const targetPath = `/hrmEmployeePost/postInformation/${employeeId}`;
752
+ return {
753
+ module: "hrmEmployeePost",
754
+ readonly: true,
755
+ targetPath,
756
+ data: await client.post(targetPath)
757
+ };
758
+ },
759
+ crm_list_hrm_attendance_month_records: async (args) => {
760
+ const targetPath = "/hrmAttendanceEmpMonthRecord/queryAttendanceEmpMonthRecordPageList";
761
+ return {
762
+ module: "hrmAttendance",
763
+ readonly: true,
764
+ targetPath,
765
+ data: await client.post(targetPath, buildHrmAttendanceMonthRecordBody(args))
766
+ };
767
+ },
768
+ crm_get_hrm_attendance_daily_detail: async (args) => {
769
+ const targetPath = "/hrmAttendanceClock/queryAttendanceDailyDetail";
770
+ return {
771
+ module: "hrmAttendance",
772
+ readonly: true,
773
+ targetPath,
774
+ data: await client.post(targetPath, buildHrmAttendanceDailyDetailBody(args))
775
+ };
776
+ },
777
+ crm_list_hrm_leave_records: async (args) => {
778
+ const targetPath = "/hrmEmployeeLeaveRecord/queryLeaveRecordPageList";
779
+ return {
780
+ module: "hrmLeaveRecord",
781
+ readonly: true,
782
+ targetPath,
783
+ data: await client.post(targetPath, buildHrmLeaveRecordBody(args))
784
+ };
785
+ },
786
+ crm_list_hrm_leave_types: async () => {
787
+ const targetPath = "/hrmEmployeeLeaveRecord/queryLeaveTypeList";
788
+ return {
789
+ module: "hrmLeaveRecord",
790
+ readonly: true,
791
+ targetPath,
792
+ data: await client.post(targetPath)
793
+ };
794
+ },
795
+ crm_list_finance_modules: () => ({
796
+ readonly: true,
797
+ modules: FINANCE_MODULES
798
+ }),
799
+ crm_search_finance_vouchers: async (args) => {
800
+ const targetPath = "/financeCertificate/queryPageList";
801
+ return {
802
+ module: "financeCertificate",
803
+ moduleName: "凭证",
804
+ readonly: true,
805
+ targetPath,
806
+ data: await client.post(targetPath, buildFinanceSearchBody(args, { page: args.page ?? 1, limit: args.limit ?? 15, pageType: args.pageType ?? 1 }))
807
+ };
808
+ },
809
+ crm_get_finance_voucher: async (args) => {
810
+ const targetPath = "/financeCertificate/queryById";
811
+ return {
812
+ module: "financeCertificate",
813
+ moduleName: "凭证",
814
+ readonly: true,
815
+ targetPath,
816
+ data: await client.post(targetPath, cleanObject({ certificateId: requireFinanceCertificateId(args), accountId: args.accountId }))
817
+ };
818
+ },
819
+ crm_summarize_finance_vouchers: async (args) => {
820
+ const targetPath = "/financeCertificate/summary";
821
+ return {
822
+ module: "financeCertificate",
823
+ moduleName: "凭证汇总",
824
+ readonly: true,
825
+ targetPath,
826
+ data: await client.post(targetPath, buildFinanceBody(args))
827
+ };
828
+ },
829
+ crm_query_finance_ledger: async (args) => {
830
+ const ledgerDefinition = getFinanceEndpointByKey(args.ledger ?? args.type ?? args.kind, FINANCE_LEDGER_QUERIES, "财务账簿");
831
+ return {
832
+ module: "financeLedger",
833
+ moduleName: ledgerDefinition.name,
834
+ ledger: ledgerDefinition.key,
835
+ readonly: true,
836
+ targetPath: ledgerDefinition.targetPath,
837
+ data: await client.post(ledgerDefinition.targetPath, buildFinanceBody(args, ["ledger", "type", "kind"]))
838
+ };
839
+ },
840
+ crm_query_finance_report: async (args) => {
841
+ const reportDefinition = getFinanceEndpointByKey(args.report ?? args.type ?? args.kind, FINANCE_REPORT_QUERIES, "财务报表");
842
+ const targetPath = args.checkBalance || args.balanceCheck ? `${reportDefinition.targetPath}/balanceCheck` : reportDefinition.targetPath;
843
+ return {
844
+ module: "financeReport",
845
+ moduleName: reportDefinition.name,
846
+ report: reportDefinition.key,
847
+ balanceCheck: Boolean(args.checkBalance || args.balanceCheck),
848
+ readonly: true,
849
+ targetPath,
850
+ data: await client.post(targetPath, buildFinanceBody(args, ["report", "type", "kind", "checkBalance", "balanceCheck"]))
851
+ };
852
+ },
853
+ crm_list_finance_subjects: async (args) => {
854
+ const targetPath = "/financeSubject/list";
855
+ return {
856
+ module: "financeSubject",
857
+ moduleName: "科目",
858
+ readonly: true,
859
+ targetPath,
860
+ data: await client.post(targetPath, buildFinanceBody(args))
861
+ };
862
+ },
863
+ crm_list_finance_currencies: async (args) => {
864
+ const targetPath = args.accountId ? "/financeCurrency/queryListByAccountId" : "/financeCurrency/queryAllList";
865
+ return {
866
+ module: "financeCurrency",
867
+ moduleName: "币别",
868
+ readonly: true,
869
+ targetPath,
870
+ data: await client.post(targetPath, buildFinanceBody(args))
871
+ };
872
+ },
873
+ crm_list_finance_voucher_words: async (args) => {
874
+ const targetPath = "/financeVoucher/queryList";
875
+ return {
876
+ module: "financeVoucher",
877
+ moduleName: "凭证字",
878
+ readonly: true,
879
+ targetPath,
880
+ data: await client.post(targetPath, buildFinanceBody(args))
881
+ };
882
+ },
883
+ crm_query_finance_dashboard: async (args) => {
884
+ const dashboardDefinition = getFinanceEndpointByKey(args.query ?? args.type ?? args.kind, FINANCE_DASHBOARD_QUERIES, "财务仪表盘");
885
+ return {
886
+ module: "financeDashboard",
887
+ moduleName: dashboardDefinition.name,
888
+ query: dashboardDefinition.key,
889
+ readonly: true,
890
+ targetPath: dashboardDefinition.targetPath,
891
+ data: await client.post(dashboardDefinition.targetPath, buildFinanceBody(args, ["query", "type", "kind"]))
892
+ };
893
+ },
894
+ crm_write_hrm: async (args) => executeDomainWrite(client, WRITE_DOMAINS.hrm, args),
895
+ crm_create_hrm_record: async (args) => executeDomainWrite(client, WRITE_DOMAINS.hrm, {
896
+ targetPath: args.targetPath ?? args.path ?? args.endpoint ?? buildDomainTargetPath(args.module ?? "hrmEmployee", args.operation ?? args.action ?? "addEmployee"),
897
+ body: explicitWriteBody(args),
898
+ query: args.query,
899
+ confirm: args.confirm
900
+ }),
901
+ crm_update_hrm_record: async (args) => executeDomainWrite(client, WRITE_DOMAINS.hrm, {
902
+ targetPath: args.targetPath ?? args.path ?? args.endpoint ?? buildDomainTargetPath(args.module ?? "hrmEmployee", args.operation ?? args.action ?? "updateInformation"),
903
+ body: explicitWriteBody(args),
904
+ query: args.query,
905
+ confirm: args.confirm
906
+ }),
907
+ crm_list_jxc_modules: () => ({
908
+ readonly: true,
909
+ modules: JXC_MODULES.map(({ aliases: _aliases, ...moduleDefinition }) => moduleDefinition)
910
+ }),
911
+ crm_search_jxc_records: async (args) => {
912
+ const moduleDefinition = getJxcModuleByKey(args.module);
913
+ const targetPath = `${moduleDefinition.basePath}/queryPageList`;
914
+ return {
915
+ module: moduleDefinition.key,
916
+ moduleName: moduleDefinition.name,
917
+ readonly: true,
918
+ targetPath,
919
+ data: await client.post(targetPath, buildJxcSearchBody(args, moduleDefinition))
920
+ };
921
+ },
922
+ crm_get_jxc_record: async (args) => {
923
+ const moduleDefinition = getJxcModuleByKey(args.module);
924
+ const id = requireJxcId(args, "查询 JXC 记录详情请提供 id。");
925
+ const targetPath = `${moduleDefinition.basePath}/queryById/${id}`;
926
+ return {
927
+ module: moduleDefinition.key,
928
+ moduleName: moduleDefinition.name,
929
+ readonly: true,
930
+ targetPath,
931
+ data: await client.post(targetPath)
932
+ };
933
+ },
934
+ crm_get_jxc_record_information: async (args) => {
935
+ const moduleDefinition = getJxcModuleByKey(args.module);
936
+ const id = requireJxcId(args, "查询 JXC 详情页字段请提供 id。");
937
+ const targetPath = `${moduleDefinition.basePath}/information/${id}`;
938
+ return {
939
+ module: moduleDefinition.key,
940
+ moduleName: moduleDefinition.name,
941
+ readonly: true,
942
+ targetPath,
943
+ data: await client.post(targetPath)
944
+ };
945
+ },
946
+ crm_get_jxc_module_schema: async (args) => {
947
+ const moduleDefinition = getJxcModuleByKey(args.module);
948
+ const id = args.id ?? args.recordId;
949
+ const targetPath = id ? `${moduleDefinition.basePath}/field/${id}` : `${moduleDefinition.basePath}/field`;
950
+ return {
951
+ module: moduleDefinition.key,
952
+ moduleName: moduleDefinition.name,
953
+ readonly: true,
954
+ targetPath,
955
+ data: await client.post(targetPath, undefined, cleanQuery({ type: args.type }))
956
+ };
957
+ },
958
+ crm_list_jxc_files: async (args) => {
959
+ const moduleDefinition = getJxcModuleByKey(args.module);
960
+ const targetPath = `${moduleDefinition.basePath}/queryFileList`;
961
+ return {
962
+ module: moduleDefinition.key,
963
+ moduleName: moduleDefinition.name,
964
+ readonly: true,
965
+ targetPath,
966
+ data: await client.post(targetPath, undefined, cleanQuery({ id: requireJxcId(args, "查询 JXC 附件请提供 id。"), fileType: args.fileType }))
967
+ };
968
+ },
969
+ crm_list_jxc_stock_products: async (args) => {
970
+ const targetPath = "/jxcWarehouseProduct/queryPageList";
971
+ return {
972
+ module: "warehouseProduct",
973
+ moduleName: "产品库存",
974
+ readonly: true,
975
+ targetPath,
976
+ data: await client.post(targetPath, buildJxcStockProductsBody(args))
977
+ };
978
+ },
979
+ crm_get_jxc_product_inventory: async (args) => {
980
+ const productId = requireJxcProductId(args);
981
+ const targetPath = `/jxcProduct/queryProductInventory/${productId}`;
982
+ return {
983
+ module: "productInventory",
984
+ moduleName: "产品库存",
985
+ readonly: true,
986
+ targetPath,
987
+ data: await client.post(targetPath, undefined, cleanQuery({ warehouseId: args.warehouseId }))
988
+ };
989
+ },
990
+ crm_list_jxc_stock_movements: async (args) => {
991
+ const targetPath = "/jxcDetailed/queryPageList";
992
+ return {
993
+ module: "detailed",
994
+ moduleName: "出入库明细",
995
+ readonly: true,
996
+ targetPath,
997
+ data: await client.post(targetPath, buildJxcStockMovementsBody(args))
998
+ };
999
+ },
1000
+ crm_get_jxc_warehouse_product_stock: async (args) => {
1001
+ const targetPath = "/jxcWarehouse/queryProductWarehouseNumBatch";
1002
+ return {
1003
+ module: "warehouseProductStock",
1004
+ moduleName: "仓库产品库存数量",
1005
+ readonly: true,
1006
+ targetPath,
1007
+ data: await client.post(targetPath, requireWarehouseProductIdsMap(args))
1008
+ };
1009
+ },
1010
+ crm_get_jxc_warehouse_names: async (args) => {
1011
+ const targetPath = "/jxcWarehouse/queryWarehouseNameByIds";
1012
+ return {
1013
+ module: "warehouse",
1014
+ moduleName: "仓库",
1015
+ readonly: true,
1016
+ targetPath,
1017
+ data: await client.post(targetPath, requireWarehouseIds(args))
1018
+ };
1019
+ },
1020
+ crm_write_jxc: async (args) => executeDomainWrite(client, WRITE_DOMAINS.jxc, args),
1021
+ crm_create_jxc_record: async (args) => {
1022
+ const moduleDefinition = getJxcModuleByKey(args.module);
1023
+ const targetPath = `${moduleDefinition.basePath}/add`;
1024
+ const body = buildJxcRecordSaveBody(args, moduleDefinition, false);
1025
+ if (!args.confirm) {
1026
+ return previewDomainWrite(WRITE_DOMAINS.jxc, targetPath, body, args.query);
1027
+ }
1028
+ return {
1029
+ module: moduleDefinition.key,
1030
+ moduleName: moduleDefinition.name,
1031
+ targetPath,
1032
+ body,
1033
+ data: await client.post(targetPath, body, cleanQuery(args.query ?? {}))
1034
+ };
1035
+ },
1036
+ crm_update_jxc_record: async (args) => {
1037
+ const moduleDefinition = getJxcModuleByKey(args.module);
1038
+ const targetPath = args.useInformationUpdate ? `${moduleDefinition.basePath}/updateInformation` : `${moduleDefinition.basePath}/update`;
1039
+ const body = args.useInformationUpdate
1040
+ ? buildJxcUpdateInformationBody(args, moduleDefinition)
1041
+ : buildJxcRecordSaveBody(args, moduleDefinition, true);
1042
+ if (!args.confirm) {
1043
+ return previewDomainWrite(WRITE_DOMAINS.jxc, targetPath, body, args.query);
1044
+ }
1045
+ return {
1046
+ module: moduleDefinition.key,
1047
+ moduleName: moduleDefinition.name,
1048
+ targetPath,
1049
+ body,
1050
+ data: await client.post(targetPath, body, cleanQuery(args.query ?? {}))
1051
+ };
1052
+ },
1053
+ crm_write_finance: async (args) => executeDomainWrite(client, WRITE_DOMAINS.finance, args),
1054
+ crm_create_finance_voucher: async (args) => {
1055
+ const targetPath = "/financeCertificate/add";
1056
+ const body = buildFinanceVoucherWriteBody(args, false);
1057
+ if (!args.confirm) {
1058
+ return previewDomainWrite(WRITE_DOMAINS.finance, targetPath, body, args.query);
1059
+ }
1060
+ return {
1061
+ module: "financeCertificate",
1062
+ moduleName: "凭证",
1063
+ targetPath,
1064
+ body,
1065
+ data: await client.post(targetPath, body, cleanQuery(args.query ?? {}))
1066
+ };
1067
+ },
1068
+ crm_update_finance_voucher: async (args) => {
1069
+ const targetPath = "/financeCertificate/update";
1070
+ const body = buildFinanceVoucherWriteBody(args, true);
1071
+ if (!args.confirm) {
1072
+ return previewDomainWrite(WRITE_DOMAINS.finance, targetPath, body, args.query);
1073
+ }
1074
+ return {
1075
+ module: "financeCertificate",
1076
+ moduleName: "凭证",
1077
+ targetPath,
1078
+ body,
1079
+ data: await client.post(targetPath, body, cleanQuery(args.query ?? {}))
1080
+ };
1081
+ },
1082
+ crm_list_calendar_events: async (args) => ({
1083
+ module: "calendarEvent",
1084
+ targetPath: "/oaEvent/queryList",
1085
+ data: await client.post("/oaEvent/queryList", buildCalendarListBody(args))
1086
+ }),
1087
+ crm_get_calendar_event: async (args) => ({
1088
+ module: "calendarEvent",
1089
+ targetPath: "/oaEvent/queryById",
1090
+ data: await client.post("/oaEvent/queryById", buildCalendarDetailBody(args))
1091
+ }),
1092
+ crm_list_oa_announcements: async (args) => {
1093
+ const targetPath = "/oaAnnouncement/queryList";
1094
+ const query = buildOaAnnouncementListQuery(args);
1095
+ return {
1096
+ module: "oaAnnouncement",
1097
+ moduleName: "公告",
1098
+ readonly: true,
1099
+ targetPath,
1100
+ query,
1101
+ data: await client.post(targetPath, undefined, query)
1102
+ };
1103
+ },
1104
+ crm_get_oa_announcement: async (args) => {
1105
+ const announcementId = requireOaAnnouncementId(args, "查询 OA 公告请提供 announcementId 或 id。");
1106
+ const targetPath = `/oaAnnouncement/queryById/${announcementId}`;
1107
+ return {
1108
+ module: "oaAnnouncement",
1109
+ moduleName: "公告",
1110
+ readonly: true,
1111
+ targetPath,
1112
+ data: await client.post(targetPath)
1113
+ };
1114
+ },
1115
+ crm_create_oa_announcement: async (args) => executeOaAnnouncementWrite(client, args, false),
1116
+ crm_update_oa_announcement: async (args) => executeOaAnnouncementWrite(client, args, true),
1117
+ crm_delete_oa_announcement: async (args) => {
1118
+ const announcementId = requireOaAnnouncementId(args, "删除 OA 公告请提供 announcementId 或 id。");
1119
+ const targetPath = `/oaAnnouncement/delete/${announcementId}`;
1120
+ if (!args.confirm) {
1121
+ return previewOaWrite("delete", "oaAnnouncement", "公告", targetPath, {});
1122
+ }
1123
+ return {
1124
+ module: "oaAnnouncement",
1125
+ moduleName: "公告",
1126
+ targetPath,
1127
+ data: await client.post(targetPath)
1128
+ };
1129
+ },
1130
+ crm_mark_oa_announcement_read: async (args) => {
1131
+ const targetPath = "/oaAnnouncement/readAnnouncement";
1132
+ const body = buildOaAnnouncementReadBody(args);
1133
+ if (!args.confirm) {
1134
+ return previewOaWrite("read", "oaAnnouncement", "公告", targetPath, body);
1135
+ }
1136
+ return {
1137
+ module: "oaAnnouncement",
1138
+ moduleName: "公告",
1139
+ targetPath,
1140
+ body,
1141
+ data: await client.post(targetPath, body)
1142
+ };
1143
+ },
1144
+ crm_list_oa_logs: async (args) => {
1145
+ const targetPath = "/oaLog/queryList";
1146
+ return {
1147
+ module: "oaLog",
1148
+ moduleName: "日志",
1149
+ readonly: true,
1150
+ targetPath,
1151
+ data: await client.post(targetPath, buildOaLogListBody(args))
1152
+ };
1153
+ },
1154
+ crm_get_oa_log: async (args) => {
1155
+ const targetPath = "/oaLog/queryById";
1156
+ const query = { logId: requireOaLogId(args, "查询 OA 日志请提供 logId 或 id。") };
1157
+ return {
1158
+ module: "oaLog",
1159
+ moduleName: "日志",
1160
+ readonly: true,
1161
+ targetPath,
1162
+ query,
1163
+ data: await client.post(targetPath, undefined, query)
1164
+ };
1165
+ },
1166
+ crm_get_oa_log_information: async (args) => {
1167
+ const logId = requireOaLogId(args, "查询 OA 日志详情页字段请提供 logId 或 id。");
1168
+ const targetPath = `/oaLog/information/${logId}`;
1169
+ return {
1170
+ module: "oaLog",
1171
+ moduleName: "日志",
1172
+ readonly: true,
1173
+ targetPath,
1174
+ data: await client.post(targetPath)
1175
+ };
1176
+ },
1177
+ crm_list_oa_log_templates: async (args) => {
1178
+ const targetPath = "/oaLogTemplate/queryPartList";
1179
+ return {
1180
+ module: "oaLogTemplate",
1181
+ moduleName: "日志模板",
1182
+ readonly: true,
1183
+ targetPath,
1184
+ data: await client.post(targetPath, buildOaLogTemplateListBody(args))
1185
+ };
1186
+ },
1187
+ crm_get_oa_log_fields: async (args) => {
1188
+ const logId = args.logId ?? args.id;
1189
+ const targetPath = logId ? `/oaLogTemplateField/field/${logId}` : "/oaLogTemplateField/field";
1190
+ return {
1191
+ module: "oaLog",
1192
+ moduleName: "日志字段",
1193
+ readonly: true,
1194
+ targetPath,
1195
+ data: await client.post(targetPath, undefined, buildOaLogFieldQuery(args))
1196
+ };
1197
+ },
1198
+ crm_get_oa_log_welcome: async () => {
1199
+ const targetPath = "/oaLog/getLogWelcomeSpeech";
1200
+ return {
1201
+ module: "oaLog",
1202
+ moduleName: "日志欢迎语",
1203
+ readonly: true,
1204
+ targetPath,
1205
+ data: await client.post(targetPath)
1206
+ };
1207
+ },
1208
+ crm_get_oa_log_bulletin: async () => {
1209
+ const targetPath = "/oaLog/queryLogBulletin";
1210
+ return {
1211
+ module: "oaLog",
1212
+ moduleName: "日志概要",
1213
+ readonly: true,
1214
+ targetPath,
1215
+ data: await client.post(targetPath)
1216
+ };
1217
+ },
1218
+ crm_get_oa_log_complete_stats: async (args) => {
1219
+ const targetPath = "/oaLog/queryCompleteStats";
1220
+ const query = { configId: requireOaLogConfigId(args) };
1221
+ return {
1222
+ module: "oaLog",
1223
+ moduleName: "日志完成统计",
1224
+ readonly: true,
1225
+ targetPath,
1226
+ query,
1227
+ data: await client.post(targetPath, undefined, query)
1228
+ };
1229
+ },
1230
+ crm_list_oa_log_complete_records: async (args) => {
1231
+ const targetPath = "/oaLog/queryCompleteOaLogList";
1232
+ return {
1233
+ module: "oaLog",
1234
+ moduleName: "日志已完成列表",
1235
+ readonly: true,
1236
+ targetPath,
1237
+ data: await client.post(targetPath, buildOaLogListBody(args))
1238
+ };
1239
+ },
1240
+ crm_list_oa_log_incomplete_records: async (args) => {
1241
+ const targetPath = "/oaLog/queryIncompleteOaLogList";
1242
+ return {
1243
+ module: "oaLog",
1244
+ moduleName: "日志未完成列表",
1245
+ readonly: true,
1246
+ targetPath,
1247
+ data: await client.post(targetPath, buildOaLogListBody(args))
1248
+ };
1249
+ },
1250
+ crm_list_oa_examine_categories: async (args) => {
1251
+ const targetPath = "/examines/queryPartList";
1252
+ return {
1253
+ module: "oaExamine",
1254
+ moduleName: "办公审批",
1255
+ readonly: true,
1256
+ targetPath,
1257
+ data: await client.post(targetPath, buildOaExamineCategoryBody(args))
1258
+ };
1259
+ },
1260
+ crm_list_oa_examine_groups: async (args) => {
1261
+ const targetPath = "/examineSuperExamines/queryExamineGroup";
1262
+ return {
1263
+ module: "oaExamine",
1264
+ moduleName: "办公审批",
1265
+ readonly: true,
1266
+ targetPath,
1267
+ data: await client.post(targetPath, buildOaExamineGroupBody(args))
1268
+ };
1269
+ },
1270
+ crm_list_oa_examines: async (args) => {
1271
+ const listDefinition = getOaExamineListDefinition(args);
1272
+ return {
1273
+ module: "oaExamine",
1274
+ moduleName: "办公审批",
1275
+ readonly: true,
1276
+ listType: listDefinition.key,
1277
+ listName: listDefinition.name,
1278
+ targetPath: listDefinition.targetPath,
1279
+ data: await client.post(listDefinition.targetPath, buildOaExamineListBody(args))
1280
+ };
1281
+ },
1282
+ crm_get_oa_examine: async (args) => {
1283
+ const examineId = requireOaExamineId(args, "查询 OA 审批详情请提供 examineId 或 id。");
1284
+ const targetPath = `/oaExamine/queryOaExamineInfo/${examineId}`;
1285
+ return {
1286
+ module: "oaExamine",
1287
+ moduleName: "办公审批",
1288
+ readonly: true,
1289
+ targetPath,
1290
+ data: await client.post(targetPath)
1291
+ };
1292
+ },
1293
+ crm_get_oa_examine_fields: async (args) => {
1294
+ const categoryId = args.categoryId ?? args.examineIdForFields;
1295
+ if (!isBlank(categoryId) && isBlank(args.examineId) && isBlank(args.id)) {
1296
+ const useListEndpoint = args.useFieldList || args.fieldList || args.mode === "list";
1297
+ const targetPath = useListEndpoint ? `/oaExamineField/queryFieldList/${categoryId}` : `/oaExamineField/queryField/${categoryId}`;
1298
+ return {
1299
+ module: "oaExamine",
1300
+ moduleName: "办公审批",
1301
+ readonly: true,
1302
+ targetPath,
1303
+ data: await client.post(targetPath, useListEndpoint ? undefined : buildOaExamineCreateFieldQuery(args, categoryId))
1304
+ };
1305
+ }
1306
+ const targetPath = "/oaExamine/getField";
1307
+ return {
1308
+ module: "oaExamine",
1309
+ moduleName: "办公审批",
1310
+ readonly: true,
1311
+ targetPath,
1312
+ data: await client.post(targetPath, buildOaExamineFieldBody(args))
1313
+ };
1314
+ },
1315
+ crm_list_oa_examine_record_logs: async (args) => {
1316
+ const targetPath = "/examineRecord/queryExamineRecordLog";
1317
+ return {
1318
+ module: "oaExamine",
1319
+ moduleName: "办公审批",
1320
+ readonly: true,
1321
+ targetPath,
1322
+ data: await client.post(targetPath, buildOaExamineRecordLogBody(args))
1323
+ };
1324
+ },
1325
+ crm_list_oa_examine_flow_records: async (args) => {
1326
+ const targetPath = "/examineSuperExamine/examine/record/list";
1327
+ return {
1328
+ module: "oaExamine",
1329
+ moduleName: "办公审批",
1330
+ readonly: true,
1331
+ targetPath,
1332
+ data: await client.post(targetPath, buildOaExamineFlowRecordBody(args))
1333
+ };
1334
+ },
1335
+ crm_list_oa_tasks: async (args) => ({
1336
+ module: "oaTask",
1337
+ targetPath: "/oaTask/queryTaskList",
1338
+ data: await client.post("/oaTask/queryTaskList", buildOaTaskListBody(args))
1339
+ }),
1340
+ crm_list_related_oa_tasks: async (args) => ({
1341
+ module: "oaTask",
1342
+ targetPath: "/oaTask/queryTypeTaskList",
1343
+ data: await client.post("/oaTask/queryTypeTaskList", undefined, buildRelatedOaTaskQuery(args))
1344
+ }),
1345
+ crm_preview_write: (args) => previewWrite(args),
1346
+ crm_update_customer: async (args) => {
1347
+ const moduleDefinition = getModuleByKey("customer");
1348
+ const targetPath = `${moduleDefinition.basePath}/updateInformation`;
1349
+ const directId = args.customerId ?? args.id;
1350
+ if (!args.confirm) {
1351
+ return previewWrite({
1352
+ ...args,
1353
+ module: "customer",
1354
+ id: directId ?? "<需要通过客户查询唯一命中后填充>",
1355
+ operation: "updateInformation",
1356
+ targetPath
1357
+ });
1358
+ }
1359
+ const resolvedRecord = directId ? undefined : await resolveUniqueRecord(client, moduleDefinition, args);
1360
+ const id = directId ?? resolvedRecord?.[moduleDefinition.primaryKey] ?? resolvedRecord?.id;
1361
+ const body = await buildUpdateInformationBody(client, moduleDefinition, { ...args, id });
1362
+ const data = await client.post(targetPath, body);
1363
+ const verification = args.verify === false ? undefined : await verifyInformationUpdate(client, moduleDefinition, body);
1364
+ return cleanObject({
1365
+ module: moduleDefinition.key,
1366
+ targetPath,
1367
+ resolvedRecord,
1368
+ body,
1369
+ data,
1370
+ verification
1371
+ });
1372
+ },
1373
+ crm_create_record: async (args) => {
1374
+ const moduleDefinition = getModuleByKey(args.module);
1375
+ ensureWritableModule(moduleDefinition);
1376
+ const targetPath = `${moduleDefinition.basePath}/add`;
1377
+ if (!args.confirm) {
1378
+ return previewWrite({ ...args, operation: "create", targetPath });
1379
+ }
1380
+ return {
1381
+ module: moduleDefinition.key,
1382
+ targetPath,
1383
+ data: await client.post(targetPath, buildModelSaveBody(args))
1384
+ };
1385
+ },
1386
+ crm_update_record_fields: async (args) => {
1387
+ const moduleDefinition = getModuleByKey(args.module);
1388
+ ensureWritableModule(moduleDefinition);
1389
+ const targetPath = `${moduleDefinition.basePath}/updateInformation`;
1390
+ if (!args.confirm) {
1391
+ return previewWrite({ ...args, operation: "updateInformation", targetPath });
1392
+ }
1393
+ const body = await buildUpdateInformationBody(client, moduleDefinition, args);
1394
+ const data = await client.post(targetPath, body);
1395
+ const verification = args.verify === false ? undefined : await verifyInformationUpdate(client, moduleDefinition, body);
1396
+ return {
1397
+ module: moduleDefinition.key,
1398
+ targetPath,
1399
+ body,
1400
+ data,
1401
+ verification
1402
+ };
1403
+ },
1404
+ crm_create_product: async (args) => executeCrmProductSave(client, args, false),
1405
+ crm_update_product: async (args) => executeCrmProductSave(client, args, true),
1406
+ crm_update_product_fields: async (args) => {
1407
+ const moduleDefinition = getModuleByKey("product");
1408
+ const id = args.productId ?? args.id;
1409
+ const targetPath = "/crmProduct/updateInformation";
1410
+ if (!args.confirm) {
1411
+ return previewWrite({ ...args, module: "product", id, operation: "updateInformation", targetPath });
1412
+ }
1413
+ const body = await buildUpdateInformationBody(client, moduleDefinition, { ...args, id });
1414
+ const data = await client.post(targetPath, body);
1415
+ const verification = args.verify === false ? undefined : await verifyInformationUpdate(client, moduleDefinition, body);
1416
+ return cleanObject({
1417
+ module: "product",
1418
+ moduleName: "产品",
1419
+ targetPath,
1420
+ body,
1421
+ data,
1422
+ verification
1423
+ });
1424
+ },
1425
+ crm_update_product_status: async (args) => executeCrmProductSimpleWrite(client, args, "status", "/crmProduct/updateStatus", buildCrmProductStatusBody(args)),
1426
+ crm_transfer_products_owner: async (args) => executeCrmProductSimpleWrite(client, args, "transferOwner", "/crmProduct/changeOwnerUser", buildCrmProductTransferBody(args)),
1427
+ crm_delete_products: async (args) => executeCrmProductSimpleWrite(client, args, "delete", "/crmProduct/deleteByIds", requireIdArray(args.ids ?? args.productIds, "删除 CRM 产品请提供 ids 或 productIds 数组。")),
1428
+ crm_create_contract: async (args) => executeCrmDocumentWrite(client, "contract", args, false),
1429
+ crm_update_contract: async (args) => executeCrmDocumentWrite(client, "contract", args, true),
1430
+ crm_create_receivables: async (args) => executeCrmDocumentWrite(client, "receivables", args, false),
1431
+ crm_update_receivables: async (args) => executeCrmDocumentWrite(client, "receivables", args, true),
1432
+ crm_create_receivables_plan: async (args) => executeCrmDocumentWrite(client, "receivablesPlan", args, false),
1433
+ crm_update_receivables_plan: async (args) => executeCrmDocumentWrite(client, "receivablesPlan", args, true),
1434
+ crm_create_invoice: async (args) => executeCrmDocumentWrite(client, "invoice", args, false),
1435
+ crm_update_invoice: async (args) => executeCrmDocumentWrite(client, "invoice", args, true),
1436
+ crm_create_quotation: async (args) => executeCrmDocumentWrite(client, "quotation", args, false),
1437
+ crm_update_quotation: async (args) => executeCrmDocumentWrite(client, "quotation", args, true),
1438
+ crm_create_calendar_event: async (args) => {
1439
+ const targetPath = "/oaEvent/save";
1440
+ const body = buildCalendarEventBody(args, false);
1441
+ if (!args.confirm) {
1442
+ return previewCalendarWrite("createCalendarEvent", targetPath, body);
1443
+ }
1444
+ return {
1445
+ module: "calendarEvent",
1446
+ targetPath,
1447
+ body,
1448
+ data: await client.post(targetPath, body)
1449
+ };
1450
+ },
1451
+ crm_update_calendar_event: async (args) => {
1452
+ const targetPath = "/oaEvent/update";
1453
+ const body = buildCalendarEventBody(args, true);
1454
+ if (!args.confirm) {
1455
+ return previewCalendarWrite("updateCalendarEvent", targetPath, body);
1456
+ }
1457
+ return {
1458
+ module: "calendarEvent",
1459
+ targetPath,
1460
+ body,
1461
+ data: await client.post(targetPath, body)
1462
+ };
1463
+ },
1464
+ crm_create_oa_log: async (args) => executeOaLogWrite(client, args, false),
1465
+ crm_update_oa_log: async (args) => executeOaLogWrite(client, args, true),
1466
+ crm_delete_oa_log: async (args) => {
1467
+ const targetPath = "/oaLog/deleteById";
1468
+ const query = { logId: requireOaLogId(args, "删除 OA 日志请提供 logId 或 id。") };
1469
+ if (!args.confirm) {
1470
+ return previewOaWrite("delete", "oaLog", "日志", targetPath, query);
1471
+ }
1472
+ return {
1473
+ module: "oaLog",
1474
+ moduleName: "日志",
1475
+ targetPath,
1476
+ query,
1477
+ data: await client.post(targetPath, undefined, query)
1478
+ };
1479
+ },
1480
+ crm_toggle_oa_log_favour: async (args) => {
1481
+ const targetPath = "/oaLog/favourOrCancel";
1482
+ const query = buildOaLogFavourQuery(args);
1483
+ if (!args.confirm) {
1484
+ return previewOaWrite("favourOrCancel", "oaLog", "日志", targetPath, query);
1485
+ }
1486
+ return {
1487
+ module: "oaLog",
1488
+ moduleName: "日志",
1489
+ targetPath,
1490
+ query,
1491
+ data: await client.post(targetPath, undefined, query)
1492
+ };
1493
+ },
1494
+ crm_create_oa_examine: async (args) => {
1495
+ const targetPath = "/oaExamine/setOaExamine";
1496
+ const body = await buildOaExamineWriteBody(client, args, false);
1497
+ if (!args.confirm) {
1498
+ return previewOaWrite("createOaExamine", "oaExamine", "办公审批", targetPath, body);
1499
+ }
1500
+ return {
1501
+ module: "oaExamine",
1502
+ moduleName: "办公审批",
1503
+ targetPath,
1504
+ body,
1505
+ data: await client.post(targetPath, body)
1506
+ };
1507
+ },
1508
+ crm_save_oa_examine_draft: async (args) => {
1509
+ const targetPath = "/oaExamine/setOaExamineDraft";
1510
+ const body = await buildOaExamineWriteBody(client, args, false);
1511
+ if (!args.confirm) {
1512
+ return previewOaWrite("saveOaExamineDraft", "oaExamine", "办公审批", targetPath, body);
1513
+ }
1514
+ return {
1515
+ module: "oaExamine",
1516
+ moduleName: "办公审批",
1517
+ targetPath,
1518
+ body,
1519
+ data: await client.post(targetPath, body)
1520
+ };
1521
+ },
1522
+ crm_audit_oa_examine: async (args) => {
1523
+ const targetPath = "/examineRecord/auditExamine";
1524
+ const body = buildOaExamineAuditBody(args, false);
1525
+ if (!args.confirm) {
1526
+ return previewOaWrite("auditOaExamine", "oaExamine", "办公审批", targetPath, body);
1527
+ }
1528
+ return {
1529
+ module: "oaExamine",
1530
+ moduleName: "办公审批",
1531
+ targetPath,
1532
+ body,
1533
+ data: await client.post(targetPath, body)
1534
+ };
1535
+ },
1536
+ crm_batch_audit_oa_examine: async (args) => {
1537
+ const targetPath = "/examineRecord/batchAuditExamine";
1538
+ const body = buildOaExamineAuditBody(args, true);
1539
+ if (!args.confirm) {
1540
+ return previewOaWrite("batchAuditOaExamine", "oaExamine", "办公审批", targetPath, body);
1541
+ }
1542
+ return {
1543
+ module: "oaExamine",
1544
+ moduleName: "办公审批",
1545
+ targetPath,
1546
+ body,
1547
+ data: await client.post(targetPath, body)
1548
+ };
1549
+ },
1550
+ crm_create_oa_task: async (args) => {
1551
+ const targetPath = "/workTask/saveWorkTask";
1552
+ const body = buildOaTaskBody(args);
1553
+ if (!args.confirm) {
1554
+ return previewOaTaskWrite("createOaTask", targetPath, body);
1555
+ }
1556
+ return {
1557
+ module: "oaTask",
1558
+ targetPath,
1559
+ body,
1560
+ data: await client.post(targetPath, body)
1561
+ };
1562
+ },
1563
+ crm_update_oa_task_fields: async (args) => {
1564
+ const calls = buildOaTaskUpdateCalls(args);
1565
+ if (!args.confirm) {
1566
+ return {
1567
+ preview: true,
1568
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
1569
+ module: "oaTask",
1570
+ moduleName: "OA任务",
1571
+ operation: "updateOaTaskFields",
1572
+ calls
1573
+ };
1574
+ }
1575
+ const results = [];
1576
+ for (const call of calls) {
1577
+ results.push({
1578
+ targetPath: call.targetPath,
1579
+ data: await client.post(call.targetPath, call.body)
1580
+ });
1581
+ }
1582
+ return {
1583
+ module: "oaTask",
1584
+ results
1585
+ };
1586
+ },
1587
+ crm_add_followup: async (args) => {
1588
+ const targetPath = "/crmActivity/add";
1589
+ if (!args.confirm) {
1590
+ return previewWrite({ ...args, operation: "addFollowup", targetPath });
1591
+ }
1592
+ return {
1593
+ module: "activity",
1594
+ targetPath,
1595
+ data: await client.post(targetPath, buildFollowupSaveBody(args))
1596
+ };
1597
+ },
1598
+ crm_update_followup: async (args) => {
1599
+ const targetPath = args.useInformationUpdate ? "/crmActivity/updateInformation" : "/crmActivity/update";
1600
+ if (!args.confirm) {
1601
+ return previewWrite({ ...args, operation: "updateFollowup", targetPath });
1602
+ }
1603
+ const body = args.useInformationUpdate
1604
+ ? cleanObject({ id: args.id, label: 19, list: args.list ?? args.fields ?? [], batchId: args.batchId })
1605
+ : buildFollowupSaveBody(args);
1606
+ return {
1607
+ module: "activity",
1608
+ targetPath,
1609
+ data: await client.post(targetPath, body)
1610
+ };
1611
+ },
1612
+ workorder_search_records: async (args) => searchWorkorders(client, args),
1613
+ workorder_get_record: async (args) => getWorkorder(client, args),
1614
+ workorder_get_schema: async (args) => getWorkorderSchema(client, args),
1615
+ workorder_preview_write: async (args) => previewWorkorderWrite(args),
1616
+ workorder_create_record: async (args) => saveWorkorder(client, args, false),
1617
+ workorder_update_record: async (args) => saveWorkorder(client, args, true)
1618
+ };
1619
+ }
1620
+ async function searchWorkorders(client, args) {
1621
+ const scope = normalizeWorkorderScope(args.scope, true);
1622
+ const body = cleanObject({
1623
+ page: positiveInteger(args.page, 1),
1624
+ limit: positiveInteger(args.limit, 15),
1625
+ search: args.search ?? args.keyword ?? args.query,
1626
+ statusId: args.statusId,
1627
+ status: args.status,
1628
+ templateId: args.templateId,
1629
+ priority: args.priority,
1630
+ sceneId: args.sceneId,
1631
+ viewMode: args.viewMode,
1632
+ startTime: args.startTime,
1633
+ endTime: args.endTime,
1634
+ isPool: scope === "pool" ? 1 : 0
1635
+ });
1636
+ const targetPath = scope === "offline" ? "/wkoOfflineWorkorder/queryPageList" : "/wkoWorkorder/queryPageList";
1637
+ return {
1638
+ module: "workorder",
1639
+ scope,
1640
+ readonly: true,
1641
+ targetPath,
1642
+ data: await client.post(targetPath, body)
1643
+ };
1644
+ }
1645
+ async function getWorkorder(client, args) {
1646
+ const scope = normalizeWorkorderScope(args.scope, false);
1647
+ const id = requiredWorkorderId(args.id);
1648
+ const prefix = scope === "offline" ? "/wkoOfflineWorkorder" : "/wkoWorkorder";
1649
+ const [record, information] = await Promise.all([
1650
+ client.post(`${prefix}/queryById/${id}`),
1651
+ client.post(`${prefix}/information/${id}`)
1652
+ ]);
1653
+ return {
1654
+ module: "workorder",
1655
+ scope,
1656
+ id,
1657
+ record,
1658
+ information
1659
+ };
1660
+ }
1661
+ async function getWorkorderSchema(client, args) {
1662
+ const scope = normalizeWorkorderScope(args.scope, false);
1663
+ const prefix = scope === "offline" ? "/wkoOfflineWorkorder" : "/wkoWorkorder";
1664
+ const id = args.id === undefined || args.id === null || args.id === "" ? "" : `/${requiredWorkorderId(args.id)}`;
1665
+ return {
1666
+ module: "workorder",
1667
+ scope,
1668
+ readonly: true,
1669
+ targetPath: `${prefix}/field${id}`,
1670
+ fields: await client.post(`${prefix}/field${id}`, cleanObject({ templateId: args.templateId }))
1671
+ };
1672
+ }
1673
+ function previewWorkorderWrite(args) {
1674
+ const update = Boolean(args.id ?? args.workorderId ?? args.offlineWorkorderId);
1675
+ const prepared = prepareWorkorderWrite(args, update);
1676
+ return {
1677
+ preview: true,
1678
+ message: "写入前预览:未传 confirm=true,MCP 不会调用工单写接口。",
1679
+ ...prepared
1680
+ };
1681
+ }
1682
+ async function saveWorkorder(client, args, update) {
1683
+ const prepared = prepareWorkorderWrite(args, update);
1684
+ if (args.confirm !== true) {
1685
+ return {
1686
+ preview: true,
1687
+ message: "写入前预览:请核对字段并传 confirm=true 后再执行。",
1688
+ ...prepared
1689
+ };
1690
+ }
1691
+ const data = await client.post(prepared.targetPath, prepared.body);
1692
+ const id = extractSavedWorkorderId(data, prepared);
1693
+ const verification = id
1694
+ ? await getWorkorder(client, { scope: prepared.scope, id })
1695
+ : { ok: false, message: "服务端未返回工单 ID,请使用列表查询核对写入结果。" };
1696
+ return {
1697
+ preview: false,
1698
+ scope: prepared.scope,
1699
+ targetPath: prepared.targetPath,
1700
+ data,
1701
+ verification
1702
+ };
1703
+ }
1704
+ function prepareWorkorderWrite(args, update) {
1705
+ const scope = normalizeWorkorderScope(args.scope, false);
1706
+ if (scope === "offline") {
1707
+ const source = safeExtra(args.body ?? args.record ?? args.data);
1708
+ const id = update ? requiredWorkorderId(args.id ?? args.offlineWorkorderId ?? source.offlineWorkorderId) : undefined;
1709
+ const body = cleanObject({ ...source, ...(id ? { offlineWorkorderId: id } : {}) });
1710
+ return { scope, targetPath: `/wkoOfflineWorkorder/${update ? "update" : "add"}`, body };
1711
+ }
1712
+ const sourceBody = safeExtra(args.body);
1713
+ const sourceEntity = safeExtra(sourceBody.entity ?? args.entity ?? args.record ?? args.data);
1714
+ const id = update ? requiredWorkorderId(args.id ?? args.workorderId ?? sourceEntity.workorderId) : undefined;
1715
+ const entity = cleanObject({ ...sourceEntity, ...(id ? { workorderId: id } : {}) });
1716
+ const body = Object.keys(sourceBody).length > 0
1717
+ ? { ...sourceBody, entity }
1718
+ : cleanObject({
1719
+ entity,
1720
+ field: args.field ?? args.fields ?? [],
1721
+ ownerUserList: args.ownerUserList,
1722
+ member: args.member,
1723
+ saveType: args.saveType
1724
+ });
1725
+ return { scope, targetPath: `/wkoWorkorder/${update ? "update" : "add"}`, body };
1726
+ }
1727
+ function normalizeWorkorderScope(value, allowPool) {
1728
+ const scope = String(value ?? "workorder").trim();
1729
+ const allowed = allowPool ? ["workorder", "pool", "offline"] : ["workorder", "offline"];
1730
+ if (!allowed.includes(scope)) {
1731
+ throw new Error(`不支持的工单范围:${scope}。可选值:${allowed.join("、")}。`);
1732
+ }
1733
+ return scope;
1734
+ }
1735
+ function requiredWorkorderId(value) {
1736
+ const id = String(value ?? "").trim();
1737
+ if (!id) {
1738
+ throw new Error("工单 ID 不能为空。");
1739
+ }
1740
+ return id;
1741
+ }
1742
+ function extractSavedWorkorderId(data, prepared) {
1743
+ if (prepared.scope === "offline") {
1744
+ return data?.offlineWorkorderId ?? prepared.body.offlineWorkorderId;
1745
+ }
1746
+ return data?.workorderId ?? prepared.body.entity?.workorderId;
1747
+ }
1748
+ const FIELD_SEARCH_CONTAINS = 3;
1749
+ const QUICK_SEARCH_FIELDS = {
1750
+ leads: [
1751
+ { fieldName: "leadsName", formType: "text" },
1752
+ { fieldName: "mobile", formType: "mobile" },
1753
+ { fieldName: "telephone", formType: "text" }
1754
+ ],
1755
+ leadsPool: [
1756
+ { fieldName: "leadsName", formType: "text" },
1757
+ { fieldName: "mobile", formType: "mobile" },
1758
+ { fieldName: "telephone", formType: "text" }
1759
+ ],
1760
+ customer: [
1761
+ { fieldName: "customerName", formType: "text" },
1762
+ { fieldName: "mobile", formType: "mobile" },
1763
+ { fieldName: "telephone", formType: "text" }
1764
+ ],
1765
+ customerPool: [
1766
+ { fieldName: "customerName", formType: "text" },
1767
+ { fieldName: "mobile", formType: "mobile" },
1768
+ { fieldName: "telephone", formType: "text" }
1769
+ ],
1770
+ contacts: [
1771
+ { fieldName: "name", formType: "text" },
1772
+ { fieldName: "mainFieldValue", formType: "text" },
1773
+ { fieldName: "mobile", formType: "mobile" },
1774
+ { fieldName: "telephone", formType: "text" },
1775
+ { fieldName: "email", formType: "email" }
1776
+ ],
1777
+ business: [{ fieldName: "businessName", formType: "text" }],
1778
+ product: [{ fieldName: "name", formType: "text" }],
1779
+ contract: [
1780
+ { fieldName: "num", formType: "text" },
1781
+ { fieldName: "name", formType: "text" },
1782
+ { fieldName: "customerName", formType: "text" }
1783
+ ],
1784
+ receivables: [
1785
+ { fieldName: "number", formType: "text" },
1786
+ { fieldName: "customerName", formType: "text" }
1787
+ ],
1788
+ receivablesPlan: [
1789
+ { fieldName: "customerName", formType: "text" },
1790
+ { fieldName: "contractNum", formType: "text" }
1791
+ ],
1792
+ invoice: [
1793
+ { fieldName: "invoiceApplyNumber", formType: "text" },
1794
+ { fieldName: "invoiceNumber", formType: "text" },
1795
+ { fieldName: "customerName", formType: "text" }
1796
+ ],
1797
+ activity: [{ fieldName: "content", formType: "text" }],
1798
+ quotation: [
1799
+ { fieldName: "name", formType: "text" },
1800
+ { fieldName: "num", formType: "text" },
1801
+ { fieldName: "customerName", formType: "text" }
1802
+ ],
1803
+ returnVisit: [
1804
+ { fieldName: "customerName", formType: "text" },
1805
+ { fieldName: "contractNum", formType: "text" }
1806
+ ],
1807
+ visitPlan: [
1808
+ { fieldName: "customerName", formType: "text" },
1809
+ { fieldName: "contactsName", formType: "text" }
1810
+ ]
1811
+ };
1812
+ const NAME_FIELD_BY_MODULE = {
1813
+ leads: { fieldName: "leadsName", formType: "text" },
1814
+ leadsPool: { fieldName: "leadsName", formType: "text" },
1815
+ customer: { fieldName: "customerName", formType: "text" },
1816
+ customerPool: { fieldName: "customerName", formType: "text" },
1817
+ contacts: { fieldName: "name", formType: "text" },
1818
+ business: { fieldName: "businessName", formType: "text" },
1819
+ product: { fieldName: "name", formType: "text" },
1820
+ contract: { fieldName: "name", formType: "text" },
1821
+ quotation: { fieldName: "name", formType: "text" }
1822
+ };
1823
+ const USER_FILTER_FORM_TYPES = new Set(["user", "single_user"]);
1824
+ const USER_FILTER_FIELD_NAMES = new Set(["ownerUserId", "createUserId"]);
1825
+ const USER_FILTER_ARGUMENTS = [
1826
+ {
1827
+ fieldName: "ownerUserId",
1828
+ idKeys: ["ownerUserId", "ownerUserIds"],
1829
+ nicknameKeys: ["ownerUserName", "ownerUserNames", "ownerName", "ownerNames"]
1830
+ },
1831
+ {
1832
+ fieldName: "createUserId",
1833
+ idKeys: ["createUserId", "createUserIds", "creatorUserId", "creatorUserIds"],
1834
+ nicknameKeys: ["createUserName", "createUserNames", "creatorUserName", "creatorUserNames", "creatorName", "creatorNames"]
1835
+ }
1836
+ ];
1837
+ async function buildResolvedSearchList(client, args, moduleDefinition) {
1838
+ const searchList = buildSearchList(args, moduleDefinition);
1839
+ const userSearchList = buildConvenienceUserSearchList(args);
1840
+ return resolveUserSearchFilters(client, combineSearchLists(searchList, userSearchList), args.companyId);
1841
+ }
1842
+ function buildSearchList(args, moduleDefinition) {
1843
+ const explicitSearchList = toArray(args.filters ?? args.searchList);
1844
+ const semanticSearchList = buildSemanticSearchList(args, moduleDefinition);
1845
+ return combineSearchLists(explicitSearchList, semanticSearchList);
1846
+ }
1847
+ function buildConvenienceUserSearchList(args) {
1848
+ return USER_FILTER_ARGUMENTS.flatMap(({ fieldName, idKeys, nicknameKeys }) => {
1849
+ const values = [...argumentValues(args, idKeys), ...argumentValues(args, nicknameKeys)];
1850
+ if (values.length === 0) {
1851
+ return [];
1852
+ }
1853
+ return [{
1854
+ fieldName,
1855
+ formType: "user",
1856
+ type: 1,
1857
+ searchEnum: 1,
1858
+ values
1859
+ }];
1860
+ });
1861
+ }
1862
+ function argumentValues(args, keys) {
1863
+ return keys.flatMap((key) => valueArray(args[key]));
1864
+ }
1865
+ function valueArray(value) {
1866
+ if (value === undefined || value === null) {
1867
+ return [];
1868
+ }
1869
+ return Array.isArray(value) ? value : [value];
1870
+ }
1871
+ function combineSearchLists(left, right) {
1872
+ if (left.length === 0) {
1873
+ return right;
1874
+ }
1875
+ if (right.length === 0) {
1876
+ return left;
1877
+ }
1878
+ const leftGroups = groupSearchFilters(left);
1879
+ const rightGroups = groupSearchFilters(right);
1880
+ const result = [];
1881
+ let groupId = 0;
1882
+ for (const leftGroup of leftGroups) {
1883
+ for (const rightGroup of rightGroups) {
1884
+ for (const filter of [...leftGroup, ...rightGroup]) {
1885
+ result.push({ ...filter, groupId });
1886
+ }
1887
+ groupId += 1;
1888
+ }
1889
+ }
1890
+ return result;
1891
+ }
1892
+ function groupSearchFilters(filters) {
1893
+ const groups = new Map();
1894
+ for (const filter of filters) {
1895
+ const groupId = filter.groupId ?? 0;
1896
+ const group = groups.get(groupId) ?? [];
1897
+ group.push(filter);
1898
+ groups.set(groupId, group);
1899
+ }
1900
+ return [...groups.values()];
1901
+ }
1902
+ async function resolveUserSearchFilters(client, searchList, companyId) {
1903
+ const nicknameCache = new Map();
1904
+ const resolvedFilters = [];
1905
+ for (const filter of searchList) {
1906
+ if (!isUserSearchFilter(filter)) {
1907
+ resolvedFilters.push(filter);
1908
+ continue;
1909
+ }
1910
+ const rawValues = valueArray(filter.values ?? filter.value);
1911
+ const searchType = Number(filter.type ?? filter.searchEnum);
1912
+ if (rawValues.length === 0 && (searchType === 5 || searchType === 6)) {
1913
+ resolvedFilters.push({ ...filter, formType: filter.formType ?? "user" });
1914
+ continue;
1915
+ }
1916
+ if (rawValues.length === 0) {
1917
+ throw new Error(`用户类型筛选“${filter.fieldName ?? filter.name ?? "未知字段"}”请提供 values。`);
1918
+ }
1919
+ const values = [];
1920
+ for (const value of rawValues) {
1921
+ values.push(await resolveUserFilterValue(client, value, nicknameCache, companyId));
1922
+ }
1923
+ resolvedFilters.push({
1924
+ ...filter,
1925
+ formType: filter.formType ?? "user",
1926
+ values: [...new Set(values)]
1927
+ });
1928
+ }
1929
+ return resolvedFilters;
1930
+ }
1931
+ function isUserSearchFilter(filter) {
1932
+ const formType = String(filter.formType ?? "").trim().toLowerCase();
1933
+ return USER_FILTER_FORM_TYPES.has(formType) || USER_FILTER_FIELD_NAMES.has(String(filter.fieldName ?? filter.name ?? ""));
1934
+ }
1935
+ async function resolveUserFilterValue(client, value, nicknameCache, companyId) {
1936
+ const objectValue = isPlainObject(value) ? value : undefined;
1937
+ const idValue = objectValue ? objectValue.userId ?? objectValue.id : value;
1938
+ const userId = normalizePositiveUserId(idValue);
1939
+ if (userId) {
1940
+ return userId;
1941
+ }
1942
+ const nicknameValue = objectValue
1943
+ ? objectValue.realname ?? objectValue.nickname ?? objectValue.name
1944
+ : value;
1945
+ const nickname = firstTextValue(nicknameValue);
1946
+ if (!nickname) {
1947
+ throw new Error("用户类型筛选值必须是正整数用户 ID、昵称字符串,或包含 userId/realname 的对象。");
1948
+ }
1949
+ return resolveUserIdByNickname(client, nickname, nicknameCache, companyId);
1950
+ }
1951
+ function normalizePositiveUserId(value) {
1952
+ if (typeof value === "number") {
1953
+ return Number.isSafeInteger(value) && value > 0 ? String(value) : undefined;
1954
+ }
1955
+ if (typeof value !== "string") {
1956
+ return undefined;
1957
+ }
1958
+ const text = value.trim();
1959
+ return /^[1-9]\d*$/.test(text) ? text : undefined;
1960
+ }
1961
+ async function resolveUserIdByNickname(client, nickname, cache, companyId) {
1962
+ const normalizedNickname = normalizeText(nickname);
1963
+ const cached = cache.get(normalizedNickname);
1964
+ if (cached) {
1965
+ return cached;
1966
+ }
1967
+ const page = await client.post("/adminUser/queryUserList", {
1968
+ page: 1,
1969
+ limit: 100,
1970
+ pageType: 1,
1971
+ realname: nickname,
1972
+ companyId
1973
+ });
1974
+ const users = Array.isArray(page.list)
1975
+ ? page.list
1976
+ : Array.isArray(page.records)
1977
+ ? page.records
1978
+ : [];
1979
+ const exactMatches = users.filter((user) => normalizeText(user.realname ?? user.name) === normalizedNickname);
1980
+ const validMatches = exactMatches.filter((user) => normalizePositiveUserId(user.userId ?? user.id));
1981
+ if (validMatches.length === 1) {
1982
+ const userId = normalizePositiveUserId(validMatches[0].userId ?? validMatches[0].id);
1983
+ cache.set(normalizedNickname, userId);
1984
+ return userId;
1985
+ }
1986
+ if (validMatches.length > 1) {
1987
+ throw new Error(`昵称“${nickname}”匹配到多个用户:${formatUserCandidates(validMatches)}。请直接传入明确的用户 ID。`);
1988
+ }
1989
+ const suggestions = users.filter((user) => normalizePositiveUserId(user.userId ?? user.id)).slice(0, 5);
1990
+ const suggestionText = suggestions.length > 0 ? ` 相近用户:${formatUserCandidates(suggestions)}。` : "";
1991
+ throw new Error(`未找到昵称完全等于“${nickname}”的用户。${suggestionText}请先使用 crm_list_users 核对昵称,或直接传入用户 ID。`);
1992
+ }
1993
+ function formatUserCandidates(users) {
1994
+ return users.map((user) => {
1995
+ const userId = normalizePositiveUserId(user.userId ?? user.id);
1996
+ const realname = firstTextValue(user.realname, user.name) ?? "未命名";
1997
+ const deptName = firstTextValue(user.deptName);
1998
+ return `${realname}(ID: ${userId}${deptName ? `, 部门: ${deptName}` : ""})`;
1999
+ }).join(";");
2000
+ }
2001
+ function buildSemanticSearchList(args, moduleDefinition) {
2002
+ const keyword = firstTextValue(args.keyword, args.query, args.q, args.search, args.searchText, args.text, args.term);
2003
+ if (keyword) {
2004
+ return containsFilters(QUICK_SEARCH_FIELDS[moduleDefinition.key] ?? [], keyword, 1);
2005
+ }
2006
+ const phone = firstTextValue(args.phone, args.mobile, args.telephone);
2007
+ if (phone) {
2008
+ const fields = (QUICK_SEARCH_FIELDS[moduleDefinition.key] ?? []).filter((field) => ["mobile", "telephone", "phone"].includes(field.fieldName));
2009
+ return containsFilters(fields, phone, 1);
2010
+ }
2011
+ const name = firstTextValue(args.name, args.customerName, args.leadsName, args.contactsName, args.businessName, args.productName, args.contractName, args.quotationName);
2012
+ const nameField = NAME_FIELD_BY_MODULE[moduleDefinition.key];
2013
+ if (name && nameField) {
2014
+ return containsFilters([nameField], name, 0);
2015
+ }
2016
+ const email = firstTextValue(args.email);
2017
+ if (email) {
2018
+ return containsFilters([{ fieldName: "email", formType: "email" }], email, 0);
2019
+ }
2020
+ return [];
2021
+ }
2022
+ function containsFilters(fields, value, startGroupId) {
2023
+ return fields.map((field, index) => ({
2024
+ fieldName: field.fieldName,
2025
+ formType: field.formType,
2026
+ type: FIELD_SEARCH_CONTAINS,
2027
+ searchEnum: FIELD_SEARCH_CONTAINS,
2028
+ values: [value],
2029
+ groupId: fields.length > 1 ? startGroupId + index : startGroupId
2030
+ }));
2031
+ }
2032
+ function firstTextValue(...values) {
2033
+ for (const value of values) {
2034
+ if (value === undefined || value === null) {
2035
+ continue;
2036
+ }
2037
+ const text = String(value).trim();
2038
+ if (text) {
2039
+ return text;
2040
+ }
2041
+ }
2042
+ return undefined;
2043
+ }
2044
+ function toArray(value) {
2045
+ if (Array.isArray(value)) {
2046
+ return value;
2047
+ }
2048
+ if (value && typeof value === "object") {
2049
+ return [value];
2050
+ }
2051
+ return [];
2052
+ }
2053
+ async function resolveUniqueRecord(client, moduleDefinition, args) {
2054
+ const searchList = await resolveUserSearchFilters(client, buildSearchList(args, moduleDefinition), args.companyId);
2055
+ if (searchList.length === 0) {
2056
+ throw new Error(`请提供可定位${moduleDefinition.name}的 customerId/id、phone/mobile、name/customerName 或 keyword。`);
2057
+ }
2058
+ const page = await client.post(`${moduleDefinition.basePath}/queryPageList`, cleanObject({
2059
+ page: 1,
2060
+ limit: 2,
2061
+ pageType: 1,
2062
+ label: moduleDefinition.label,
2063
+ searchList
2064
+ }));
2065
+ const list = Array.isArray(page.list) ? page.list : [];
2066
+ if (list.length !== 1) {
2067
+ throw new Error(`需要唯一命中的${moduleDefinition.name}才能写入,当前命中 ${page.totalRow ?? list.length} 条,请先缩小查询条件或直接传入 ID。`);
2068
+ }
2069
+ return list[0];
2070
+ }
2071
+ async function buildUpdateInformationBody(client, moduleDefinition, args) {
2072
+ const rawBody = buildUpdateInformationPreviewBody(moduleDefinition, args);
2073
+ const fields = await fetchModuleFields(client, moduleDefinition, args);
2074
+ const list = hydrateUpdateList(rawBody.list ?? [], fields);
2075
+ const batchId = rawBody.batchId ?? await resolveUpdateBatchId(client, moduleDefinition, rawBody, list);
2076
+ return {
2077
+ ...rawBody,
2078
+ batchId,
2079
+ list
2080
+ };
2081
+ }
2082
+ function buildUpdateInformationPreviewBody(moduleDefinition, args) {
2083
+ return cleanObject({
2084
+ id: args.id ?? args[moduleDefinition.primaryKey],
2085
+ label: moduleDefinition.label,
2086
+ list: rawUpdateList(args),
2087
+ batchId: args.batchId,
2088
+ idList: args.idList,
2089
+ ownerUserSave: args.ownerUserSave
2090
+ });
2091
+ }
2092
+ function rawUpdateList(args) {
2093
+ const raw = args.list ?? args.fields ?? args.field;
2094
+ const list = toArray(raw);
2095
+ if (list.length > 0) {
2096
+ return list;
2097
+ }
2098
+ const updates = args.updates;
2099
+ if (!updates || typeof updates !== "object" || Array.isArray(updates)) {
2100
+ return [];
2101
+ }
2102
+ return Object.entries(updates).map(([key, value]) => looksLikeFieldName(key) ? { fieldName: key, value } : { name: key, value });
2103
+ }
2104
+ function looksLikeFieldName(value) {
2105
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
2106
+ }
2107
+ async function fetchModuleFields(client, moduleDefinition, args) {
2108
+ const id = args.id ?? args[moduleDefinition.primaryKey];
2109
+ const path = id ? `${moduleDefinition.basePath}/field/${id}` : `${moduleDefinition.basePath}/field`;
2110
+ const data = await client.post(path, undefined, cleanQuery({ type: args.type, poolId: args.poolId }));
2111
+ return flattenFieldDefinitions(data);
2112
+ }
2113
+ function flattenFieldDefinitions(input) {
2114
+ const output = [];
2115
+ const seen = new Set();
2116
+ const visit = (value) => {
2117
+ if (!value || typeof value !== "object" || seen.has(value)) {
2118
+ return;
2119
+ }
2120
+ seen.add(value);
2121
+ if (Array.isArray(value)) {
2122
+ value.forEach(visit);
2123
+ return;
2124
+ }
2125
+ const record = value;
2126
+ if (record.fieldName || record.fieldId || (record.name && (record.fieldType !== undefined || record.type !== undefined || record.formType))) {
2127
+ output.push(record);
2128
+ }
2129
+ for (const child of Object.values(record)) {
2130
+ if (child && typeof child === "object") {
2131
+ visit(child);
2132
+ }
2133
+ }
2134
+ };
2135
+ visit(input);
2136
+ return output;
2137
+ }
2138
+ function hydrateUpdateList(list, fields) {
2139
+ return list.map((item) => hydrateUpdateItem(item, fields));
2140
+ }
2141
+ function hydrateUpdateItem(item, fields) {
2142
+ const field = findFieldDefinition(item, fields);
2143
+ if (!field && hasUpdateMetadata(item)) {
2144
+ return normalizeUpdateItem(item);
2145
+ }
2146
+ if (!field) {
2147
+ const fieldLabel = item.name ?? item.fieldName ?? item.fieldId ?? "<unknown>";
2148
+ throw new Error(`未在字段 schema 中找到可写字段:${fieldLabel}。请先调用 crm_get_module_schema 确认字段名。`);
2149
+ }
2150
+ const merged = cleanObject({
2151
+ fieldId: item.fieldId ?? field.fieldId ?? field.id,
2152
+ fieldName: item.fieldName ?? field.fieldName,
2153
+ name: item.name ?? field.name,
2154
+ fieldType: item.fieldType ?? field.fieldType,
2155
+ type: item.type ?? field.type,
2156
+ formType: item.formType ?? field.formType,
2157
+ value: normalizeOptionValue(item.value, field)
2158
+ });
2159
+ return normalizeUpdateItem(merged);
2160
+ }
2161
+ function hasUpdateMetadata(item) {
2162
+ return Boolean(item.fieldName && item.fieldType !== undefined && item.type !== undefined);
2163
+ }
2164
+ function normalizeUpdateItem(item) {
2165
+ return cleanObject({
2166
+ fieldId: item.fieldId,
2167
+ fieldName: item.fieldName,
2168
+ name: item.name,
2169
+ fieldType: item.fieldType,
2170
+ type: item.type,
2171
+ formType: item.formType,
2172
+ value: item.value
2173
+ });
2174
+ }
2175
+ async function resolveUpdateBatchId(client, moduleDefinition, body, list) {
2176
+ if (body.batchId || !body.id || !updateNeedsBatchId(list)) {
2177
+ return undefined;
2178
+ }
2179
+ const data = await client.post(`${moduleDefinition.basePath}/queryById/${body.id}`);
2180
+ return findFirstValueByKey(data, "batchId");
2181
+ }
2182
+ function updateNeedsBatchId(list) {
2183
+ return list.some((item) => {
2184
+ const fieldType = Number(item.fieldType);
2185
+ return fieldType === 0 || fieldType === 2;
2186
+ });
2187
+ }
2188
+ function findFirstValueByKey(input, key) {
2189
+ const seen = new Set();
2190
+ const visit = (value) => {
2191
+ if (!value || typeof value !== "object" || seen.has(value)) {
2192
+ return undefined;
2193
+ }
2194
+ seen.add(value);
2195
+ if (Array.isArray(value)) {
2196
+ for (const item of value) {
2197
+ const found = visit(item);
2198
+ if (found !== undefined && found !== null && found !== "") {
2199
+ return found;
2200
+ }
2201
+ }
2202
+ return undefined;
2203
+ }
2204
+ const record = value;
2205
+ if (record[key] !== undefined && record[key] !== null && record[key] !== "") {
2206
+ return record[key];
2207
+ }
2208
+ for (const child of Object.values(record)) {
2209
+ const found = visit(child);
2210
+ if (found !== undefined && found !== null && found !== "") {
2211
+ return found;
2212
+ }
2213
+ }
2214
+ return undefined;
2215
+ };
2216
+ return visit(input);
2217
+ }
2218
+ function findFieldDefinition(item, fields) {
2219
+ const fieldId = item.fieldId !== undefined ? String(item.fieldId) : undefined;
2220
+ const fieldName = normalizeText(item.fieldName);
2221
+ const name = normalizeText(item.name);
2222
+ return fields.find((field) => {
2223
+ if (fieldId && String(field.fieldId ?? field.id) === fieldId) {
2224
+ return true;
2225
+ }
2226
+ if (fieldName && normalizeText(field.fieldName) === fieldName) {
2227
+ return true;
2228
+ }
2229
+ return Boolean(name && normalizeText(field.name) === name);
2230
+ });
2231
+ }
2232
+ function normalizeOptionValue(value, field) {
2233
+ const options = parseFieldOptions(field.options ?? field.setting ?? field.optionsData);
2234
+ if (options.length === 0 || value === undefined || value === null || value === "") {
2235
+ return value;
2236
+ }
2237
+ const valueText = normalizeText(value);
2238
+ const match = options.find((option) => [option.value, option.name, option.label].some((candidate) => normalizeText(candidate) === valueText));
2239
+ if (!match) {
2240
+ const available = options.map((option) => option.name ?? option.label ?? option.value).filter((option) => option !== undefined);
2241
+ throw new Error(`字段“${field.name ?? field.fieldName}”的可选值不包含“${value}”。可选值:${available.join("、")}`);
2242
+ }
2243
+ return value;
2244
+ }
2245
+ function parseFieldOptions(input) {
2246
+ if (!input) {
2247
+ return [];
2248
+ }
2249
+ if (Array.isArray(input)) {
2250
+ return input.map((option) => (typeof option === "object" && option ? option : { value: option, name: option }));
2251
+ }
2252
+ if (typeof input === "string") {
2253
+ const text = input.trim();
2254
+ if (!text) {
2255
+ return [];
2256
+ }
2257
+ try {
2258
+ return parseFieldOptions(JSON.parse(text));
2259
+ }
2260
+ catch {
2261
+ return text.split(",").map((option) => option.trim()).filter(Boolean).map((option) => ({ value: option, name: option }));
2262
+ }
2263
+ }
2264
+ if (typeof input === "object") {
2265
+ const object = input;
2266
+ return parseFieldOptions(object.options ?? object.list ?? object.data ?? []);
2267
+ }
2268
+ return [];
2269
+ }
2270
+ async function verifyInformationUpdate(client, moduleDefinition, body) {
2271
+ const endpoint = `${moduleDefinition.basePath}/information/${body.id}`;
2272
+ const data = await client.post(endpoint);
2273
+ const checked = (body.list ?? []).map((item) => {
2274
+ const actualValue = findInformationValue(data, item);
2275
+ return {
2276
+ fieldName: item.fieldName,
2277
+ name: item.name,
2278
+ expectedValue: item.value,
2279
+ actualValue,
2280
+ matched: valuesMatch(item.value, actualValue)
2281
+ };
2282
+ });
2283
+ return {
2284
+ endpoint,
2285
+ ok: checked.every((item) => item.matched),
2286
+ checked
2287
+ };
2288
+ }
2289
+ function findInformationValue(data, field) {
2290
+ const direct = findDirectInformationValue(data, field);
2291
+ if (direct.found) {
2292
+ return direct.value;
2293
+ }
2294
+ const seen = new Set();
2295
+ const wantedFieldName = normalizeText(field.fieldName);
2296
+ const wantedName = normalizeText(field.name);
2297
+ const visit = (value) => {
2298
+ if (!value || typeof value !== "object" || seen.has(value)) {
2299
+ return { found: false };
2300
+ }
2301
+ seen.add(value);
2302
+ if (Array.isArray(value)) {
2303
+ for (const item of value) {
2304
+ const result = visit(item);
2305
+ if (result.found) {
2306
+ return result;
2307
+ }
2308
+ }
2309
+ return { found: false };
2310
+ }
2311
+ const record = value;
2312
+ const recordFieldName = normalizeText(record.fieldName);
2313
+ const recordName = normalizeText(record.name);
2314
+ if ((wantedFieldName && recordFieldName === wantedFieldName) || (wantedName && recordName === wantedName)) {
2315
+ return { found: true, value: extractInformationValue(record) };
2316
+ }
2317
+ for (const child of Object.values(record)) {
2318
+ const result = visit(child);
2319
+ if (result.found) {
2320
+ return result;
2321
+ }
2322
+ }
2323
+ return { found: false };
2324
+ };
2325
+ return visit(data).value;
2326
+ }
2327
+ function findDirectInformationValue(data, field) {
2328
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
2329
+ return { found: false };
2330
+ }
2331
+ const record = data;
2332
+ if (field.fieldName && Object.prototype.hasOwnProperty.call(record, field.fieldName)) {
2333
+ return { found: true, value: record[field.fieldName] };
2334
+ }
2335
+ return { found: false };
2336
+ }
2337
+ function extractInformationValue(record) {
2338
+ if (Object.prototype.hasOwnProperty.call(record, "value")) {
2339
+ return record.value;
2340
+ }
2341
+ if (Object.prototype.hasOwnProperty.call(record, "valueDesc")) {
2342
+ return record.valueDesc;
2343
+ }
2344
+ if (Object.prototype.hasOwnProperty.call(record, "fieldValue")) {
2345
+ return record.fieldValue;
2346
+ }
2347
+ return undefined;
2348
+ }
2349
+ function valuesMatch(expected, actual) {
2350
+ return normalizeComparableValue(expected) === normalizeComparableValue(actual);
2351
+ }
2352
+ function normalizeComparableValue(value) {
2353
+ if (value === undefined || value === null) {
2354
+ return "";
2355
+ }
2356
+ if (typeof value === "object") {
2357
+ return JSON.stringify(value);
2358
+ }
2359
+ return String(value).trim();
2360
+ }
2361
+ function normalizeText(value) {
2362
+ return value === undefined || value === null ? "" : String(value).trim().toLowerCase();
2363
+ }
2364
+ async function listCustomerPools(client, args) {
2365
+ const pools = await client.post("/crmCustomerPool/queryPoolNameListByAuth");
2366
+ const poolList = Array.isArray(pools) ? pools : [];
2367
+ const includeSamples = Boolean(args.includeSamples);
2368
+ const limit = includeSamples ? positiveInteger(args.sampleLimit ?? args.limit, 3) : 1;
2369
+ const searchList = args.filters ?? args.searchList ?? [];
2370
+ const poolSummaries = await Promise.all(poolList.map(async (pool) => {
2371
+ const poolId = pool.poolId ?? pool.id;
2372
+ if (!poolId) {
2373
+ throw new Error("客户公海授权列表缺少 poolId,无法统计池内客户数量。");
2374
+ }
2375
+ const page = await client.post("/crmCustomerPool/queryPageList", cleanObject({
2376
+ page: 1,
2377
+ limit,
2378
+ pageType: 1,
2379
+ label: 9,
2380
+ poolId,
2381
+ searchList
2382
+ }));
2383
+ const summary = {
2384
+ poolId,
2385
+ poolName: pool.poolName ?? pool.name,
2386
+ customerCount: page.totalRow ?? page.total ?? 0
2387
+ };
2388
+ if (includeSamples) {
2389
+ const list = Array.isArray(page.list) ? page.list : [];
2390
+ summary.samples = list.map((record) => ({
2391
+ customerId: record.customerId ?? record.id,
2392
+ customerName: record.customerName ?? record.name,
2393
+ ownerUserName: record.ownerUserName,
2394
+ createTime: record.createTime
2395
+ }));
2396
+ }
2397
+ return summary;
2398
+ }));
2399
+ return {
2400
+ module: "customerPool",
2401
+ readonly: true,
2402
+ poolCount: poolSummaries.length,
2403
+ pools: poolSummaries
2404
+ };
2405
+ }
2406
+ function positiveInteger(value, fallback) {
2407
+ const numberValue = Number(value);
2408
+ if (!Number.isInteger(numberValue) || numberValue < 1) {
2409
+ return fallback;
2410
+ }
2411
+ return numberValue;
2412
+ }
2413
+ function isPoolModule(moduleDefinition) {
2414
+ return moduleDefinition.key === "customerPool" || moduleDefinition.key === "leadsPool";
2415
+ }
2416
+ function getPoolRecordModule(moduleDefinition) {
2417
+ return getModuleByKey(moduleDefinition.key === "customerPool" ? "customer" : "leads");
2418
+ }
2419
+ async function resolvePoolContext(client, moduleDefinition, args) {
2420
+ if (!isPoolModule(moduleDefinition)) {
2421
+ throw new Error(`Module ${moduleDefinition.key} is not a pool module.`);
2422
+ }
2423
+ if (args.poolId) {
2424
+ return { poolId: args.poolId, pool: cleanObject({ poolId: args.poolId, poolName: args.poolName }) };
2425
+ }
2426
+ const pools = await client.post(`${moduleDefinition.basePath}/queryPoolNameListByAuth`);
2427
+ const poolList = Array.isArray(pools) ? pools : [];
2428
+ const selectedPool = args.poolName
2429
+ ? poolList.find((pool) => String(pool.poolName ?? pool.name ?? "").trim() === String(args.poolName).trim())
2430
+ : poolList[0];
2431
+ if (!selectedPool) {
2432
+ throw new Error(`当前账号没有可查询的${moduleDefinition.name},请确认公海/线索池权限或传入 poolId。`);
2433
+ }
2434
+ const poolId = selectedPool.poolId ?? selectedPool.id;
2435
+ if (!poolId) {
2436
+ throw new Error(`${moduleDefinition.name}授权列表缺少 poolId,无法查询池内数据。`);
2437
+ }
2438
+ return { poolId, pool: selectedPool };
2439
+ }
2440
+ async function callRelatedRecords(client, moduleDefinition, args) {
2441
+ const relation = String(args.relation ?? args.type ?? "").trim();
2442
+ const pageBody = cleanObject({ page: args.page ?? 1, limit: args.limit ?? 15, pageType: 1, id: args.id, ...safeExtra(args.extra) });
2443
+ if (moduleDefinition.key === "business" && relation === "product") {
2444
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmBusiness/queryProduct", pageBody) };
2445
+ }
2446
+ if (moduleDefinition.key === "business" && relation === "contacts") {
2447
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmBusiness/queryContacts", pageBody) };
2448
+ }
2449
+ if (moduleDefinition.key === "contacts" && relation === "business") {
2450
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmContacts/queryBusiness", pageBody) };
2451
+ }
2452
+ if (moduleDefinition.key === "contract" && relation === "product") {
2453
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmContract/queryProductListByContractId", pageBody) };
2454
+ }
2455
+ if (moduleDefinition.key === "contract" && relation === "receivablesPlan") {
2456
+ return {
2457
+ module: moduleDefinition.key,
2458
+ relation,
2459
+ data: await client.post("/crmContract/queryReceivablesPlansByContractId", undefined, {
2460
+ contractId: args.id,
2461
+ receivablesId: args.receivablesId,
2462
+ productId: args.productId
2463
+ })
2464
+ };
339
2465
  }
340
- return [];
2466
+ if (moduleDefinition.key === "receivables" && relation === "product") {
2467
+ return { module: moduleDefinition.key, relation, data: await client.post(`/crmReceivables/queryProductList/${args.id}`) };
2468
+ }
2469
+ if (moduleDefinition.key === "quotation" && relation === "product") {
2470
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmQuotation/queryProductList", pageBody) };
2471
+ }
2472
+ if (moduleDefinition.key === "product" && relation === "saleProduct") {
2473
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmProduct/querySaleProductPageList", pageBody) };
2474
+ }
2475
+ if (moduleDefinition.key === "receivablesPlan" && relation === "contractCustomer") {
2476
+ return { module: moduleDefinition.key, relation, data: await client.post("/crmReceivablesPlan/queryByContractAndCustomer", pageBody) };
2477
+ }
2478
+ throw new Error(`当前 MCP 未配置“${moduleDefinition.name} / ${relation}”关联查询白名单。`);
341
2479
  }
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
- }));
2480
+ function buildHrmEmployeeListBody(args) {
2481
+ return cleanObject({
2482
+ page: args.page ?? 1,
2483
+ limit: args.limit ?? 15,
2484
+ pageType: args.pageType ?? 1,
2485
+ employeeName: firstTextValue(args.employeeName, args.name, args.keyword, args.query, args.q, args.search),
2486
+ mobile: args.mobile ?? args.phone,
2487
+ sex: args.sex,
2488
+ entryTime: args.entryTime,
2489
+ jobNumber: args.jobNumber,
2490
+ deptId: args.deptId,
2491
+ post: args.post,
2492
+ becomeTime: args.becomeTime,
2493
+ workAddress: args.workAddress,
2494
+ employmentForms: args.employmentForms,
2495
+ status: args.status,
2496
+ employeeSurvey: args.employeeSurvey,
2497
+ toDoRemind: args.toDoRemind,
2498
+ employeeIds: args.employeeIds,
2499
+ sortField: args.sortField,
2500
+ order: args.order
2501
+ });
351
2502
  }
352
- function firstTextValue(...values) {
353
- for (const value of values) {
354
- if (value === undefined || value === null) {
355
- continue;
2503
+ function buildHrmDepartmentListBody(args) {
2504
+ return cleanObject({
2505
+ name: firstTextValue(args.name, args.deptName, args.keyword, args.query, args.q, args.search),
2506
+ type: args.type ?? "tree",
2507
+ id: args.id ?? args.parentId
2508
+ });
2509
+ }
2510
+ function buildHrmDepartmentEmployeesBody(args) {
2511
+ return cleanObject({
2512
+ page: args.page ?? 1,
2513
+ limit: args.limit ?? 15,
2514
+ pageType: args.pageType ?? 1,
2515
+ deptId: requireHrmDeptId(args),
2516
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.name, args.employeeName)
2517
+ });
2518
+ }
2519
+ function buildHrmAttendanceMonthRecordBody(args) {
2520
+ return cleanObject({
2521
+ page: args.page ?? 1,
2522
+ limit: args.limit ?? 15,
2523
+ pageType: args.pageType ?? 1,
2524
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.employeeName, args.name),
2525
+ deptIds: args.deptIds ?? args.deptId,
2526
+ times: args.times ?? args.timeRange,
2527
+ sortField: args.sortField,
2528
+ order: args.order,
2529
+ isFullAttendance: args.isFullAttendance,
2530
+ employeeId: args.employeeId ?? args.id,
2531
+ isResignation: args.isResignation
2532
+ });
2533
+ }
2534
+ function buildHrmAttendanceDailyDetailBody(args) {
2535
+ return cleanObject({
2536
+ employeeId: requireHrmEmployeeId(args),
2537
+ currentDate: args.currentDate ?? args.date ?? args.day,
2538
+ hrmAttendanceGroup: args.hrmAttendanceGroup,
2539
+ multi: args.multi,
2540
+ companyId: args.companyId
2541
+ });
2542
+ }
2543
+ function buildHrmLeaveRecordBody(args) {
2544
+ return cleanObject({
2545
+ page: args.page ?? 1,
2546
+ limit: args.limit ?? 15,
2547
+ pageType: args.pageType ?? 1,
2548
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.employeeName, args.name),
2549
+ employeeId: args.employeeId ?? args.id,
2550
+ times: args.times ?? args.timeRange,
2551
+ deptIds: args.deptIds ?? args.deptId,
2552
+ leaveTypes: args.leaveTypes ?? args.leaveType,
2553
+ sortField: args.sortField,
2554
+ order: args.order
2555
+ });
2556
+ }
2557
+ function requireHrmEmployeeId(args) {
2558
+ const employeeId = args.employeeId ?? args.id;
2559
+ if (isBlank(employeeId)) {
2560
+ throw new Error("请提供 employeeId 或 id。");
2561
+ }
2562
+ return employeeId;
2563
+ }
2564
+ function requireHrmDeptId(args) {
2565
+ const deptId = args.deptId ?? args.id;
2566
+ if (isBlank(deptId)) {
2567
+ throw new Error("请提供 deptId 或 id。");
2568
+ }
2569
+ return deptId;
2570
+ }
2571
+ function getFinanceEndpointByKey(input, definitions, label) {
2572
+ const value = String(input ?? "").trim().toLowerCase();
2573
+ const definition = definitions.find((item) => item.aliases.some((alias) => alias.toLowerCase() === value));
2574
+ if (!definition) {
2575
+ throw new Error(`当前 MCP 仅支持这些${label}只读查询:${definitions.map((item) => item.key).join("、")}。`);
2576
+ }
2577
+ return definition;
2578
+ }
2579
+ function buildFinanceSearchBody(args, defaults = {}, omitKeys = []) {
2580
+ const body = buildFinanceBody(args, ["keyword", "query", "q", ...omitKeys], defaults);
2581
+ const search = firstTextValue(args.search, args.keyword, args.query, args.q);
2582
+ if (search) {
2583
+ body.search = search;
2584
+ }
2585
+ return cleanObject(body);
2586
+ }
2587
+ function buildFinanceBody(args, omitKeys = [], defaults = {}) {
2588
+ const omit = new Set(omitKeys);
2589
+ const body = { ...defaults };
2590
+ for (const [key, value] of Object.entries(args ?? {})) {
2591
+ if (!omit.has(key) && value !== undefined && value !== null) {
2592
+ body[key] = value;
2593
+ }
2594
+ }
2595
+ return cleanObject(body);
2596
+ }
2597
+ function requireFinanceCertificateId(args) {
2598
+ const certificateId = args.certificateId ?? args.id;
2599
+ if (isBlank(certificateId)) {
2600
+ throw new Error("查询财务凭证详情请提供 certificateId 或 id。");
2601
+ }
2602
+ return certificateId;
2603
+ }
2604
+ function getJxcModuleByKey(input) {
2605
+ const value = String(input ?? "").trim();
2606
+ const moduleDefinition = JXC_MODULES.find((module) => module.aliases.some((alias) => String(alias) === value));
2607
+ if (!moduleDefinition) {
2608
+ throw new Error(`当前 MCP 仅内置这些 JXC 主数据模块:${JXC_MODULES.map((module) => module.key).join("、")}。其他 JXC 接口请使用 crm_write_jxc 指定固定路径。`);
2609
+ }
2610
+ return moduleDefinition;
2611
+ }
2612
+ function buildJxcSearchBody(args, moduleDefinition) {
2613
+ return cleanObject({
2614
+ page: args.page ?? 1,
2615
+ limit: args.limit ?? 15,
2616
+ pageType: args.pageType ?? 1,
2617
+ label: moduleDefinition.label,
2618
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.name),
2619
+ sceneId: args.sceneId,
2620
+ sortField: args.sortField,
2621
+ order: args.order,
2622
+ searchList: args.searchList ?? args.filters,
2623
+ ids: args.ids,
2624
+ sortIds: args.sortIds,
2625
+ mainFieldValues: args.mainFieldValues,
2626
+ extraMap: args.extraMap,
2627
+ validFieldAuth: args.validFieldAuth,
2628
+ warehouseId: moduleDefinition.key === "product" ? args.warehouseId : undefined,
2629
+ isRelateProductSearch: moduleDefinition.key === "product" ? args.isRelateProductSearch : undefined
2630
+ });
2631
+ }
2632
+ function buildJxcStockProductsBody(args) {
2633
+ return cleanObject({
2634
+ page: args.page ?? 1,
2635
+ limit: args.limit ?? 15,
2636
+ pageType: args.pageType ?? 1,
2637
+ label: 34,
2638
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.productName, args.name),
2639
+ searchList: args.searchList ?? args.filters,
2640
+ productName: args.productName,
2641
+ productTypeId: args.productTypeId,
2642
+ productCode: args.productCode,
2643
+ warehouseId: args.warehouseId,
2644
+ isWarehouseGroup: args.isWarehouseGroup,
2645
+ stock: args.stock,
2646
+ sortField: args.sortField,
2647
+ order: args.order
2648
+ });
2649
+ }
2650
+ function buildJxcStockMovementsBody(args) {
2651
+ return cleanObject({
2652
+ page: args.page ?? 1,
2653
+ limit: args.limit ?? 15,
2654
+ pageType: args.pageType ?? 1,
2655
+ label: 35,
2656
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.productName, args.name),
2657
+ sceneId: args.sceneId,
2658
+ sortField: args.sortField,
2659
+ order: args.order,
2660
+ searchList: args.searchList ?? args.filters,
2661
+ ids: args.ids,
2662
+ sortIds: args.sortIds,
2663
+ mainFieldValues: args.mainFieldValues,
2664
+ extraMap: args.extraMap
2665
+ });
2666
+ }
2667
+ function requireJxcId(args, message) {
2668
+ const id = args.id ?? args.recordId ?? args[args.module ? `${String(args.module)}Id` : "id"];
2669
+ if (isBlank(id)) {
2670
+ throw new Error(message);
2671
+ }
2672
+ return id;
2673
+ }
2674
+ function requireJxcProductId(args) {
2675
+ const productId = args.productId ?? args.id;
2676
+ if (isBlank(productId)) {
2677
+ throw new Error("查询 JXC 产品库存请提供 productId 或 id。");
2678
+ }
2679
+ return productId;
2680
+ }
2681
+ function requireWarehouseProductIdsMap(args) {
2682
+ const value = args.warehouseProductIdsMap ?? args.map ?? args.data;
2683
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2684
+ throw new Error("查询仓库产品库存数量请提供 warehouseProductIdsMap,例如 { \"8\": [100] }。");
2685
+ }
2686
+ return value;
2687
+ }
2688
+ function requireWarehouseIds(args) {
2689
+ const value = args.warehouseIds ?? args.ids;
2690
+ if (!Array.isArray(value) || value.length === 0) {
2691
+ throw new Error("查询仓库名称请提供 warehouseIds 数组。");
2692
+ }
2693
+ return value;
2694
+ }
2695
+ async function executeDomainWrite(client, domain, args) {
2696
+ const targetPath = normalizeDomainTargetPath(domain, args);
2697
+ const body = explicitWriteBody(args);
2698
+ const query = cleanQuery(args.query ?? {});
2699
+ if (!args.confirm) {
2700
+ return previewDomainWrite(domain, targetPath, body, query);
2701
+ }
2702
+ return cleanObject({
2703
+ domain: domain.key,
2704
+ moduleName: domain.name,
2705
+ targetPath,
2706
+ body,
2707
+ query: Object.keys(query).length > 0 ? query : undefined,
2708
+ data: await client.post(targetPath, body, query)
2709
+ });
2710
+ }
2711
+ function previewDomainWrite(domain, targetPath, body, query) {
2712
+ return cleanObject({
2713
+ preview: true,
2714
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
2715
+ domain: domain.key,
2716
+ moduleName: domain.name,
2717
+ targetPath,
2718
+ body,
2719
+ query: query && Object.keys(query).length > 0 ? query : undefined
2720
+ });
2721
+ }
2722
+ function normalizeDomainTargetPath(domain, args) {
2723
+ const explicitPath = args.targetPath ?? args.path ?? args.endpoint;
2724
+ const targetPath = explicitPath
2725
+ ? normalizeEndpointPath(explicitPath)
2726
+ : buildDomainTargetPath(args.module ?? args.controller ?? args.basePath, args.operation ?? args.action);
2727
+ if (!domain.allowedPrefixes.some((prefix) => targetPath.startsWith(prefix))) {
2728
+ throw new Error(`${domain.name} 写入工具只能调用 ${domain.allowedPrefixes.join("、")} 开头的 72CRM 固定接口。`);
2729
+ }
2730
+ return targetPath;
2731
+ }
2732
+ function buildDomainTargetPath(moduleName, operation) {
2733
+ const moduleSegment = normalizePathSegment(moduleName, "module");
2734
+ const operationSegment = normalizePathSegment(operation, "operation");
2735
+ return normalizeEndpointPath(`/${moduleSegment}/${operationSegment}`);
2736
+ }
2737
+ function normalizeEndpointPath(input) {
2738
+ const raw = String(input ?? "").trim();
2739
+ if (!raw) {
2740
+ throw new Error("请提供 targetPath,或提供 module + operation 组合。");
2741
+ }
2742
+ if (raw.includes("://") || raw.startsWith("//") || raw.includes("..") || raw.includes("?") || raw.includes("#")) {
2743
+ throw new Error("targetPath 只能是 72CRM 固定接口路径,不允许 URL、上级目录或查询串。");
2744
+ }
2745
+ return raw.startsWith("/") ? raw : `/${raw}`;
2746
+ }
2747
+ function normalizePathSegment(input, name) {
2748
+ const value = String(input ?? "").trim().replace(/^\/+|\/+$/g, "");
2749
+ if (!value || value.includes("://") || value.includes("..") || value.includes("?") || value.includes("#")) {
2750
+ throw new Error(`请提供合法的 ${name}。`);
2751
+ }
2752
+ return value;
2753
+ }
2754
+ const DOMAIN_WRITE_CONTROL_KEYS = new Set([
2755
+ "targetPath",
2756
+ "path",
2757
+ "endpoint",
2758
+ "module",
2759
+ "controller",
2760
+ "basePath",
2761
+ "operation",
2762
+ "action",
2763
+ "body",
2764
+ "data",
2765
+ "query",
2766
+ "confirm"
2767
+ ]);
2768
+ function explicitWriteBody(args) {
2769
+ if (Object.prototype.hasOwnProperty.call(args, "body")) {
2770
+ return args.body;
2771
+ }
2772
+ if (Object.prototype.hasOwnProperty.call(args, "data")) {
2773
+ return args.data;
2774
+ }
2775
+ const body = {};
2776
+ for (const [key, value] of Object.entries(args ?? {})) {
2777
+ if (!DOMAIN_WRITE_CONTROL_KEYS.has(key) && value !== undefined && value !== null) {
2778
+ body[key] = value;
2779
+ }
2780
+ }
2781
+ return Object.keys(body).length > 0 ? body : undefined;
2782
+ }
2783
+ function buildJxcRecordSaveBody(args, moduleDefinition, requireId) {
2784
+ const explicitPayload = Object.prototype.hasOwnProperty.call(args, "body")
2785
+ ? args.body
2786
+ : Object.prototype.hasOwnProperty.call(args, "data")
2787
+ ? args.data
2788
+ : undefined;
2789
+ const body = explicitPayload && typeof explicitPayload === "object" && !Array.isArray(explicitPayload)
2790
+ ? { ...explicitPayload }
2791
+ : buildModelSaveBody(args);
2792
+ if (!body.entity || typeof body.entity !== "object" || Array.isArray(body.entity)) {
2793
+ body.entity = {};
2794
+ }
2795
+ else {
2796
+ body.entity = { ...body.entity };
2797
+ }
2798
+ if (requireId) {
2799
+ const id = args.id ?? args.recordId ?? args[moduleDefinition.primaryKey] ?? body.entity[moduleDefinition.primaryKey];
2800
+ if (isBlank(id)) {
2801
+ throw new Error(`修改 JXC ${moduleDefinition.name}请提供 id 或 ${moduleDefinition.primaryKey}。`);
2802
+ }
2803
+ if (isBlank(body.entity[moduleDefinition.primaryKey])) {
2804
+ body.entity[moduleDefinition.primaryKey] = id;
2805
+ }
2806
+ }
2807
+ return cleanObject(body);
2808
+ }
2809
+ function buildJxcUpdateInformationBody(args, moduleDefinition) {
2810
+ const id = args.id ?? args.recordId ?? args[moduleDefinition.primaryKey];
2811
+ if (isBlank(id)) {
2812
+ throw new Error(`修改 JXC ${moduleDefinition.name}字段请提供 id 或 ${moduleDefinition.primaryKey}。`);
2813
+ }
2814
+ return cleanObject({
2815
+ id,
2816
+ label: moduleDefinition.label,
2817
+ list: rawUpdateList(args),
2818
+ batchId: args.batchId
2819
+ });
2820
+ }
2821
+ function buildFinanceVoucherWriteBody(args, requireId) {
2822
+ const explicitBody = explicitWriteBody(args);
2823
+ const body = explicitBody && typeof explicitBody === "object" && !Array.isArray(explicitBody)
2824
+ ? { ...explicitBody }
2825
+ : {};
2826
+ const certificateId = args.certificateId ?? args.id ?? body.certificateId;
2827
+ if (requireId && isBlank(certificateId)) {
2828
+ throw new Error("修改财务凭证请提供 certificateId 或 id。");
2829
+ }
2830
+ if (requireId && isBlank(body.certificateId)) {
2831
+ body.certificateId = certificateId;
2832
+ }
2833
+ return cleanObject(body);
2834
+ }
2835
+ async function buildCrmProductSearchBody(client, args, saleOnly) {
2836
+ const moduleDefinition = getModuleByKey("product");
2837
+ let searchList = await buildResolvedSearchList(client, args, moduleDefinition);
2838
+ const categoryId = args.categoryId ?? args.productCategoryId;
2839
+ if (!isBlank(categoryId) && !searchList.some((item) => item.fieldName === "categoryId")) {
2840
+ searchList = combineSearchLists(searchList, [{
2841
+ type: 1,
2842
+ values: [categoryId],
2843
+ formType: "category",
2844
+ fieldName: "categoryId"
2845
+ }]);
2846
+ }
2847
+ return cleanObject({
2848
+ page: args.page ?? 1,
2849
+ limit: args.limit ?? 15,
2850
+ pageType: saleOnly ? 0 : args.pageType ?? 1,
2851
+ label: moduleDefinition.label,
2852
+ status: saleOnly ? undefined : args.status,
2853
+ sortIds: args.sortIds,
2854
+ searchList: searchList.length > 0 ? searchList : undefined
2855
+ });
2856
+ }
2857
+ async function executeCrmProductSave(client, args, requireId) {
2858
+ const targetPath = requireId ? "/crmProduct/update" : "/crmProduct/add";
2859
+ const body = buildCrmProductSaveBody(args, requireId);
2860
+ if (!args.confirm) {
2861
+ return {
2862
+ preview: true,
2863
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
2864
+ module: "product",
2865
+ moduleName: "产品",
2866
+ operation: requireId ? "update" : "add",
2867
+ targetPath,
2868
+ body
2869
+ };
2870
+ }
2871
+ return {
2872
+ module: "product",
2873
+ moduleName: "产品",
2874
+ targetPath,
2875
+ body,
2876
+ data: await client.post(targetPath, body)
2877
+ };
2878
+ }
2879
+ function buildCrmProductSaveBody(args, requireId) {
2880
+ const explicitPayload = hasExplicitBody(args) ? objectWriteBody(args) : buildModelSaveBody(args);
2881
+ const body = explicitPayload && typeof explicitPayload === "object" && !Array.isArray(explicitPayload)
2882
+ ? { ...explicitPayload }
2883
+ : {};
2884
+ if (!isPlainObject(body.entity)) {
2885
+ body.entity = {};
2886
+ }
2887
+ else {
2888
+ body.entity = { ...body.entity };
2889
+ }
2890
+ const productId = args.productId ?? args.id ?? body.entity.productId;
2891
+ if (requireId && isBlank(productId)) {
2892
+ throw new Error("编辑 CRM 产品请提供 productId 或 id。");
2893
+ }
2894
+ if (!isBlank(productId) && isBlank(body.entity.productId)) {
2895
+ body.entity.productId = productId;
2896
+ }
2897
+ if (!Array.isArray(body.field)) {
2898
+ body.field = [];
2899
+ }
2900
+ return cleanObject(body);
2901
+ }
2902
+ async function executeCrmProductSimpleWrite(client, args, operation, targetPath, body) {
2903
+ if (!args.confirm) {
2904
+ return {
2905
+ preview: true,
2906
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
2907
+ module: "product",
2908
+ moduleName: "产品",
2909
+ operation,
2910
+ targetPath,
2911
+ body
2912
+ };
2913
+ }
2914
+ return {
2915
+ module: "product",
2916
+ moduleName: "产品",
2917
+ targetPath,
2918
+ body,
2919
+ data: await client.post(targetPath, body)
2920
+ };
2921
+ }
2922
+ function buildCrmProductStatusBody(args) {
2923
+ if (hasExplicitBody(args)) {
2924
+ return objectWriteBody(args);
2925
+ }
2926
+ if (isBlank(args.status)) {
2927
+ throw new Error("修改 CRM 产品上下架状态请提供 status。");
2928
+ }
2929
+ return {
2930
+ ids: requireIdArray(args.ids ?? args.productIds, "修改 CRM 产品状态请提供 ids 或 productIds 数组。"),
2931
+ status: args.status
2932
+ };
2933
+ }
2934
+ function buildCrmProductTransferBody(args) {
2935
+ if (hasExplicitBody(args)) {
2936
+ return objectWriteBody(args);
2937
+ }
2938
+ const ownerUserId = args.ownerUserId ?? args.userId;
2939
+ if (isBlank(ownerUserId)) {
2940
+ throw new Error("转移 CRM 产品负责人请提供 ownerUserId。");
2941
+ }
2942
+ return cleanObject({
2943
+ ids: requireIdArray(args.ids ?? args.productIds, "转移 CRM 产品负责人请提供 ids 或 productIds 数组。"),
2944
+ ownerUserId,
2945
+ transferType: args.transferType,
2946
+ power: args.power,
2947
+ changeType: args.changeType,
2948
+ expiresTime: args.expiresTime
2949
+ });
2950
+ }
2951
+ function requireCrmProductId(args, message) {
2952
+ const productId = args.productId ?? args.id;
2953
+ if (isBlank(productId)) {
2954
+ throw new Error(message);
2955
+ }
2956
+ return productId;
2957
+ }
2958
+ function requireIdArray(value, message) {
2959
+ const ids = Array.isArray(value) ? value : isBlank(value) ? [] : [value];
2960
+ if (ids.length === 0) {
2961
+ throw new Error(message);
2962
+ }
2963
+ return ids;
2964
+ }
2965
+ function buildOaAnnouncementListQuery(args) {
2966
+ return buildPassthroughBody(args, ["keyword", "query", "q", "search"], {
2967
+ page: args.page ?? 1,
2968
+ limit: args.limit ?? 15,
2969
+ type: args.type
2970
+ });
2971
+ }
2972
+ async function executeOaAnnouncementWrite(client, args, requireId) {
2973
+ const targetPath = requireId ? "/oaAnnouncement/setAnnouncement" : "/oaAnnouncement/addAnnouncement";
2974
+ const body = buildOaAnnouncementBody(args, requireId);
2975
+ if (!args.confirm) {
2976
+ return previewOaWrite(requireId ? "update" : "create", "oaAnnouncement", "公告", targetPath, body);
2977
+ }
2978
+ return {
2979
+ module: "oaAnnouncement",
2980
+ moduleName: "公告",
2981
+ targetPath,
2982
+ body,
2983
+ data: await client.post(targetPath, body)
2984
+ };
2985
+ }
2986
+ function buildOaAnnouncementBody(args, requireId) {
2987
+ const body = hasExplicitBody(args) ? objectWriteBody(args) : buildPassthroughBody(args, ["id"]);
2988
+ const announcementId = args.announcementId ?? args.id ?? body.announcementId;
2989
+ if (requireId && isBlank(announcementId)) {
2990
+ throw new Error("编辑 OA 公告请提供 announcementId 或 id。");
2991
+ }
2992
+ if (!isBlank(announcementId) && isBlank(body.announcementId)) {
2993
+ body.announcementId = announcementId;
2994
+ }
2995
+ for (const key of ["deptIds", "ownerUserIds", "readUserIds"]) {
2996
+ if (body[key] !== undefined) {
2997
+ body[key] = toCommaSeparated(body[key]);
2998
+ }
2999
+ }
3000
+ return cleanObject(body);
3001
+ }
3002
+ function buildOaAnnouncementReadBody(args) {
3003
+ const body = hasExplicitBody(args) ? objectWriteBody(args) : {};
3004
+ const announcementId = args.announcementId ?? args.id ?? body.announcementId;
3005
+ if (isBlank(announcementId)) {
3006
+ throw new Error("标记 OA 公告已读请提供 announcementId 或 id。");
3007
+ }
3008
+ if (isBlank(body.announcementId)) {
3009
+ body.announcementId = announcementId;
3010
+ }
3011
+ return cleanObject(body);
3012
+ }
3013
+ function requireOaAnnouncementId(args, message) {
3014
+ const announcementId = args.announcementId ?? args.id;
3015
+ if (isBlank(announcementId)) {
3016
+ throw new Error(message);
3017
+ }
3018
+ return announcementId;
3019
+ }
3020
+ function buildOaLogListBody(args) {
3021
+ const body = buildPassthroughBody(args, ["keyword", "query", "q"], {
3022
+ page: args.page ?? 1,
3023
+ limit: args.limit ?? 15
3024
+ });
3025
+ const search = firstTextValue(args.search, args.keyword, args.query, args.q, args.title, args.content);
3026
+ if (search && body.search === undefined) {
3027
+ body.search = search;
3028
+ }
3029
+ return cleanObject(body);
3030
+ }
3031
+ function buildOaLogTemplateListBody(args) {
3032
+ const body = buildPassthroughBody(args, ["keyword", "query", "q"], {
3033
+ page: args.page ?? 1,
3034
+ limit: args.limit ?? 15
3035
+ });
3036
+ const search = firstTextValue(args.search, args.keyword, args.query, args.q, args.name);
3037
+ if (search && body.search === undefined) {
3038
+ body.search = search;
3039
+ }
3040
+ return cleanObject(body);
3041
+ }
3042
+ function buildOaLogFieldQuery(args) {
3043
+ return cleanQuery({
3044
+ configId: requireOaLogConfigId(args),
3045
+ type: args.type
3046
+ });
3047
+ }
3048
+ async function executeOaLogWrite(client, args, requireId) {
3049
+ const targetPath = "/oaLog/addOrUpdate";
3050
+ const body = await buildOaLogWriteBody(client, args, requireId);
3051
+ if (!args.confirm) {
3052
+ return previewOaWrite(requireId ? "update" : "create", "oaLog", "日志", targetPath, body);
3053
+ }
3054
+ return {
3055
+ module: "oaLog",
3056
+ moduleName: "日志",
3057
+ targetPath,
3058
+ body,
3059
+ data: await client.post(targetPath, body)
3060
+ };
3061
+ }
3062
+ async function buildOaLogWriteBody(client, args, requireId) {
3063
+ if (hasExplicitBody(args)) {
3064
+ return finalizeOaLogWriteBody(objectWriteBody(args), args, requireId);
3065
+ }
3066
+ const body = buildOaLogBaseBody(args);
3067
+ const fieldValuesInput = args.fieldValues ?? args.updates ?? args.fields ?? args.fieldForm;
3068
+ const shouldBuildFields = !Array.isArray(args.field)
3069
+ && !Array.isArray(args.fields)
3070
+ && !isBlank(args.categoryId)
3071
+ && fieldValuesInput
3072
+ && typeof fieldValuesInput === "object";
3073
+ if (shouldBuildFields) {
3074
+ const logId = args.logId ?? args.id;
3075
+ const targetPath = logId ? `/oaLogTemplateField/field/${logId}` : "/oaLogTemplateField/field";
3076
+ const fieldMetadata = await client.post(targetPath, undefined, buildOaLogFieldQuery(args));
3077
+ const fields = flattenOaExamineFields(fieldMetadata);
3078
+ const fieldValues = normalizeFieldValues(fieldValuesInput);
3079
+ for (const [rawKey, rawValue] of Object.entries(fieldValues)) {
3080
+ const field = findOaExamineField(fields, rawKey);
3081
+ if (!field) {
3082
+ throw new Error(`OA 日志模板 ${args.categoryId} 未找到字段“${rawKey}”,请先用 crm_get_oa_log_fields 查询字段名。`);
3083
+ }
3084
+ appendOaLogFieldValue(body, field, rawValue);
3085
+ }
3086
+ }
3087
+ else if (Array.isArray(args.field)) {
3088
+ body.field = args.field;
3089
+ }
3090
+ else if (Array.isArray(args.fields)) {
3091
+ body.field = args.fields;
3092
+ }
3093
+ return finalizeOaLogWriteBody(body, args, requireId);
3094
+ }
3095
+ function buildOaLogBaseBody(args) {
3096
+ const body = buildPassthroughBody(args, [
3097
+ "id",
3098
+ "fields",
3099
+ "fieldValues",
3100
+ "updates",
3101
+ "fieldForm",
3102
+ "type"
3103
+ ]);
3104
+ for (const key of ["sendUserIds", "sendDeptIds", "readUserIds", "customerIds", "contactsIds", "businessIds", "contractIds", "receivablesIds", "quotationIds"]) {
3105
+ if (body[key] !== undefined) {
3106
+ body[key] = toCommaSeparated(body[key]);
3107
+ }
3108
+ }
3109
+ if (!Array.isArray(body.field)) {
3110
+ body.field = [];
3111
+ }
3112
+ return cleanObject(body);
3113
+ }
3114
+ function appendOaLogFieldValue(body, field, rawValue) {
3115
+ const value = getOaExamineRealValue(field, rawValue);
3116
+ if (Number(field.fieldType) === 1) {
3117
+ body[field.fieldName] = isEmptyValue(value) ? "" : value;
3118
+ return;
3119
+ }
3120
+ if (field.formType !== "desc_text") {
3121
+ body.field.push({
3122
+ fieldName: field.fieldName,
3123
+ fieldType: field.fieldType,
3124
+ name: field.name,
3125
+ type: field.type,
3126
+ fieldId: field.fieldId,
3127
+ value
3128
+ });
3129
+ }
3130
+ }
3131
+ function finalizeOaLogWriteBody(body, args, requireId) {
3132
+ const logId = args.logId ?? args.id ?? body.logId;
3133
+ if (requireId && isBlank(logId)) {
3134
+ throw new Error("编辑 OA 日志请提供 logId 或 id。");
3135
+ }
3136
+ if (!isBlank(logId) && isBlank(body.logId)) {
3137
+ body.logId = logId;
3138
+ }
3139
+ if (!Array.isArray(body.field)) {
3140
+ body.field = [];
3141
+ }
3142
+ return cleanObject(body);
3143
+ }
3144
+ function requireOaLogId(args, message) {
3145
+ const logId = args.logId ?? args.id;
3146
+ if (isBlank(logId)) {
3147
+ throw new Error(message);
3148
+ }
3149
+ return logId;
3150
+ }
3151
+ function requireOaLogConfigId(args) {
3152
+ const configId = args.configId ?? args.categoryId;
3153
+ if (isBlank(configId)) {
3154
+ throw new Error("查询 OA 日志模板字段或完成统计请提供 configId 或 categoryId。");
3155
+ }
3156
+ return configId;
3157
+ }
3158
+ function buildOaLogFavourQuery(args) {
3159
+ return {
3160
+ logId: requireOaLogId(args, "点赞或取消点赞 OA 日志请提供 logId 或 id。"),
3161
+ isFavour: args.isFavour ?? args.favour ?? args.favorite ?? true
3162
+ };
3163
+ }
3164
+ async function executeCrmDocumentWrite(client, moduleKey, args, requireId) {
3165
+ const definition = getCrmDocumentWriteDefinition(moduleKey);
3166
+ const operation = requireId ? "update" : "add";
3167
+ const targetPath = `${definition.basePath}/${operation}`;
3168
+ const body = await buildCrmDocumentWriteBody(client, definition, args, requireId);
3169
+ const approvalNotice = getCrmDocumentApprovalNotice(definition, body);
3170
+ if (!args.confirm) {
3171
+ return previewCrmDocumentWrite(definition, operation, targetPath, body, approvalNotice);
3172
+ }
3173
+ return cleanObject({
3174
+ module: definition.key,
3175
+ moduleName: definition.name,
3176
+ targetPath,
3177
+ body,
3178
+ approvalNotice,
3179
+ data: await client.post(targetPath, body)
3180
+ });
3181
+ }
3182
+ function getCrmDocumentWriteDefinition(moduleKey) {
3183
+ const definition = CRM_DOCUMENT_WRITE_DEFINITIONS.find((item) => item.key === moduleKey);
3184
+ if (!definition) {
3185
+ throw new Error(`当前 MCP 未配置 ${moduleKey} 单据写入。`);
3186
+ }
3187
+ return definition;
3188
+ }
3189
+ async function buildCrmDocumentWriteBody(client, definition, args, requireId) {
3190
+ if (hasExplicitBody(args)) {
3191
+ return finalizeCrmDocumentBody(definition, objectWriteBody(args), args, requireId);
3192
+ }
3193
+ if (hasRawCrmDocumentShape(args)) {
3194
+ return finalizeCrmDocumentBody(definition, buildRawCrmDocumentBody(args), args, requireId);
3195
+ }
3196
+ return buildStructuredCrmDocumentBody(client, definition, args, requireId);
3197
+ }
3198
+ function hasExplicitBody(args) {
3199
+ return Object.prototype.hasOwnProperty.call(args, "body") || Object.prototype.hasOwnProperty.call(args, "data");
3200
+ }
3201
+ function hasRawCrmDocumentShape(args) {
3202
+ return isPlainObject(args.entity)
3203
+ || Array.isArray(args.field)
3204
+ || Array.isArray(args.fields)
3205
+ || Array.isArray(args.product)
3206
+ || isPlainObject(args.product)
3207
+ || Array.isArray(args.contract);
3208
+ }
3209
+ function buildRawCrmDocumentBody(args) {
3210
+ return cleanObject({
3211
+ entity: isPlainObject(args.entity) ? { ...args.entity } : {},
3212
+ field: Array.isArray(args.field) ? args.field : Array.isArray(args.fields) ? args.fields : [],
3213
+ product: args.product,
3214
+ contract: args.contract,
3215
+ examineFlowData: args.examineFlowData,
3216
+ ...safeExtra(args.extra)
3217
+ });
3218
+ }
3219
+ async function buildStructuredCrmDocumentBody(client, definition, args, requireId) {
3220
+ const fieldValues = normalizeFieldValues(args.fieldValues ?? args.updates ?? args.fields ?? args.fieldForm);
3221
+ if (Object.keys(fieldValues).length === 0 && !isPlainObject(args.entity)) {
3222
+ throw new Error(`请提供 ${definition.name} 的 body/data 原始 payload,或提供 entity/field,或提供 fields/updates 让 MCP 自动组装。`);
3223
+ }
3224
+ const recordId = args[definition.primaryKey] ?? args.id ?? args.recordId;
3225
+ const fieldPath = requireId && !isBlank(recordId) ? `${definition.basePath}/field/${recordId}` : `${definition.basePath}/field`;
3226
+ const fieldMetadata = await client.post(fieldPath, undefined, cleanQuery({ type: args.type }));
3227
+ const fields = flattenOaExamineFields(fieldMetadata);
3228
+ if (fields.length === 0 && Object.keys(fieldValues).length > 0) {
3229
+ throw new Error(`未从 ${fieldPath} 读取到 ${definition.name} 字段,无法自动组装单据字段。`);
3230
+ }
3231
+ const params = {
3232
+ entity: isPlainObject(args.entity) ? { ...args.entity } : {},
3233
+ field: []
3234
+ };
3235
+ for (const [rawKey, rawValue] of Object.entries(fieldValues)) {
3236
+ const field = findOaExamineField(fields, rawKey);
3237
+ if (!field) {
3238
+ throw new Error(`${definition.name} 未找到字段“${rawKey}”,请先用 crm_get_module_schema 查询字段名。`);
3239
+ }
3240
+ appendCrmDocumentFieldValue(params, field, rawValue);
3241
+ }
3242
+ if (args.product !== undefined && params.product === undefined) {
3243
+ params.product = args.product;
3244
+ }
3245
+ if (args.contract !== undefined && params.contract === undefined) {
3246
+ params.contract = args.contract;
3247
+ }
3248
+ if (args.examineFlowData !== undefined) {
3249
+ params.examineFlowData = args.examineFlowData;
3250
+ }
3251
+ return finalizeCrmDocumentBody(definition, params, args, requireId);
3252
+ }
3253
+ function appendCrmDocumentFieldValue(params, field, rawValue) {
3254
+ if (field.formType === "product" && !field.isModuleSelect) {
3255
+ appendCrmProductParams(params, rawValue, Array.isArray(field.productFields) ? field.productFields : []);
3256
+ return;
3257
+ }
3258
+ if (field.formType === "map_address") {
3259
+ appendCrmAddressParams(params.entity, rawValue);
3260
+ return;
3261
+ }
3262
+ const value = getOaExamineRealValue(field, rawValue);
3263
+ if (Number(field.fieldType) === 1) {
3264
+ params.entity[field.fieldName] = isEmptyValue(value) ? "" : value;
3265
+ return;
3266
+ }
3267
+ if (field.formType !== "desc_text") {
3268
+ params.field.push({
3269
+ fieldName: field.fieldName,
3270
+ fieldType: field.fieldType,
3271
+ name: field.name,
3272
+ type: field.type,
3273
+ fieldId: field.fieldId,
3274
+ value
3275
+ });
3276
+ }
3277
+ }
3278
+ function appendCrmProductParams(params, rawValue, productFields) {
3279
+ if (!rawValue) {
3280
+ params.product = [];
3281
+ params.entity.totalPrice = "";
3282
+ params.entity.discountRate = "";
3283
+ return;
3284
+ }
3285
+ const productValue = isPlainObject(rawValue) ? rawValue : { product: rawValue };
3286
+ const products = Array.isArray(productValue.product) ? productValue.product : Array.isArray(rawValue) ? rawValue : [];
3287
+ params.product = products.map((item) => {
3288
+ if (!isPlainObject(item)) {
3289
+ return item;
3290
+ }
3291
+ const product = { ...item };
3292
+ for (const [key, value] of Object.entries(product)) {
3293
+ const field = productFields.find((productField) => productField.fieldName === key);
3294
+ if (field && Number(field.fieldType) === 0) {
3295
+ product[key] = getOaExamineRealValue(field, value);
3296
+ }
356
3297
  }
357
- const text = String(value).trim();
358
- if (text) {
359
- return text;
3298
+ if (Array.isArray(product.name) && product.name.length > 0 && isPlainObject(product.name[0])) {
3299
+ product.name = product.name[0].name ?? "";
360
3300
  }
3301
+ product.salesPrice = product.salesPrice === "" || product.salesPrice === undefined ? 0 : product.salesPrice;
3302
+ product.num = product.num === "" || product.num === undefined ? 0 : product.num;
3303
+ product.discount = product.discount === "" || product.discount === undefined ? 0 : product.discount;
3304
+ return product;
3305
+ });
3306
+ params.entity.totalPrice = productValue.totalPrice ?? 0;
3307
+ params.entity.discountRate = productValue.discountRate ?? 0;
3308
+ }
3309
+ function appendCrmAddressParams(entity, rawValue) {
3310
+ if (!isPlainObject(rawValue)) {
3311
+ entity.address = "";
3312
+ entity.detailAddress = "";
3313
+ entity.location = "";
3314
+ entity.lng = "";
3315
+ entity.lat = "";
3316
+ return;
361
3317
  }
362
- return undefined;
3318
+ const value = rawValue;
3319
+ entity.address = Array.isArray(value.address) ? value.address.join(",") : value.address ?? "";
3320
+ entity.detailAddress = value.detailAddress;
3321
+ entity.location = value.location;
3322
+ entity.lng = value.lng;
3323
+ entity.lat = value.lat;
363
3324
  }
364
- function toArray(value) {
365
- if (Array.isArray(value)) {
366
- return value;
3325
+ function finalizeCrmDocumentBody(definition, body, args, requireId) {
3326
+ if (!isPlainObject(body.entity)) {
3327
+ body.entity = {};
367
3328
  }
368
- if (value && typeof value === "object") {
369
- return [value];
3329
+ else {
3330
+ body.entity = { ...body.entity };
370
3331
  }
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。`);
3332
+ const recordId = args[definition.primaryKey] ?? args.id ?? args.recordId ?? body.entity[definition.primaryKey];
3333
+ if (requireId && isBlank(recordId)) {
3334
+ throw new Error(`编辑${definition.name}请提供 ${definition.primaryKey} 或 id。`);
377
3335
  }
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。`);
3336
+ if (!isBlank(recordId) && isBlank(body.entity[definition.primaryKey])) {
3337
+ body.entity[definition.primaryKey] = recordId;
388
3338
  }
389
- return list[0];
3339
+ if (requireId && !isBlank(args.batchId) && isBlank(body.entity.batchId)) {
3340
+ body.entity.batchId = args.batchId;
3341
+ }
3342
+ if ((args.draft || args.isDraft) && isBlank(body.entity.checkStatus)) {
3343
+ body.entity.checkStatus = 5;
3344
+ }
3345
+ if (!Array.isArray(body.field)) {
3346
+ body.field = [];
3347
+ }
3348
+ return cleanObject(body);
390
3349
  }
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
- };
3350
+ function getCrmDocumentApprovalNotice(definition, body) {
3351
+ if (!definition.requiresApprovalFlow || body.examineFlowData || body.entity?.checkStatus === 5) {
3352
+ return undefined;
3353
+ }
3354
+ return `${definition.name}通常会走审批流;当前请求未包含 examineFlowData。如线上审批配置要求审批,请先补充审批流数据,或传 draft=true 保存草稿。`;
401
3355
  }
402
- function buildUpdateInformationPreviewBody(moduleDefinition, args) {
3356
+ function previewCrmDocumentWrite(definition, operation, targetPath, body, approvalNotice) {
403
3357
  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
3358
+ preview: true,
3359
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
3360
+ module: definition.key,
3361
+ moduleName: definition.name,
3362
+ operation,
3363
+ targetPath,
3364
+ approvalNotice,
3365
+ body
410
3366
  });
411
3367
  }
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;
3368
+ function buildAddressBookListBody(args) {
3369
+ const body = buildPassthroughBody(args, ["keyword", "query", "q", "name"], {
3370
+ page: args.page ?? 1,
3371
+ limit: args.limit ?? 15
3372
+ });
3373
+ const search = firstTextValue(args.search, args.keyword, args.query, args.q, args.realname, args.name);
3374
+ if (search && body.search === undefined && body.realname === undefined) {
3375
+ body.search = search;
417
3376
  }
418
- const updates = args.updates;
419
- if (!updates || typeof updates !== "object" || Array.isArray(updates)) {
420
- return [];
3377
+ const deptId = args.deptId ?? args.departmentId;
3378
+ if (!isBlank(deptId) && body.deptId === undefined) {
3379
+ body.deptId = deptId;
421
3380
  }
422
- return Object.entries(updates).map(([key, value]) => looksLikeFieldName(key) ? { fieldName: key, value } : { name: key, value });
3381
+ return cleanObject(body);
423
3382
  }
424
- function looksLikeFieldName(value) {
425
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
3383
+ function buildAddressBookDepartmentBody(args) {
3384
+ const body = buildPassthroughBody(args, ["keyword", "query", "q"], {
3385
+ type: args.type ?? "tree"
3386
+ });
3387
+ const name = firstTextValue(args.name, args.deptName, args.keyword, args.query, args.q);
3388
+ if (name && body.name === undefined && body.deptName === undefined) {
3389
+ body.name = name;
3390
+ }
3391
+ return cleanObject(body);
426
3392
  }
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);
3393
+ function addressBookDeptIds(args) {
3394
+ return requireIdArray(args.deptIds ?? args.ids ?? args.deptId ?? args.id, "查询通讯录部门用户请提供 deptIds/ids 数组或 deptId/id。");
432
3395
  }
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;
3396
+ function buildAddressBookUserDeptRoleBody(args) {
3397
+ const body = cleanObject({
3398
+ userIds: args.userIds,
3399
+ deptIds: args.deptIds,
3400
+ roleIds: args.roleIds
3401
+ });
3402
+ if (!Array.isArray(body.userIds) && !Array.isArray(body.deptIds) && !Array.isArray(body.roleIds)) {
3403
+ throw new Error("查询通讯录用户、部门、角色聚合信息请至少提供 userIds、deptIds 或 roleIds。");
3404
+ }
3405
+ return body;
457
3406
  }
458
- function hydrateUpdateList(list, fields) {
459
- return list.map((item) => hydrateUpdateItem(item, fields));
3407
+ function requireAddressBookUserId(args, message) {
3408
+ const userId = args.userId ?? args.id;
3409
+ if (isBlank(userId)) {
3410
+ throw new Error(message);
3411
+ }
3412
+ return userId;
460
3413
  }
461
- function hydrateUpdateItem(item, fields) {
462
- const field = findFieldDefinition(item, fields);
463
- if (!field && hasUpdateMetadata(item)) {
464
- return normalizeUpdateItem(item);
3414
+ function requireAddressBookUsername(args) {
3415
+ const username = firstTextValue(args.username, args.userName, args.name);
3416
+ if (!username) {
3417
+ throw new Error("查询通讯录用户数量请提供 username 或 userName。");
465
3418
  }
466
- if (!field) {
467
- const fieldLabel = item.name ?? item.fieldName ?? item.fieldId ?? "<unknown>";
468
- throw new Error(`未在字段 schema 中找到可写字段:${fieldLabel}。请先调用 crm_get_module_schema 确认字段名。`);
3419
+ return username;
3420
+ }
3421
+ function buildAddressBookAttentionBody(args) {
3422
+ const body = objectWriteBody(args);
3423
+ const userId = args.userId ?? args.id ?? body.userId;
3424
+ if (isBlank(userId)) {
3425
+ throw new Error("切换通讯录关注状态请提供 userId 或 id。");
469
3426
  }
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);
3427
+ if (isBlank(body.userId)) {
3428
+ body.userId = userId;
3429
+ }
3430
+ return cleanObject(body);
480
3431
  }
481
- function hasUpdateMetadata(item) {
482
- return Boolean(item.fieldName && item.fieldType !== undefined && item.type !== undefined);
3432
+ function buildOaExamineCategoryBody(args) {
3433
+ const body = buildPassthroughBody(args, ["keyword", "query", "q"], {
3434
+ pageType: args.pageType ?? 0,
3435
+ label: args.label ?? 0
3436
+ });
3437
+ const search = firstTextValue(args.search, args.keyword, args.query, args.q, args.name);
3438
+ if (search && body.search === undefined) {
3439
+ body.search = search;
3440
+ }
3441
+ return cleanObject(body);
483
3442
  }
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
3443
+ function buildOaExamineGroupBody(args) {
3444
+ return buildPassthroughBody(args, [], {
3445
+ groupType: args.groupType ?? 0
493
3446
  });
494
3447
  }
495
- async function resolveUpdateBatchId(client, moduleDefinition, body, list) {
496
- if (body.batchId || !body.id || !updateNeedsBatchId(list)) {
497
- return undefined;
3448
+ function getOaExamineListDefinition(args) {
3449
+ const listType = firstTextValue(args.listType, args.view, args.scene, args.kind, args.mode, "upcoming") ?? "upcoming";
3450
+ const tab = firstTextValue(args.tab, args.scope);
3451
+ const candidates = [
3452
+ tab ? `${listType}.${tab}` : undefined,
3453
+ tab ? `${listType}${tab}` : undefined,
3454
+ listType
3455
+ ]
3456
+ .filter((value) => Boolean(value))
3457
+ .map(normalizeOaExamineAlias);
3458
+ const definition = candidates
3459
+ .map((candidate) => OA_EXAMINE_LIST_ENDPOINTS.find((item) => item.aliases.some((alias) => normalizeOaExamineAlias(alias) === candidate)))
3460
+ .find((item) => Boolean(item));
3461
+ if (!definition) {
3462
+ throw new Error(`当前 MCP 支持的 OA 审批列表类型:${OA_EXAMINE_LIST_ENDPOINTS.map((item) => item.key).join("、")}。`);
498
3463
  }
499
- const data = await client.post(`${moduleDefinition.basePath}/queryById/${body.id}`);
500
- return findFirstValueByKey(data, "batchId");
3464
+ return definition;
501
3465
  }
502
- function updateNeedsBatchId(list) {
503
- return list.some((item) => {
504
- const fieldType = Number(item.fieldType);
505
- return fieldType === 0 || fieldType === 2;
3466
+ function buildOaExamineListBody(args) {
3467
+ const body = buildPassthroughBody(args, ["listType", "view", "scene", "kind", "mode", "tab", "scope", "keyword", "query", "q", "filters"], {
3468
+ page: args.page ?? 1,
3469
+ limit: args.limit ?? 15
506
3470
  });
3471
+ const search = firstTextValue(args.search, args.keyword, args.query, args.q, args.name, args.content);
3472
+ if (search && body.search === undefined) {
3473
+ body.search = search;
3474
+ }
3475
+ if (args.filters && body.searchBO === undefined) {
3476
+ body.searchBO = { searchList: args.filters };
3477
+ }
3478
+ return cleanObject(body);
507
3479
  }
508
- function findFirstValueByKey(input, key) {
509
- const seen = new Set();
3480
+ function buildOaExamineFieldBody(args) {
3481
+ return buildPassthroughBody(args, ["id", "examineIdForFields"], {
3482
+ examineId: requireOaExamineId(args, "查询 OA 审批详情字段请提供 examineId 或 id。"),
3483
+ isDetail: args.isDetail ?? 1,
3484
+ type: args.type ?? 1
3485
+ });
3486
+ }
3487
+ function buildOaExamineCreateFieldQuery(args, categoryId) {
3488
+ return buildPassthroughBody(args, [
3489
+ "id",
3490
+ "examineIdForFields",
3491
+ "mode",
3492
+ "useFieldList",
3493
+ "fieldList",
3494
+ "fields",
3495
+ "updates",
3496
+ "fieldValues",
3497
+ "fieldForm",
3498
+ "entity",
3499
+ "oaExamine",
3500
+ "oaExamineRelation",
3501
+ "oaExamineTravelList",
3502
+ "examineFlowData",
3503
+ "batchId",
3504
+ "strictRequired",
3505
+ "customerId",
3506
+ "customerIds",
3507
+ "contactsId",
3508
+ "contactsIds",
3509
+ "businessId",
3510
+ "businessIds",
3511
+ "contractId",
3512
+ "contractIds",
3513
+ "receivablesId",
3514
+ "receivablesIds",
3515
+ "quotationId",
3516
+ "quotationIds"
3517
+ ], {
3518
+ categoryId,
3519
+ needHidden: args.needHidden ?? true
3520
+ });
3521
+ }
3522
+ function buildOaExamineRecordLogBody(args) {
3523
+ const recordId = args.recordId ?? args.examineRecordId ?? args.id;
3524
+ if (isBlank(recordId)) {
3525
+ throw new Error("查询 OA 审批记录日志请提供 recordId、examineRecordId 或 id。");
3526
+ }
3527
+ return buildPassthroughBody(args, ["id", "examineRecordId"], { recordId });
3528
+ }
3529
+ function buildOaExamineFlowRecordBody(args) {
3530
+ const recordId = args.recordId ?? args.examineRecordId ?? args.id;
3531
+ if (isBlank(recordId)) {
3532
+ throw new Error("查询 OA 审批流程节点请提供 recordId、examineRecordId 或 id。");
3533
+ }
3534
+ return buildPassthroughBody(args, ["id", "examineRecordId"], { recordId });
3535
+ }
3536
+ async function buildOaExamineWriteBody(client, args, requireId) {
3537
+ const body = objectWriteBody(args);
3538
+ const examineId = args.examineId ?? args.id ?? body.examineId;
3539
+ if (requireId && isBlank(examineId)) {
3540
+ throw new Error("修改 OA 审批请提供 examineId 或 id。");
3541
+ }
3542
+ if (shouldBuildStructuredOaExamineBody(args, body)) {
3543
+ return buildStructuredOaExamineWriteBody(client, args, examineId);
3544
+ }
3545
+ if (!isBlank(examineId) && isBlank(body.examineId)) {
3546
+ body.examineId = examineId;
3547
+ }
3548
+ return cleanObject(body);
3549
+ }
3550
+ function shouldBuildStructuredOaExamineBody(args, body) {
3551
+ if (body.oaExamine || body.oaExamineRelation || body.oaExamineTravelList || Array.isArray(body.field)) {
3552
+ return false;
3553
+ }
3554
+ const categoryId = args.categoryId ?? body.categoryId;
3555
+ const fieldValues = args.fieldValues ?? args.fields ?? args.updates ?? args.fieldForm;
3556
+ return !isBlank(categoryId) && fieldValues && typeof fieldValues === "object";
3557
+ }
3558
+ async function buildStructuredOaExamineWriteBody(client, args, examineId) {
3559
+ const categoryId = args.categoryId;
3560
+ if (isBlank(categoryId)) {
3561
+ throw new Error("自动组装 OA 审批自定义字段请提供 categoryId。");
3562
+ }
3563
+ const targetPath = `/oaExamineField/queryField/${categoryId}`;
3564
+ const fieldMetadata = await client.post(targetPath, buildOaExamineCreateFieldQuery(args, categoryId));
3565
+ const fields = flattenOaExamineFields(fieldMetadata);
3566
+ if (fields.length === 0) {
3567
+ throw new Error(`未从 ${targetPath} 读取到 OA 审批字段,无法自动组装自定义字段。`);
3568
+ }
3569
+ const fieldValues = normalizeFieldValues(args.fieldValues ?? args.fields ?? args.updates ?? args.fieldForm);
3570
+ const params = {
3571
+ oaExamine: cleanObject({
3572
+ ...(isPlainObject(args.oaExamine) ? args.oaExamine : {}),
3573
+ ...(isPlainObject(args.entity) ? args.entity : {}),
3574
+ categoryId,
3575
+ examineId: isBlank(examineId) ? undefined : examineId,
3576
+ batchId: args.batchId ?? args.oaExamine?.batchId ?? randomUUID()
3577
+ }),
3578
+ oaExamineRelation: buildOaExamineRelation(args),
3579
+ field: [],
3580
+ oaExamineTravelList: Array.isArray(args.oaExamineTravelList) ? [...args.oaExamineTravelList] : []
3581
+ };
3582
+ for (const [rawKey, rawValue] of Object.entries(fieldValues)) {
3583
+ const field = findOaExamineField(fields, rawKey);
3584
+ if (!field) {
3585
+ throw new Error(`OA 审批类型 ${categoryId} 未找到字段“${rawKey}”,请先用 crm_get_oa_examine_fields 查询字段名。`);
3586
+ }
3587
+ appendOaExamineFieldValue(params, field, rawValue);
3588
+ }
3589
+ if (args.examineFlowData !== undefined) {
3590
+ params.examineFlowData = args.examineFlowData;
3591
+ }
3592
+ if (args.strictRequired) {
3593
+ const missing = findMissingRequiredOaFields(fields, fieldValues);
3594
+ if (missing.length > 0) {
3595
+ throw new Error(`OA 审批缺少必填字段:${missing.map((field) => field.name ?? field.fieldName).join("、")}。`);
3596
+ }
3597
+ }
3598
+ return cleanObject(params);
3599
+ }
3600
+ function flattenOaExamineFields(input) {
3601
+ const output = [];
510
3602
  const visit = (value) => {
511
- if (!value || typeof value !== "object" || seen.has(value)) {
512
- return undefined;
3603
+ if (!Array.isArray(value)) {
3604
+ return;
513
3605
  }
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;
3606
+ for (const item of value) {
3607
+ if (Array.isArray(item)) {
3608
+ visit(item);
3609
+ }
3610
+ else if (isPlainObject(item)) {
3611
+ const record = item;
3612
+ if (record.fieldName || record.fieldId) {
3613
+ output.push(record);
3614
+ }
3615
+ for (const key of ["list", "fieldList", "children"]) {
3616
+ if (Array.isArray(record[key])) {
3617
+ visit(record[key]);
3618
+ }
520
3619
  }
521
3620
  }
522
- return undefined;
523
3621
  }
524
- const record = value;
525
- if (record[key] !== undefined && record[key] !== null && record[key] !== "") {
526
- return record[key];
3622
+ };
3623
+ visit(input);
3624
+ return output;
3625
+ }
3626
+ function normalizeFieldValues(input) {
3627
+ if (Array.isArray(input)) {
3628
+ const output = {};
3629
+ for (const item of input) {
3630
+ if (!isPlainObject(item)) {
3631
+ continue;
3632
+ }
3633
+ const record = item;
3634
+ const key = record.fieldName ?? record.name ?? record.field ?? record.fieldId;
3635
+ if (!isBlank(key)) {
3636
+ output[String(key)] = Object.prototype.hasOwnProperty.call(record, "value") ? record.value : record;
3637
+ }
527
3638
  }
528
- for (const child of Object.values(record)) {
529
- const found = visit(child);
530
- if (found !== undefined && found !== null && found !== "") {
531
- return found;
3639
+ return output;
3640
+ }
3641
+ if (isPlainObject(input)) {
3642
+ return input;
3643
+ }
3644
+ return {};
3645
+ }
3646
+ function findOaExamineField(fields, key) {
3647
+ const target = normalizeFieldLookupKey(key);
3648
+ return fields.find((field) => [field.fieldName, field.name, field.field, field.fieldId]
3649
+ .some((value) => normalizeFieldLookupKey(value) === target));
3650
+ }
3651
+ function appendOaExamineFieldValue(params, field, rawValue) {
3652
+ if (field.fieldName === "cause") {
3653
+ appendOaExamineCauseValue(params, field, rawValue);
3654
+ return;
3655
+ }
3656
+ const value = getOaExamineRealValue(field, rawValue);
3657
+ if (Number(field.fieldType) === 1) {
3658
+ params.oaExamine[field.fieldName] = isEmptyValue(value) ? "" : value;
3659
+ return;
3660
+ }
3661
+ if (field.formType !== "desc_text") {
3662
+ params.field.push({
3663
+ fieldName: field.fieldName,
3664
+ fieldType: field.fieldType,
3665
+ name: field.name,
3666
+ type: field.type,
3667
+ fieldId: field.fieldId,
3668
+ value
3669
+ });
3670
+ }
3671
+ }
3672
+ function appendOaExamineCauseValue(params, field, rawValue) {
3673
+ const value = isPlainObject(rawValue) && Array.isArray(rawValue.list) ? rawValue : { list: rawValue };
3674
+ const list = Array.isArray(value.list) ? value.list : [];
3675
+ if (field.formType === "business_cause") {
3676
+ params.oaExamineTravelList = list;
3677
+ return;
3678
+ }
3679
+ if (field.formType === "examine_cause") {
3680
+ for (const item of list) {
3681
+ if (isPlainObject(item)) {
3682
+ const copy = { ...item };
3683
+ delete copy.imgList;
3684
+ params.oaExamineTravelList.push(copy);
532
3685
  }
533
3686
  }
534
- return undefined;
535
- };
536
- return visit(input);
3687
+ }
537
3688
  }
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;
3689
+ function getOaExamineRealValue(field, value) {
3690
+ const formType = field.formType;
3691
+ if (formType === "contract" && field.multiContract) {
3692
+ if (field.multiType === "receivables") {
3693
+ return value;
545
3694
  }
546
- if (fieldName && normalizeText(field.fieldName) === fieldName) {
547
- return true;
3695
+ if (field.multiType === "invoice" && Array.isArray(value)) {
3696
+ return value.map((item) => {
3697
+ if (!isPlainObject(item)) {
3698
+ return item;
3699
+ }
3700
+ const record = item;
3701
+ return {
3702
+ contractId: record.contractId,
3703
+ receivablesIdList: Array.isArray(record.receivablesList)
3704
+ ? record.receivablesList.map((receivable) => receivable.receivablesId)
3705
+ : [],
3706
+ currInvoiceMoney: record.currInvoiceMoney,
3707
+ remark: record.remark
3708
+ };
3709
+ });
548
3710
  }
549
- return Boolean(name && normalizeText(field.name) === name);
550
- });
3711
+ }
3712
+ if (isModuleSelectFormType(formType)) {
3713
+ return getModuleSelectValue(field, value);
3714
+ }
3715
+ if (formType === "user" || formType === "single_user" || formType === "structure") {
3716
+ return getUserOrStructureValue(formType, value);
3717
+ }
3718
+ if (formType === "file") {
3719
+ return getFileBatchId(value);
3720
+ }
3721
+ if (formType === "category") {
3722
+ return Array.isArray(value) ? value[value.length - 1] : value ?? "";
3723
+ }
3724
+ if (formType === "checkbox") {
3725
+ return Array.isArray(value) ? value.join(",") : value;
3726
+ }
3727
+ if (formType === "detail_table") {
3728
+ return getDetailTableValue(field, value);
3729
+ }
3730
+ if (formType === "number" || formType === "floatnumber") {
3731
+ return typeof value === "number" ? (Number.isNaN(value) ? "" : String(value)) : value;
3732
+ }
3733
+ if (formType === "data_dictionary") {
3734
+ return value ?? null;
3735
+ }
3736
+ return value;
551
3737
  }
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;
3738
+ function isModuleSelectFormType(formType) {
3739
+ return [
3740
+ "leads",
3741
+ "contacts",
3742
+ "customer",
3743
+ "contract",
3744
+ "receivables",
3745
+ "business",
3746
+ "workorder",
3747
+ "quotation",
3748
+ "visitPlan",
3749
+ "supplier",
3750
+ "warehouse",
3751
+ "invoice"
3752
+ ].includes(String(formType));
3753
+ }
3754
+ function getModuleSelectValue(field, value) {
3755
+ if (isEmptyValue(value)) {
3756
+ return "";
556
3757
  }
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("、")}`);
3758
+ const formType = String(field.formType);
3759
+ if (Array.isArray(value) && value.length > 0 && isPlainObject(value[0])) {
3760
+ if (formType === "invoice" && field.isMulti == 1) {
3761
+ return value.map((item) => item[`${formType}Id`]);
3762
+ }
3763
+ return value[0][`${formType}Id`] ?? value[0].id;
3764
+ }
3765
+ if (isPlainObject(value)) {
3766
+ const record = value;
3767
+ return record[`${formType}Id`] ?? record.id;
3768
+ }
3769
+ if (Array.isArray(value)) {
3770
+ return value[value.length - 1];
562
3771
  }
563
3772
  return value;
564
3773
  }
565
- function parseFieldOptions(input) {
566
- if (!input) {
567
- return [];
3774
+ function getUserOrStructureValue(formType, value) {
3775
+ let normalized = value ?? [];
3776
+ if (Array.isArray(value) && value.length > 0 && isPlainObject(value[0])) {
3777
+ normalized = value.map((item) => {
3778
+ const record = item;
3779
+ return formType === "structure" ? record.id ?? record.deptId : record.userId ?? record.id;
3780
+ });
3781
+ }
3782
+ else if (isPlainObject(value)) {
3783
+ const record = value;
3784
+ normalized = formType === "structure" ? record.id ?? record.deptId : record.userId ?? record.id;
568
3785
  }
569
- if (Array.isArray(input)) {
570
- return input.map((option) => (typeof option === "object" && option ? option : { value: option, name: option }));
3786
+ return Array.isArray(normalized) ? normalized.join(",") : normalized;
3787
+ }
3788
+ function getFileBatchId(value) {
3789
+ if (Array.isArray(value) && value.length > 0 && isPlainObject(value[0])) {
3790
+ return value[0].batchId ?? "";
571
3791
  }
572
- if (typeof input === "string") {
573
- const text = input.trim();
574
- if (!text) {
575
- return [];
576
- }
577
- try {
578
- return parseFieldOptions(JSON.parse(text));
3792
+ if (isPlainObject(value)) {
3793
+ return value.batchId ?? "";
3794
+ }
3795
+ return "";
3796
+ }
3797
+ function getDetailTableValue(field, value) {
3798
+ const fieldExtendList = Array.isArray(field.fieldExtendList) ? field.fieldExtendList : [];
3799
+ const rows = Array.isArray(value) ? value : [];
3800
+ const result = [];
3801
+ for (const row of rows) {
3802
+ if (!isPlainObject(row) || isDetailRowEmpty(fieldExtendList, row)) {
3803
+ continue;
579
3804
  }
580
- catch {
581
- return text.split(",").map((option) => option.trim()).filter(Boolean).map((option) => ({ value: option, name: option }));
3805
+ const rowRecord = row;
3806
+ const batchId = rowRecord.batchId || randomUUID();
3807
+ const rowFields = [];
3808
+ for (const tableField of fieldExtendList) {
3809
+ if (!isPlainObject(tableField)) {
3810
+ continue;
3811
+ }
3812
+ const copy = { ...tableField };
3813
+ delete copy.companyId;
3814
+ copy.value = getOaExamineRealValue(copy, rowRecord[copy.fieldName]);
3815
+ copy.batchId = batchId;
3816
+ rowFields.push(copy);
582
3817
  }
3818
+ result.push(rowFields);
583
3819
  }
584
- if (typeof input === "object") {
585
- const object = input;
586
- return parseFieldOptions(object.options ?? object.list ?? object.data ?? []);
3820
+ return result;
3821
+ }
3822
+ function isDetailRowEmpty(fields, row) {
3823
+ return fields.every((field) => isEmptyValue(row[field.fieldName]));
3824
+ }
3825
+ function buildOaExamineRelation(args) {
3826
+ if (isPlainObject(args.oaExamineRelation)) {
3827
+ return { ...args.oaExamineRelation };
587
3828
  }
588
- return [];
3829
+ return cleanObject({
3830
+ customerIds: toCommaSeparated(args.customerIds ?? args.customerId),
3831
+ contactsIds: toCommaSeparated(args.contactsIds ?? args.contactsId),
3832
+ businessIds: toCommaSeparated(args.businessIds ?? args.businessId),
3833
+ contractIds: toCommaSeparated(args.contractIds ?? args.contractId),
3834
+ receivablesIds: toCommaSeparated(args.receivablesIds ?? args.receivablesId),
3835
+ quotationIds: toCommaSeparated(args.quotationIds ?? args.quotationId)
3836
+ });
589
3837
  }
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
- };
3838
+ function findMissingRequiredOaFields(fields, values) {
3839
+ return fields.filter((field) => {
3840
+ if (Number(field.isNull) !== 1 || field.formType === "desc_text") {
3841
+ return false;
3842
+ }
3843
+ const value = values[field.fieldName] ?? values[field.name] ?? values[field.fieldId];
3844
+ return isEmptyValue(value);
602
3845
  });
603
- return {
604
- endpoint,
605
- ok: checked.every((item) => item.matched),
606
- checked
607
- };
608
3846
  }
609
- function findInformationValue(data, field) {
610
- const direct = findDirectInformationValue(data, field);
611
- if (direct.found) {
612
- return direct.value;
3847
+ function normalizeFieldLookupKey(value) {
3848
+ return String(value ?? "").trim().toLowerCase();
3849
+ }
3850
+ function isPlainObject(value) {
3851
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3852
+ }
3853
+ function isEmptyValue(value) {
3854
+ if (value === undefined || value === null || value === "") {
3855
+ return true;
613
3856
  }
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 };
3857
+ if (Array.isArray(value)) {
3858
+ return value.length === 0;
3859
+ }
3860
+ if (isPlainObject(value)) {
3861
+ return Object.keys(value).length === 0;
3862
+ }
3863
+ return false;
3864
+ }
3865
+ function buildOaExamineAuditBody(args, batch) {
3866
+ const body = objectWriteBody(args);
3867
+ const recordId = args.recordId ?? args.examineRecordId ?? args.id ?? body.recordId;
3868
+ const recordIds = args.recordIds ?? args.examineRecordIds ?? args.ids ?? body.recordIds;
3869
+ if (batch) {
3870
+ if (!Array.isArray(recordIds) || recordIds.length === 0) {
3871
+ throw new Error("批量审批 OA 审批请提供 recordIds、examineRecordIds 或 ids 数组。");
620
3872
  }
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 };
3873
+ if (body.recordIds === undefined) {
3874
+ body.recordIds = recordIds;
630
3875
  }
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) };
3876
+ }
3877
+ else {
3878
+ if (isBlank(recordId)) {
3879
+ throw new Error("审批 OA 审批请提供 recordId、examineRecordId id。");
636
3880
  }
637
- for (const child of Object.values(record)) {
638
- const result = visit(child);
639
- if (result.found) {
640
- return result;
641
- }
3881
+ if (isBlank(body.recordId)) {
3882
+ body.recordId = recordId;
642
3883
  }
643
- return { found: false };
644
- };
645
- return visit(data).value;
3884
+ }
3885
+ return cleanObject(body);
646
3886
  }
647
- function findDirectInformationValue(data, field) {
648
- if (!data || typeof data !== "object" || Array.isArray(data)) {
649
- return { found: false };
3887
+ function requireOaExamineId(args, message) {
3888
+ const examineId = args.examineId ?? args.id;
3889
+ if (isBlank(examineId)) {
3890
+ throw new Error(message);
650
3891
  }
651
- const record = data;
652
- if (field.fieldName && Object.prototype.hasOwnProperty.call(record, field.fieldName)) {
653
- return { found: true, value: record[field.fieldName] };
3892
+ return examineId;
3893
+ }
3894
+ function buildPassthroughBody(args, omitKeys = [], defaults = {}) {
3895
+ const omit = new Set(["confirm", ...omitKeys]);
3896
+ const body = { ...defaults };
3897
+ for (const [key, value] of Object.entries(args ?? {})) {
3898
+ if (!omit.has(key) && value !== undefined && value !== null) {
3899
+ body[key] = value;
3900
+ }
654
3901
  }
655
- return { found: false };
3902
+ return cleanObject(body);
656
3903
  }
657
- function extractInformationValue(record) {
658
- if (Object.prototype.hasOwnProperty.call(record, "value")) {
659
- return record.value;
3904
+ function objectWriteBody(args) {
3905
+ const explicitBody = explicitWriteBody(args);
3906
+ return explicitBody && typeof explicitBody === "object" && !Array.isArray(explicitBody)
3907
+ ? { ...explicitBody }
3908
+ : {};
3909
+ }
3910
+ function normalizeOaExamineAlias(value) {
3911
+ return String(value ?? "").trim().replace(/[\s._-]+/g, "").toLowerCase();
3912
+ }
3913
+ function previewOaWrite(operation, module, moduleName, targetPath, body) {
3914
+ return {
3915
+ preview: true,
3916
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
3917
+ module,
3918
+ moduleName,
3919
+ operation,
3920
+ targetPath,
3921
+ body
3922
+ };
3923
+ }
3924
+ function buildCalendarListBody(args) {
3925
+ return cleanObject({
3926
+ startTime: args.startTime ?? args.start,
3927
+ endTime: args.endTime ?? args.end,
3928
+ typeIds: args.typeIds,
3929
+ userId: args.userId
3930
+ });
3931
+ }
3932
+ function buildCalendarDetailBody(args) {
3933
+ return cleanObject({
3934
+ eventId: requireCalendarEventId(args),
3935
+ startTime: args.startTime ?? args.start,
3936
+ endTime: args.endTime ?? args.end
3937
+ });
3938
+ }
3939
+ function buildCalendarEventBody(args, requireEventId) {
3940
+ const event = buildCalendarEvent(args);
3941
+ if (requireEventId && isBlank(event.eventId)) {
3942
+ throw new Error("修改日程请提供 eventId 或 id。");
660
3943
  }
661
- if (Object.prototype.hasOwnProperty.call(record, "valueDesc")) {
662
- return record.valueDesc;
3944
+ const relation = args.relation !== undefined ? args.relation : buildCalendarRelation(args);
3945
+ const notice = args.notice ?? args.notices ?? [];
3946
+ return cleanObject({
3947
+ event,
3948
+ relation,
3949
+ notice,
3950
+ type: args.type,
3951
+ time: args.time
3952
+ });
3953
+ }
3954
+ function buildCalendarEvent(args) {
3955
+ const event = args.event && typeof args.event === "object" && !Array.isArray(args.event)
3956
+ ? { ...args.event }
3957
+ : cleanObject({
3958
+ eventId: args.eventId ?? args.id,
3959
+ title: args.title,
3960
+ typeId: args.typeId,
3961
+ startTime: args.startTime ?? args.start,
3962
+ endTime: args.endTime ?? args.end,
3963
+ ownerUserIds: args.ownerUserIds ?? args.ownerUserId ?? args.userIds,
3964
+ repetitionType: args.repetitionType,
3965
+ repeatRate: args.repeatRate,
3966
+ repeatTime: args.repeatTime,
3967
+ endType: args.endType,
3968
+ endTypeConfig: args.endTypeConfig,
3969
+ repeatStartTime: args.repeatStartTime,
3970
+ repeatEndTime: args.repeatEndTime,
3971
+ batchId: args.batchId,
3972
+ status: args.status
3973
+ });
3974
+ const directEventId = args.eventId ?? args.id;
3975
+ if (!isBlank(directEventId) && isBlank(event.eventId)) {
3976
+ event.eventId = directEventId;
663
3977
  }
664
- if (Object.prototype.hasOwnProperty.call(record, "fieldValue")) {
665
- return record.fieldValue;
3978
+ if (event.ownerUserIds !== undefined) {
3979
+ event.ownerUserIds = toCommaSeparated(event.ownerUserIds);
666
3980
  }
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 "";
3981
+ if (event.repetitionType === undefined || event.repetitionType === null) {
3982
+ event.repetitionType = 1;
675
3983
  }
676
- if (typeof value === "object") {
677
- return JSON.stringify(value);
3984
+ if (event.endType === undefined || event.endType === null) {
3985
+ event.endType = 1;
678
3986
  }
679
- return String(value).trim();
3987
+ return cleanObject(event);
680
3988
  }
681
- function normalizeText(value) {
682
- return value === undefined || value === null ? "" : String(value).trim().toLowerCase();
3989
+ function buildCalendarRelation(args) {
3990
+ const relation = cleanObject({
3991
+ customerIds: toCommaSeparated(args.customerIds ?? args.customerId),
3992
+ leadsIds: toCommaSeparated(args.leadsIds ?? args.leadsId),
3993
+ quotationIds: toCommaSeparated(args.quotationIds ?? args.quotationId),
3994
+ receivablesIds: toCommaSeparated(args.receivablesIds ?? args.receivablesId),
3995
+ contactsIds: toCommaSeparated(args.contactsIds ?? args.contactsId),
3996
+ businessIds: toCommaSeparated(args.businessIds ?? args.businessId),
3997
+ contractIds: toCommaSeparated(args.contractIds ?? args.contractId),
3998
+ roomIds: toCommaSeparated(args.roomIds ?? args.roomId)
3999
+ });
4000
+ return Object.keys(relation).length > 0 ? relation : undefined;
683
4001
  }
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
- }));
4002
+ function requireCalendarEventId(args) {
4003
+ const eventId = args.eventId ?? args.id;
4004
+ if (isBlank(eventId)) {
4005
+ throw new Error("查询日程详情请提供 eventId 或 id。");
4006
+ }
4007
+ return eventId;
4008
+ }
4009
+ function previewCalendarWrite(operation, targetPath, body) {
719
4010
  return {
720
- module: "customerPool",
721
- readonly: true,
722
- poolCount: poolSummaries.length,
723
- pools: poolSummaries
4011
+ preview: true,
4012
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
4013
+ module: "calendarEvent",
4014
+ moduleName: "日程",
4015
+ operation,
4016
+ targetPath,
4017
+ body
724
4018
  };
725
4019
  }
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";
4020
+ function buildOaTaskListBody(args) {
4021
+ return cleanObject({
4022
+ page: args.page,
4023
+ limit: args.limit,
4024
+ type: args.type,
4025
+ status: args.status,
4026
+ priority: args.priority,
4027
+ dueDate: args.dueDate,
4028
+ mold: args.mold,
4029
+ userId: args.userId,
4030
+ mainUserId: args.mainUserId,
4031
+ mainUserIds: args.mainUserIds,
4032
+ search: firstTextValue(args.search, args.keyword, args.query, args.q, args.name)
4033
+ });
735
4034
  }
736
- function getPoolRecordModule(moduleDefinition) {
737
- return getModuleByKey(moduleDefinition.key === "customerPool" ? "customer" : "leads");
4035
+ function buildRelatedOaTaskQuery(args) {
4036
+ return cleanQuery({
4037
+ type: args.type,
4038
+ typeId: args.typeId ?? args.id
4039
+ });
738
4040
  }
739
- async function resolvePoolContext(client, moduleDefinition, args) {
740
- if (!isPoolModule(moduleDefinition)) {
741
- throw new Error(`Module ${moduleDefinition.key} is not a pool module.`);
4041
+ function buildOaTaskBody(args) {
4042
+ const task = args.task && typeof args.task === "object" && !Array.isArray(args.task)
4043
+ ? { ...args.task }
4044
+ : cleanObject({
4045
+ taskId: args.taskId ?? args.id,
4046
+ name: args.name ?? args.title,
4047
+ createUserId: args.createUserId,
4048
+ mainUserId: args.mainUserId,
4049
+ ownerUserId: args.ownerUserId ?? args.ownerUserIds,
4050
+ status: args.status,
4051
+ classId: args.classId,
4052
+ labelId: args.labelId ?? args.labelIds,
4053
+ description: args.description,
4054
+ pid: args.pid,
4055
+ startTime: args.startTime ?? args.start,
4056
+ stopTime: args.stopTime ?? args.endTime ?? args.end,
4057
+ priority: args.priority,
4058
+ workId: args.workId,
4059
+ isTop: args.isTop,
4060
+ isOpen: args.isOpen,
4061
+ orderNum: args.orderNum,
4062
+ topOrderNum: args.topOrderNum,
4063
+ batchId: args.batchId,
4064
+ isArchive: args.isArchive,
4065
+ customerIds: args.customerIds ?? args.customerId,
4066
+ contactsIds: args.contactsIds ?? args.contactsId,
4067
+ businessIds: args.businessIds ?? args.businessId,
4068
+ contractIds: args.contractIds ?? args.contractId
4069
+ });
4070
+ for (const key of ["ownerUserId", "labelId", "customerIds", "contactsIds", "businessIds", "contractIds"]) {
4071
+ if (task[key] !== undefined) {
4072
+ task[key] = toCommaSeparated(task[key]);
4073
+ }
742
4074
  }
743
- if (args.poolId) {
744
- return { poolId: args.poolId, pool: cleanObject({ poolId: args.poolId, poolName: args.poolName }) };
4075
+ return cleanObject(task);
4076
+ }
4077
+ function buildOaTaskUpdateCalls(args) {
4078
+ const taskId = requireOaTaskId(args);
4079
+ const source = args.updates && typeof args.updates === "object" && !Array.isArray(args.updates) ? args.updates : args;
4080
+ const calls = [];
4081
+ const name = firstPresentValue(source.name, source.title);
4082
+ if (name !== undefined) {
4083
+ calls.push({ targetPath: "/workTask/setWorkTaskTitle", body: { taskId, name } });
745
4084
  }
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。`);
4085
+ const description = firstPresentValue(source.description);
4086
+ if (description !== undefined) {
4087
+ calls.push({ targetPath: "/workTask/setWorkTaskDescription", body: { taskId, description } });
753
4088
  }
754
- const poolId = selectedPool.poolId ?? selectedPool.id;
755
- if (!poolId) {
756
- throw new Error(`${moduleDefinition.name}授权列表缺少 poolId,无法查询池内数据。`);
4089
+ const mainUserId = firstPresentValue(source.mainUserId, source.userId);
4090
+ if (mainUserId !== undefined) {
4091
+ calls.push({ targetPath: "/workTask/setWorkTaskMainUser", body: { taskId, userId: mainUserId } });
757
4092
  }
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) };
4093
+ const ownerUserId = firstPresentValue(source.ownerUserId, source.ownerUserIds);
4094
+ if (ownerUserId !== undefined) {
4095
+ calls.push({ targetPath: "/workTask/setWorkTaskOwnerUser", body: { taskId, ownerUserId: toCommaSeparated(ownerUserId) } });
765
4096
  }
766
- if (moduleDefinition.key === "business" && relation === "contacts") {
767
- return { module: moduleDefinition.key, relation, data: await client.post("/crmBusiness/queryContacts", pageBody) };
4097
+ const startTime = firstPresentValue(source.startTime, source.start);
4098
+ const stopTime = firstPresentValue(source.stopTime, source.endTime, source.end);
4099
+ if (startTime !== undefined || stopTime !== undefined) {
4100
+ calls.push({
4101
+ targetPath: "/workTask/setWorkTaskTime",
4102
+ body: cleanObject({ taskId, startTime, stopTime })
4103
+ });
768
4104
  }
769
- if (moduleDefinition.key === "contacts" && relation === "business") {
770
- return { module: moduleDefinition.key, relation, data: await client.post("/crmContacts/queryBusiness", pageBody) };
4105
+ const labelId = firstPresentValue(source.labelId, source.labelIds);
4106
+ if (labelId !== undefined) {
4107
+ calls.push({ targetPath: "/workTask/setWorkTaskLabel", body: { taskId, labelId: toCommaSeparated(labelId) } });
771
4108
  }
772
- if (moduleDefinition.key === "contract" && relation === "product") {
773
- return { module: moduleDefinition.key, relation, data: await client.post("/crmContract/queryProductListByContractId", pageBody) };
4109
+ const priority = firstPresentValue(source.priority);
4110
+ if (priority !== undefined) {
4111
+ calls.push({ targetPath: "/workTask/setWorkTaskPriority", body: { taskId, priority } });
774
4112
  }
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
- };
4113
+ const status = firstPresentValue(source.status);
4114
+ if (status !== undefined) {
4115
+ calls.push({ targetPath: "/workTask/setWorkTaskStatus", body: { taskId, status } });
785
4116
  }
786
- if (moduleDefinition.key === "receivables" && relation === "product") {
787
- return { module: moduleDefinition.key, relation, data: await client.post(`/crmReceivables/queryProductList/${args.id}`) };
4117
+ if (calls.length === 0) {
4118
+ throw new Error("请提供要更新的 OA 任务字段:name、description、mainUserId、ownerUserId、startTime/stopTime、labelId、priority status。");
788
4119
  }
789
- if (moduleDefinition.key === "quotation" && relation === "product") {
790
- return { module: moduleDefinition.key, relation, data: await client.post("/crmQuotation/queryProductList", pageBody) };
4120
+ return calls;
4121
+ }
4122
+ function requireOaTaskId(args) {
4123
+ const taskId = args.taskId ?? args.id;
4124
+ if (isBlank(taskId)) {
4125
+ throw new Error("更新 OA 任务请提供 taskId 或 id。");
791
4126
  }
792
- if (moduleDefinition.key === "product" && relation === "saleProduct") {
793
- return { module: moduleDefinition.key, relation, data: await client.post("/crmProduct/querySaleProductPageList", pageBody) };
4127
+ return taskId;
4128
+ }
4129
+ function previewOaTaskWrite(operation, targetPath, body) {
4130
+ return {
4131
+ preview: true,
4132
+ message: "写入前预览:未传 confirm=true,MCP 不会调用 72CRM 写接口。",
4133
+ module: "oaTask",
4134
+ moduleName: "OA任务",
4135
+ operation,
4136
+ targetPath,
4137
+ body
4138
+ };
4139
+ }
4140
+ function toCommaSeparated(value) {
4141
+ if (Array.isArray(value)) {
4142
+ return value.join(",");
794
4143
  }
795
- if (moduleDefinition.key === "receivablesPlan" && relation === "contractCustomer") {
796
- return { module: moduleDefinition.key, relation, data: await client.post("/crmReceivablesPlan/queryByContractAndCustomer", pageBody) };
4144
+ return value;
4145
+ }
4146
+ function firstPresentValue(...values) {
4147
+ for (const value of values) {
4148
+ if (value !== undefined && value !== null) {
4149
+ return value;
4150
+ }
797
4151
  }
798
- throw new Error(`当前 MCP 未配置“${moduleDefinition.name} / ${relation}”关联查询白名单。`);
4152
+ return undefined;
4153
+ }
4154
+ function isBlank(value) {
4155
+ return value === undefined || value === null || String(value).trim() === "";
799
4156
  }
800
4157
  function previewWrite(args) {
801
4158
  const moduleDefinition = args.module ? getModuleByKey(args.module) : undefined;