yindie-erp-mcp 2.0.4 → 2.0.5

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/client.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { basename, join } from 'node:path';
1
4
  /** ERP API 客户端 */
2
5
  export class YindieClient {
3
6
  /** 后端根地址 */
@@ -63,6 +66,45 @@ export class YindieClient {
63
66
  async postForm(path, formFactory) {
64
67
  return this.requestForm(path, formFactory);
65
68
  }
69
+ /** 下载文件到临时目录,返回落盘路径 */
70
+ async download(method, path, fileName, options = {}, retried = false) {
71
+ const url = new URL(path, this.baseUrl);
72
+ for (const [key, value] of Object.entries(options.params ?? {})) {
73
+ if (value !== undefined && value !== '') {
74
+ url.searchParams.set(key, value);
75
+ }
76
+ }
77
+ const headers = {
78
+ Authorization: `Bearer ${this.token}`,
79
+ clientid: this.clientId
80
+ };
81
+ const init = { method, headers };
82
+ if (options.body !== undefined) {
83
+ headers['Content-Type'] = 'application/json';
84
+ init.body = JSON.stringify(options.body);
85
+ }
86
+ const response = await fetch(url, init);
87
+ const contentType = response.headers.get('content-type') ?? '';
88
+ if (contentType.includes('application/json')) {
89
+ const result = (await response.json());
90
+ if (result.code === 401 && !retried) {
91
+ this.token = await this.relogin();
92
+ return this.download(method, path, fileName, options, true);
93
+ }
94
+ throw new Error(result.msg || `下载失败: ${path}`);
95
+ }
96
+ if (!response.ok) {
97
+ throw new Error(`下载失败: ${path}(HTTP ${response.status})`);
98
+ }
99
+ const headerName = response.headers.get('download-filename');
100
+ const name = fileName ?? (headerName ? decodeURIComponent(headerName) : basename(path));
101
+ const bytes = Buffer.from(await response.arrayBuffer());
102
+ const dir = join(tmpdir(), 'yindie-erp-exports');
103
+ await mkdir(dir, { recursive: true });
104
+ const filePath = join(dir, basename(name));
105
+ await writeFile(filePath, bytes);
106
+ return { filePath, size: bytes.length };
107
+ }
66
108
  /** 统一 HTTP 请求 */
67
109
  async request(method, path, body, params, retried = false) {
68
110
  const url = new URL(path, this.baseUrl);
@@ -1,9 +1,6 @@
1
1
  import { JACKET_STYLE_GROUPS, PANTS_STYLE_GROUPS } from './template.js';
2
2
  /** 拼字段示例 */
3
3
  function buildExample(item) {
4
- if (item.control === 'fixed') {
5
- return JSON.stringify({ input: item.fixedText || '' });
6
- }
7
4
  if (item.control === 'radio') {
8
5
  const first = item.options?.[0] || '选项';
9
6
  if (item.alwaysInput || item.inputWhen?.includes(first)) {
@@ -35,7 +32,7 @@ function buildExample(item) {
35
32
  return JSON.stringify({ checkbox: item.options?.slice(0, 2) || [] });
36
33
  }
37
34
  if (item.control === 'input') {
38
- return JSON.stringify({ input: '填写内容' });
35
+ return JSON.stringify({ input: item.defaultInput || '填写内容' });
39
36
  }
40
37
  if (item.control === 'combine' && item.multiFill) {
41
38
  const map = {};
@@ -60,9 +57,6 @@ function buildExample(item) {
60
57
  }
61
58
  /** 拼选择说明 */
62
59
  function buildHowTo(item) {
63
- if (item.control === 'fixed') {
64
- return `固定文案:${item.fixedText || ''}`;
65
- }
66
60
  if (item.control === 'radio') {
67
61
  let text = `单选其一:${(item.options || []).join('/')}`;
68
62
  if (item.allowAdd) {
@@ -114,9 +108,11 @@ function buildHowTo(item) {
114
108
  return text;
115
109
  }
116
110
  if (item.control === 'input') {
117
- return item.multiline
118
- ? `文本域自由填写${item.inputSuffix ? ',后缀 ' + item.inputSuffix : ''}`
119
- : `输入框填写${item.inputSuffix ? ',后缀 ' + item.inputSuffix : ''}`;
111
+ if (item.multiline) {
112
+ return `文本域自由填写${item.inputSuffix ? ',后缀 ' + item.inputSuffix : ''}`;
113
+ }
114
+ const suffix = item.inputSuffix ? `,后缀 ${item.inputSuffix}` : '';
115
+ return `输入框填写${suffix}${item.defaultInput ? `;默认「${item.defaultInput}」` : ''}`;
120
116
  }
121
117
  if (item.control === 'combine' && item.multiFill) {
122
118
  const parts = (item.combineOptions || []).map((o) => `${o.label}${o.needValue ? '(旁填值' + (o.suffix || '') + ')' : ''}`);
@@ -158,11 +154,7 @@ function flattenGroups(garmentPart, groups) {
158
154
  syncSizeOptions: item.syncSizeOptions,
159
155
  inputSuffix: item.inputSuffix,
160
156
  multiline: item.multiline,
161
- noteWhen: item.key === 'creaseHas'
162
- ? 'radio=有 → 填 note'
163
- : item.key === 'spareButton'
164
- ? 'radio=自己写 → 填 note'
165
- : undefined,
157
+ defaultInput: item.defaultInput,
166
158
  fieldValueExample: buildExample(item),
167
159
  howToSelect: buildHowTo(item)
168
160
  });
@@ -170,19 +162,44 @@ function flattenGroups(garmentPart, groups) {
170
162
  }
171
163
  return list;
172
164
  }
165
+ /** 色卡白名单字段(与后端 ErpColorCardCatalog 一致) */
166
+ const COLOR_CARD_FIELDS = [
167
+ { garmentPart: 'jacket', fieldKey: 'lining', titleCn: '里布' },
168
+ { garmentPart: 'jacket', fieldKey: 'handkerchiefShow', titleCn: '手巾袋露出' },
169
+ { garmentPart: 'jacket', fieldKey: 'fuse', titleCn: '粘衬' },
170
+ { garmentPart: 'jacket', fieldKey: 'sleeveButton', titleCn: '袖扣' },
171
+ { garmentPart: 'jacket', fieldKey: 'buttons', titleCn: '门襟大扣' },
172
+ { garmentPart: 'jacket', fieldKey: 'sleeveLining', titleCn: '袖里' },
173
+ { garmentPart: 'jacket', fieldKey: 'collarHang', titleCn: '领吊' },
174
+ { garmentPart: 'jacket', fieldKey: 'collarBottom', titleCn: '领底' },
175
+ { garmentPart: 'jacket', fieldKey: 'triangle', titleCn: '三角' },
176
+ { garmentPart: 'jacket', fieldKey: 'weltClimb', titleCn: '嵌线袋小攀' },
177
+ { garmentPart: 'jacket', fieldKey: 'innerPatch', titleCn: '里贴袋' },
178
+ { garmentPart: 'jacket', fieldKey: 'pocketCloth', titleCn: '上衣口袋布' },
179
+ { garmentPart: 'pants', fieldKey: 'crotchCloth', titleCn: '小裆布' },
180
+ { garmentPart: 'pants', fieldKey: 'liningHas', titleCn: '脚绸' },
181
+ { garmentPart: 'pants', fieldKey: 'backPocketStitch', titleCn: '后袋' },
182
+ { garmentPart: 'pants', fieldKey: 'backSeam', titleCn: '裤后缝' },
183
+ { garmentPart: 'pants', fieldKey: 'waistInnerStrip', titleCn: '腰里嵌条' },
184
+ { garmentPart: 'pants', fieldKey: 'waistInnerMaterial', titleCn: '腰里材质' },
185
+ { garmentPart: 'pants', fieldKey: 'pantsPocketCloth', titleCn: '裤子口袋布' },
186
+ { garmentPart: 'pants', fieldKey: 'button', titleCn: '裤子纽扣' },
187
+ { garmentPart: 'pants', fieldKey: 'hemTape', titleCn: '脚口织带' }
188
+ ];
173
189
  /** 工艺选择总指南 */
174
190
  export const CRAFT_SELECTION_GUIDE = {
175
191
  summary: '写工艺前先读本指南。fieldKey=指南里的 fieldKey;fieldValue=JSON 字符串,结构见各字段 fieldValueExample;craftModule=模块 key',
176
192
  controls: {
177
- radio: '单选 → {"radio":"选项"};特例 creaseHas/spareButton 可能再带 note',
193
+ radio: '单选 → {"radio":"选项"};howToSelect 提示可填备注时再带 note',
178
194
  checkbox: '多选 → {"checkbox":["a","b"]}',
179
195
  'checkbox+perItemSize': '多选+尺寸 → {"checkbox":["领"],"combineMap":{"领":"0.15"}}',
180
196
  'checkbox+gateNone': '先有无再多选 → 无:{"radio":"无","checkbox":["无"]};有:{"radio":"有","checkbox":["领"],"combineMap":{"领":"0.15"}}',
181
- combine: '结合单选 → {"combineKey":"三折光","combineValue":"3"};needValue=false 可不带 combineValue',
197
+ combine: '结合单选 → {"combineKey":"净脚口三折光压线","combineValue":"3"};needValue=false 可不带 combineValue',
182
198
  'combine+multiFill': '多填 → {"combineMap":{"28L*":"2","24L*":"1"}}',
183
- input: '输入 → {"input":"文案"}',
184
- fixed: '固定文案,一般可不传'
199
+ input: '输入 → {"input":"文案"};各模块末尾 xxNote 备注、人工填写项同此'
185
200
  },
201
+ colorCardFields: COLOR_CARD_FIELDS,
202
+ colorCardNote: '色卡选料写在 style_save colors[].colorCardJson(JSON 字符串,键=上面 fieldKey,值={materialCode,materialName,titleCn};pantsPocketCloth 列仅裤子前后袋工艺有值时可填)',
186
203
  jacket: flattenGroups('jacket', JACKET_STYLE_GROUPS),
187
204
  pants: flattenGroups('pants', PANTS_STYLE_GROUPS)
188
205
  };
@@ -31,6 +31,7 @@ const MARK_ITEMS = [
31
31
  { label: '无', needValue: false },
32
32
  ],
33
33
  },
34
+ { key: 'markNote', label: '备注', control: 'input' },
34
35
  ];
35
36
  /** 印花组字段 */
36
37
  const PRINT_ITEMS = [
@@ -41,9 +42,10 @@ const PRINT_ITEMS = [
41
42
  options: ['有', '无'],
42
43
  },
43
44
  { key: 'printDetail', label: '印花位置及颜色', control: 'input' },
45
+ { key: 'printNote', label: '备注', control: 'input' },
44
46
  ];
45
47
  /** 制边部位 */
46
- const EDGE_PARTS = ['领', '驳', '挂面止口', '手巾袋上', '袖口', '下摆', '开叉'];
48
+ const EDGE_PARTS = ['领', '驳', '挂面止口', '手巾袋上', '袋盖', '袖口', '下摆', '开叉'];
47
49
  /** 裤子工艺字段 */
48
50
  export const PANTS_STYLE_GROUPS = [
49
51
  {
@@ -132,10 +134,14 @@ export const PANTS_STYLE_GROUPS = [
132
134
  control: 'combine',
133
135
  combineOptions: [
134
136
  {
135
- label: '三折光',
137
+ label: '净脚口三折光压线',
138
+ needValue: true,
139
+ suffix: 'CM',
140
+ },
141
+ {
142
+ label: '净脚口内折撬边',
136
143
  needValue: true,
137
144
  suffix: 'CM',
138
- textPrefix: '脚口',
139
145
  },
140
146
  {
141
147
  label: '内撬',
@@ -166,6 +172,7 @@ export const PANTS_STYLE_GROUPS = [
166
172
  alwaysInput: true,
167
173
  inputPlaceholder: '特殊要求',
168
174
  },
175
+ { key: 'liningNote', label: '备注', control: 'input' },
169
176
  ],
170
177
  },
171
178
  {
@@ -192,8 +199,8 @@ export const PANTS_STYLE_GROUPS = [
192
199
  label: '后袋',
193
200
  control: 'combine',
194
201
  combineOptions: [
195
- { label: '拷边', needValue: false },
196
- { label: '折光', needValue: false },
202
+ { label: '拷边', needValue: true, suffix: '' },
203
+ { label: '折光', needValue: true, suffix: '' },
197
204
  { label: '包边', needValue: true, suffix: '' },
198
205
  ],
199
206
  },
@@ -214,6 +221,7 @@ export const PANTS_STYLE_GROUPS = [
214
221
  { label: '宽度', needValue: true, suffix: '' },
215
222
  ],
216
223
  },
224
+ { key: 'topstitchNote', label: '备注', control: 'input' },
217
225
  ],
218
226
  },
219
227
  {
@@ -237,6 +245,17 @@ export const PANTS_STYLE_GROUPS = [
237
245
  inputWhen: ['其他'],
238
246
  inputPlaceholder: '材质',
239
247
  },
248
+ {
249
+ key: 'beadEdge',
250
+ label: '珠边',
251
+ control: 'combine',
252
+ textPrefix: '珠边',
253
+ combineOptions: [
254
+ { label: '有', needValue: true, suffix: '', hideLabel: true },
255
+ { label: '无', needValue: false },
256
+ ],
257
+ },
258
+ { key: 'waistInnerNote', label: '备注', control: 'input' },
240
259
  ],
241
260
  },
242
261
  {
@@ -265,26 +284,7 @@ export const PANTS_STYLE_GROUPS = [
265
284
  control: 'radio',
266
285
  options: ['卷边连腰', '卷边不连腰', '拷边'],
267
286
  },
268
- {
269
- key: 'clothMaterial',
270
- label: '袋布材质',
271
- control: 'radio',
272
- options: ['80棉20涤', '65棉35涤', '100涤', '100棉', '面料', '其他'],
273
- inputWhen: ['其他'],
274
- inputPlaceholder: '材质',
275
- },
276
- {
277
- key: 'clothTexture',
278
- label: '袋布纹理',
279
- control: 'radio',
280
- options: ['斜纹', '平纹', '人字纹', '网眼', '无'],
281
- },
282
- {
283
- key: 'clothColor',
284
- label: '袋布颜色',
285
- control: 'radio',
286
- options: ['配色', '黑', '藏青', '本白', '漂白'],
287
- },
287
+ { key: 'pocketFacingNote', label: '备注', control: 'input' },
288
288
  ],
289
289
  },
290
290
  {
@@ -329,8 +329,11 @@ export const PANTS_STYLE_GROUPS = [
329
329
  key: 'bartackColor',
330
330
  label: '配色',
331
331
  control: 'radio',
332
- options: ['套结配面料色'],
332
+ options: ['套结配面料色', '其他'],
333
+ inputWhen: ['其他'],
334
+ inputPlaceholder: '配色',
333
335
  },
336
+ { key: 'bartackNote', label: '备注', control: 'input' },
334
337
  ],
335
338
  },
336
339
  { key: 'mark', label: '标识', items: MARK_ITEMS },
@@ -341,7 +344,7 @@ export const PANTS_STYLE_GROUPS = [
341
344
  items: [
342
345
  {
343
346
  key: 'button',
344
- label: '纽扣',
347
+ label: '裤子纽扣',
345
348
  control: 'combine',
346
349
  multiFill: true,
347
350
  combineOptions: [
@@ -400,6 +403,7 @@ export const PANTS_STYLE_GROUPS = [
400
403
  { label: '无', needValue: false },
401
404
  ],
402
405
  },
406
+ { key: 'accessoryNote', label: '备注', control: 'input' },
403
407
  ],
404
408
  },
405
409
  {
@@ -413,6 +417,7 @@ export const PANTS_STYLE_GROUPS = [
413
417
  textPrefix: '挺缝线',
414
418
  options: ['有', '无'],
415
419
  },
420
+ { key: 'creaseNote', label: '备注', control: 'input' },
416
421
  ],
417
422
  },
418
423
  {
@@ -466,6 +471,7 @@ export const PANTS_STYLE_GROUPS = [
466
471
  control: 'radio',
467
472
  options: ['挂装', '平箱有裤架', '平箱无裤架'],
468
473
  },
474
+ { key: 'packNote', label: '备注', control: 'input' },
469
475
  ],
470
476
  },
471
477
  ];
@@ -526,9 +532,9 @@ export const JACKET_STYLE_GROUPS = [
526
532
  options: [
527
533
  '贴袋',
528
534
  '平袋有袋盖',
529
- '平袋双嵌线袋',
535
+ '平袋双嵌线袋有袋盖',
530
536
  '斜袋有袋盖',
531
- '斜袋双嵌线袋',
537
+ '斜袋双嵌线袋有袋盖',
532
538
  ],
533
539
  },
534
540
  {
@@ -604,6 +610,7 @@ export const JACKET_STYLE_GROUPS = [
604
610
  inputWhen: ['备注'],
605
611
  inputPlaceholder: '备注',
606
612
  },
613
+ { key: 'fuseNote', label: '备注', control: 'input' },
607
614
  ],
608
615
  },
609
616
  {
@@ -660,6 +667,7 @@ export const JACKET_STYLE_GROUPS = [
660
667
  { label: '无', needValue: false },
661
668
  ],
662
669
  },
670
+ { key: 'edgeNote', label: '备注', control: 'input' },
663
671
  ],
664
672
  },
665
673
  {
@@ -676,9 +684,9 @@ export const JACKET_STYLE_GROUPS = [
676
684
  },
677
685
  {
678
686
  key: 'sleeveButton',
679
- label: '纽扣',
687
+ label: '袖扣',
680
688
  control: 'combine',
681
- textPrefix: '纽扣',
689
+ textPrefix: '袖扣',
682
690
  combineOptions: [
683
691
  { label: '叠扣', needValue: true, suffix: '' },
684
692
  { label: '平扣', needValue: true, suffix: '' },
@@ -686,12 +694,12 @@ export const JACKET_STYLE_GROUPS = [
686
694
  },
687
695
  {
688
696
  key: 'sleeveButtonCount',
689
- label: '每个袖口纽扣数',
697
+ label: '每个袖口袖扣数',
690
698
  control: 'input',
691
699
  },
692
700
  {
693
701
  key: 'buttons',
694
- label: '扣子',
702
+ label: '门襟大扣',
695
703
  control: 'combine',
696
704
  multiFill: true,
697
705
  combineOptions: [
@@ -710,9 +718,9 @@ export const JACKET_STYLE_GROUPS = [
710
718
  {
711
719
  key: 'spare',
712
720
  label: '备扣',
713
- control: 'fixed',
721
+ control: 'input',
714
722
  textPrefix: '备扣',
715
- fixedText: '一大一小',
723
+ defaultInput: '一大一小',
716
724
  },
717
725
  {
718
726
  key: 'thread',
@@ -726,12 +734,14 @@ export const JACKET_STYLE_GROUPS = [
726
734
  {
727
735
  key: 'sleeveLining',
728
736
  label: '袖里',
729
- control: 'radio',
737
+ control: 'combine',
730
738
  textPrefix: '袖里',
731
- options: ['普通白底黑条', 'PLN白底细黑条', '配面料色', '其他'],
732
- inputWhen: ['其他'],
733
- inputPlaceholder: '袖里',
739
+ combineOptions: [
740
+ { label: '有', needValue: true, suffix: '', hideLabel: true },
741
+ { label: '无', needValue: false },
742
+ ],
734
743
  },
744
+ { key: 'sleeveNote', label: '备注', control: 'input' },
735
745
  ],
736
746
  },
737
747
  {
@@ -747,6 +757,7 @@ export const JACKET_STYLE_GROUPS = [
747
757
  { label: '里布', needValue: true, suffix: '' },
748
758
  { label: '丝带', needValue: true, suffix: '' },
749
759
  { label: '领标', needValue: true, suffix: '' },
760
+ { label: '其他', needValue: true, suffix: '' },
750
761
  ],
751
762
  },
752
763
  {
@@ -759,6 +770,7 @@ export const JACKET_STYLE_GROUPS = [
759
770
  { label: '特殊材料', needValue: true, suffix: '' },
760
771
  ],
761
772
  },
773
+ { key: 'collarNote', label: '备注', control: 'input' },
762
774
  ],
763
775
  },
764
776
  {
@@ -804,13 +816,14 @@ export const JACKET_STYLE_GROUPS = [
804
816
  },
805
817
  {
806
818
  key: 'pocketCloth',
807
- label: '口袋布',
819
+ label: '上衣口袋布',
808
820
  control: 'combine',
809
821
  combineOptions: [
810
822
  { label: '有', needValue: true, suffix: '', hideLabel: true },
811
823
  { label: '无', needValue: false },
812
824
  ],
813
825
  },
826
+ { key: 'innerNote', label: '备注', control: 'input' },
814
827
  ],
815
828
  },
816
829
  {
@@ -823,9 +836,9 @@ export const JACKET_STYLE_GROUPS = [
823
836
  control: 'combine',
824
837
  textPrefix: '大袋',
825
838
  combineOptions: [
826
- { label: '直套结', needValue: false },
827
- { label: 'D套结', needValue: false },
828
- { label: '横套结0.6CM', needValue: false },
839
+ { label: '直套结', needValue: true, suffix: '' },
840
+ { label: 'D套结', needValue: true, suffix: '' },
841
+ { label: '横套结0.6CM', needValue: true, suffix: '' },
829
842
  { label: '无', needValue: false },
830
843
  ],
831
844
  },
@@ -835,11 +848,12 @@ export const JACKET_STYLE_GROUPS = [
835
848
  control: 'combine',
836
849
  textPrefix: '里袋',
837
850
  combineOptions: [
838
- { label: '直套结', needValue: false },
839
- { label: 'D套结', needValue: false },
851
+ { label: '直套结', needValue: true, suffix: '' },
852
+ { label: 'D套结', needValue: true, suffix: '' },
840
853
  { label: '无', needValue: false },
841
854
  ],
842
855
  },
856
+ { key: 'bartackNote', label: '备注', control: 'input' },
843
857
  ],
844
858
  },
845
859
  { key: 'mark', label: '标识', items: MARK_ITEMS },
@@ -847,7 +861,10 @@ export const JACKET_STYLE_GROUPS = [
847
861
  {
848
862
  key: 'handmade',
849
863
  label: '特殊手工',
850
- items: [{ key: 'handmade', label: '特殊手工', control: 'input' }],
864
+ items: [
865
+ { key: 'handmade', label: '特殊手工', control: 'input' },
866
+ { key: 'handmadeNote', label: '备注', control: 'input' },
867
+ ],
851
868
  },
852
869
  {
853
870
  key: 'pack',
@@ -899,6 +916,7 @@ export const JACKET_STYLE_GROUPS = [
899
916
  control: 'radio',
900
917
  options: ['挂装', '平箱有衣架', '平箱无衣架', '立体箱'],
901
918
  },
919
+ { key: 'packNote', label: '备注', control: 'input' },
902
920
  ],
903
921
  },
904
922
  ];
package/dist/tool-meta.js CHANGED
@@ -63,7 +63,7 @@ export const ENTITY_META = {
63
63
  addOptional: 'country, defaultCurrency, communicationChannel, email, phone, taxNo, shortCode, assigneeId, status, remark',
64
64
  editRequired: 'id, customerName, customerType',
65
65
  enums: 'customerType:direct/trader/foreign_trade; status:active/inactive(默认active);customerCode 后端自动生成勿传;taxNo/shortCode 选手填',
66
- removeConstraint: '客户存在关联PI时不可删除',
66
+ removeConstraint: '客户下 PI 关联的订单仍存在时不可删除;关联订单已删除的 PI 会随客户一起清理',
67
67
  next: 'erp_customer_contact_add → erp_customer_address_add → erp_pi_add'
68
68
  },
69
69
  erp_customer_contact: {
@@ -81,7 +81,7 @@ export const ENTITY_META = {
81
81
  label: '客户地址',
82
82
  prerequisite: '客户已创建',
83
83
  addRequired: 'customerId, addressType, detailAddress',
84
- addOptional: 'recipient, company, country, province, city, postalCode, phone, isDefault(Y/N), remark',
84
+ addOptional: 'recipient, company, country, province, city, postalCode, phone, isDefault(Y/N), effectiveDate, remark',
85
85
  editRequired: 'id, customerId, addressType, detailAddress',
86
86
  enums: 'addressType:shipping/billing/sample'
87
87
  },
@@ -101,9 +101,9 @@ export const ENTITY_META = {
101
101
  label: 'PI',
102
102
  prerequisite: '客户已创建',
103
103
  addRequired: 'customerId, sourceType, piNo',
104
- addOptional: 'orderId, piVersion, paymentTerms, totalAmount, currency, qtyTolerance, shippingMethod, shippingAddress, piFileOssId, status, confirmedDate, remark',
104
+ addOptional: 'orderId, piVersion, paymentTerms, totalAmount, currency, qtyTolerance, shippingMethod, packingType, shipmentPeriod, shippingAddress, piFileOssId, status, confirmedDate, remark, items',
105
105
  editRequired: 'id, customerId, sourceType, piNo',
106
- enums: 'sourceType:customer/internal; status:draft/confirmed/superseded(默认draft);piNo 手填可改,同号同版本重复报PI编号已存在;传 orderId 则关联订单(客户须一致)',
106
+ enums: 'sourceType:customer/internal; status:draft/confirmed/superseded(默认draft);piNo 手填可改,同号同版本重复报PI编号已存在;传 orderId 则关联订单(客户须一致);items 为明细行 [{rowKey, code, style, fabric, col, composition, subtotalQty, fobNingbo, subtotalAmount}],包装方式 packingType 保存时同步订单',
107
107
  next: 'erp_order_add → erp_order_link_pi(若 add 已带 orderId 可跳过 link)'
108
108
  },
109
109
  erp_order: {
@@ -115,6 +115,7 @@ export const ENTITY_META = {
115
115
  addOptional: 'brandId, orderStatus(默认inquiry), orderDate, deliveryDate, contractDate, depositReceived(Y/N), qtyTolerance, shippingMethod, packingType, priority, sourceChannel, parentOrderId, remark',
116
116
  editRequired: 'id, orderNo, customerId, bizLine, assigneeId',
117
117
  enums: 'bizLine:direct/trader; orderStatus见erp_business_flow资源;orderNo 手填全库唯一可改;重复报订单号已存在',
118
+ removeConstraint: '删除会级联清该单 PI、款式树(含工艺/商标位置/色卡)和生产单,订单号改为 _原ID 释放号码',
118
119
  next: 'erp_order_link_pi → erp_order_style_save → erp_order_change_status'
119
120
  },
120
121
  erp_task: {
@@ -133,8 +134,8 @@ export const ENTITY_META = {
133
134
  addRequired: 'categoryId, materialCode, materialName',
134
135
  addOptional: 'spec, color, unit, defaultSupplierId, composition, width, weight, imageOssId, status, remark',
135
136
  editRequired: 'id, categoryId, materialName',
136
- enums: 'materialCode 新增手填必传、唯一,修改不可改;分类 fabric:materialCode=面料号且 color(色号)必填;分类 lining:composition(成分)必填',
137
- removeConstraint: '已被订单引用(款式面料色号行/colorCardJson 色卡料/工艺 materialCode)、生产单引用或有比价快照时不可删,可改为停用',
137
+ enums: 'materialCode 新增手填必传、唯一,修改不可改;分类 fabric:materialCode=面料号且 color(色号)必填;分类 lining:composition(成分)必填;编码命中已删除记录时 add 直接恢复并更新原记录,不报重复',
138
+ removeConstraint: '已被订单引用(款式面料色号行/colorCardJson 色卡料/工艺 materialCode)、生产单引用或有比价快照时不可删,可改为停用;删前用 material.bound_orders 查引用',
138
139
  next: '面料 → style_save colors[].fabricMaterialId(colorCode 取物料 color);色卡料 → colors[].colorCardJson'
139
140
  },
140
141
  erp_material_category: {
@@ -198,10 +199,10 @@ export const ENTITY_META = {
198
199
  label: '生产单',
199
200
  prerequisite: '订单已存在',
200
201
  addRequired: 'orderId, bizLine',
201
- addOptional: 'poVersion, poStatus(默认draft), deliveryDate, qtyTolerance, shippingMethod, shippingAddress, packingType, bulkSampleRequired(Y/N), remark',
202
+ addOptional: 'poVersion, poStatus(默认draft), deliveryDate, qtyTolerance, shippingMethod, shippingAddress, packingType, bulkSampleRequired(Y/N), bulkSampleDeadline, remark',
202
203
  editRequired: 'id, orderId, bizLine',
203
204
  enums: 'poStatus:draft/reviewing/issued/superseded;poNo 后端自动生成勿传',
204
- removeConstraint: '已下发(issued)不可删除;仅draft可修改',
205
+ removeConstraint: '已下发(issued)且订单仍存在时不可删除;订单删除会连带清生产单;仅 draft 可修改',
205
206
  next: 'erp_production craft_save(可选)→ check → submit_review → issue;辅料工艺只读展示款式配置已选面料与色卡,一般不必 save_items'
206
207
  },
207
208
  erp_bid: {
@@ -258,7 +259,7 @@ export const WORKFLOW_META = {
258
259
  step: '录入款式明细',
259
260
  summary: '保存订单款式及颜色尺码/色卡物料,成功后回查 styles(含 id 作 styleId);页面配置确定即调本接口',
260
261
  prerequisite: '订单已创建;payload 内 orderId 用字符串',
261
- payload: '{orderId:string, styles:[{id?, styleNo?, styleName?, sampleNo?, shipSampleTip?, materialUsage?, garmentType?, fabricMaterialId?, meterage?, colors:[{fabricMaterialId, colorCode, colorName, colorCardJson?, sizes:[{sizeName, qty}]}]}]};styleNo 可省略则默认订单号-序号;全库唯一;colors[].colorName 必填',
262
+ payload: '{orderId:string, styles:[{id?, styleNo?, styleName?, sampleNo?, shipSampleTip?, garmentType?, customerStyleRef?, materialUsage?, fabricMaterialId?, liningMaterialId?, styleImageOssId?, techFileOssId?, sizeChartJson?, remark?, colors:[{id?, fabricMaterialId, colorCode, colorName, customerStyleRef?, colorCardJson?, meterage?, materialUsage?, cuttableWidth?, totalQty?, remark?, sizes:[{sizeName, qty}]}]}]};styleNo 可省略则默认订单号-序号;全库唯一;colors[].colorName 必填',
262
263
  constraint: '全量 styles 列表;已有 styles[].id 或同款号则更新主档并替换颜色尺码,工艺/商标保留,sizeChartJson/样板号/船样提示空则沿用库中值;无 id 为新增;未出现在列表中的旧款整树删除(含工艺);款号重复报款号已存在;配置:每行 colors[] 各自 material.list/add 后面料 fabricMaterialId,colorCode 取该物料 color(色号),colorName=物料 materialName,勿手填独立色号、勿传 fabricColorNo;款式级 fabricMaterialId 取首行面料;色卡选料按行写入 colorCardJson(JSON 字符串,键=fieldKey 如 lining,值={materialCode,materialName,titleCn});页脚备注走 color_card_save,不在本接口',
263
264
  next: '从返回 styles[].id 取 styleId → craft_save;色卡备注 → color_card_save'
264
265
  },
@@ -268,7 +269,7 @@ export const WORKFLOW_META = {
268
269
  summary: '按 styleId 写 colorCardNote;与前端色卡弹窗一致',
269
270
  prerequisite: '款式已保存;选料已在 style_save.colorCardJson',
270
271
  payload: '{styleId:string, colorCardNote?, items?:[{garmentPart, fieldKey, colorCardCode?, materialCode?}]}',
271
- constraint: '前端常规只传 colorCardNote 且 items:[];选料不在本接口。items 写 craft 编号为遗留可选,一般勿填;PDF 导出无 MCP,走前端',
272
+ constraint: '前端常规只传 colorCardNote 且 items:[];选料不在本接口。items 写 craft 编号为遗留可选,一般勿填;色卡导出用 action=color_card_export',
272
273
  next: 'erp_order action=change_status(如 color_card_confirmed)'
273
274
  },
274
275
  erp_bid_close: {
@@ -302,9 +303,9 @@ export const WORKFLOW_META = {
302
303
  step: '录入结构化工艺',
303
304
  summary: '保存款式上衣/裤子工艺及商标位置(选项对齐打样单模板,非字典)',
304
305
  prerequisite: '款式已保存且有 styleId;先读 erp_order_craft_schema 取 fieldKey',
305
- payload: '{styleId, processNote?, crafts:[{garmentPart, craftModule, fieldKey, fieldValue}], labelPositions?:[...]}。键名必须是 crafts(复数)与 craftModule;每条带 garmentPart;fieldValue 为 JSON 字符串,旁框用 note 不是 input',
306
+ payload: '{styleId, processNote?, crafts:[{garmentPart, craftModule, fieldKey, fieldValue}], labelPositions?:[{garmentPart, labelType, positionSide?, sizeCm?, positionDesc?, materialId?, diagramOssId?, remark?, sortOrder?}]}。键名必须是 crafts(复数)与 craftModule;每条带 garmentPart;fieldValue 为 JSON 字符串,旁框用 note 不是 input',
306
307
  enums: 'garmentPart:jacket/pants; craftModule/fieldKey 见 schema;labelType/positionSide 仍用字典 erp_label_*',
307
- constraint: '全量替换:按 styleId 先删该款全部工艺再插入。jacket+pants 必须放进同一次 crafts;分部位连调会互相覆盖。返回 data=写入条数。清空传 crafts:[];processNote 为工艺备注(订单页会传);色卡选料不在本接口,走 style_save colors[].colorCardJson',
308
+ constraint: '全量替换:按 styleId 先删该款全部工艺和商标位置再插入。jacket+pants 必须放进同一次 crafts;分部位连调会互相覆盖。已有商标位置时必须一并回传 labelPositions,漏传会清空。返回 data=写入条数。清空传 crafts:[];processNote 为工艺备注(订单页会传);色卡列 fieldValue 里的 colorCardCode/materialCode 由后端沿用旧值,勿手填;商标图片来自物料:materialId 选商标物料,diagramOssId 取该物料 imageOssId;色卡选料不在本接口,走 style_save colors[].colorCardJson',
308
309
  next: 'erp_order action=craft_get 校验 → erp_order_change_status 推进后再 erp_production_create_from_order'
309
310
  },
310
311
  erp_order_style_size_chart_save: {
@@ -322,7 +323,7 @@ export const WORKFLOW_META = {
322
323
  summary: '生产单草稿阶段保存款式工艺(同订单工艺模板与 JSON 存值)',
323
324
  prerequisite: '生产单 poStatus=draft,款式已存在',
324
325
  payload: '同订单 craft_save 的 crafts/craftModule/labelPositions;生产页通常不传 processNote',
325
- constraint: '同订单 craft_save:全量替换,返回写入条数',
326
+ constraint: '同订单 craft_save:全量替换(含商标位置,漏传 labelPositions 会清空),返回写入条数',
326
327
  next: 'erp_production action=craft_get 校验 → erp_production_check'
327
328
  },
328
329
  erp_oss_upload: {
@@ -479,6 +480,10 @@ export const READ_META = {
479
480
  usage: '返回跟单员角色用户列表;建客户/订单或 order.assign 前取 assigneeId(字符串)',
480
481
  returns: '[{userId, userName, nickName}]'
481
482
  },
483
+ erp_user_options: {
484
+ usage: '返回全部用户;任务负责人等非跟单场景取 userId(字符串)',
485
+ returns: '[{userId, userName, nickName}]'
486
+ },
482
487
  erp_customer_list: {
483
488
  usage: '筛选客户类型 direct/trader/foreign_trade,状态 active/inactive'
484
489
  },
@@ -487,12 +492,19 @@ export const READ_META = {
487
492
  returns: '客户基本信息'
488
493
  },
489
494
  erp_customer_contact_list: { usage: '查某客户下全部联系人' },
495
+ erp_customer_contact_get: { usage: '按 ID 查单个联系人' },
490
496
  erp_customer_address_list: { usage: '查某客户地址,可按 shipping/billing/sample 筛选' },
497
+ erp_customer_address_get: { usage: '按 ID 查单个地址' },
491
498
  erp_customer_brand_list: { usage: '查某客户品牌列表,建订单时 brandId 来源' },
499
+ erp_customer_brand_get: { usage: '按 ID 查单个品牌' },
492
500
  erp_pi_list: { usage: 'PI状态 draft/confirmed/superseded,可按 customerId 筛选' },
493
501
  erp_pi_get: { usage: '查PI详情含金额、运输、发货地址' },
502
+ erp_pi_export: {
503
+ usage: '按 PI ID 导出形式发票 Excel 到本机',
504
+ returns: '{filePath, size},filePath 为本机绝对路径'
505
+ },
494
506
  erp_order_list: {
495
- usage: '订单状态见 erp_business_flow;可按 orderStatus/assigneeId/customerId 筛选'
507
+ usage: '订单状态见 erp_business_flow;可按 orderStatus/assigneeId/customerId/brandId/bizLine 筛选'
496
508
  },
497
509
  erp_order_get: { usage: '查订单主档', returns: '订单基本信息' },
498
510
  erp_order_style_list: {
@@ -508,6 +520,18 @@ export const READ_META = {
508
520
  usage: '按 styleId 读色卡工艺项清单(前端色卡弹窗实际用 style.colorCardJson 展示,本接口可选);写备注用 color_card_save',
509
521
  returns: '[{garmentPart, fieldKey, titleCn, colorCardCode, materialCode, materialName, sort}]'
510
522
  },
523
+ erp_order_style_craft_export: {
524
+ usage: '单款导出工艺单 xlsx 到本机;默认走 preview-excel 不写附件,archive=true 才走 export-excel 写订单附件',
525
+ returns: '{filePath, size},filePath 为本机绝对路径'
526
+ },
527
+ erp_order_style_color_card_export: {
528
+ usage: '单款导出色卡 Excel 到本机;默认走 color-card/preview 不写附件,archive=true 才走 color-card/export 写订单附件',
529
+ returns: '{filePath, size},filePath 为本机绝对路径'
530
+ },
531
+ erp_order_attachment_export: {
532
+ usage: '按 ossId 下载订单附件到本机(含工艺单/色卡/尺寸表等全部附件);ossId 从 attachment_list 取',
533
+ returns: '{filePath, size},filePath 为本机绝对路径'
534
+ },
511
535
  erp_production_craft_schema: {
512
536
  usage: '同 erp_order_craft_schema'
513
537
  },
@@ -525,13 +549,22 @@ export const READ_META = {
525
549
  erp_task_get: { usage: '查任务详情' },
526
550
  erp_material_category_list: { usage: '查物料分类树,新建物料前先查 categoryId' },
527
551
  erp_material_list: {
528
- usage: '分页查物料;款式配置选面料/色卡前先查;可按分类/编码/名称筛选'
552
+ usage: '分页查物料;款式配置选面料/色卡前先查;可按分类/编码/名称/状态筛选'
529
553
  },
530
554
  erp_material_get: { usage: '查物料详情;拿到 id/materialCode/color 再写入款式配置或色卡' },
555
+ erp_material_bound_orders: {
556
+ usage: '查引用该物料的订单(含 orderNo/customerName/styleNo/bindSource),删除或停用前确认',
557
+ returns: '[{orderId, orderNo, customerName, styleNo, styleName, bindSource}]'
558
+ },
531
559
  erp_material_supplier_list: { usage: '查某物料下可议价供应商清单' },
532
560
  erp_material_price_snapshot_list: { usage: '分页查比价快照,可按品名/编码/口径筛选' },
533
561
  erp_material_price_snapshot_get: { usage: '查比价快照详情(含 quotes 明细)' },
534
562
  erp_material_price_quote_list: { usage: '按 snapshotId 分页查明细,可筛匹配度/渠道/风险' },
563
+ erp_material_price_quote_get: { usage: '按 ID 查比价明细单条' },
564
+ erp_material_price_point_series: {
565
+ usage: '查比价历史点趋势;传 snapshotId 即由服务端解析标的,也可传 subjectKey 或 materialId',
566
+ returns: '[{priceDate, unitPrice, ...}]'
567
+ },
535
568
  erp_supplier_list: { usage: '供应商类型 fabric/accessory/both' },
536
569
  erp_supplier_get: { usage: '查供应商详情' },
537
570
  erp_bid_list: { usage: '可按 materialId/bidMode/bidStatus 筛选;状态由开结标时间懒刷新' },
@@ -642,12 +675,13 @@ export const ERP_BUSINESS_FLOW = {
642
675
  other: '其他',
643
676
  size_chart: '尺寸表',
644
677
  production_order: '生产单',
645
- color_card: '色卡'
678
+ color_card: '色卡',
679
+ craft: '工艺单'
646
680
  },
647
681
  sampleType: { proto: '打样', pre_prod: '产前样', bulk: '船样', mass: '大货' },
648
682
  sampleStatus: { pending: '待制作', making: '制作中', shipped: '已寄出', confirmed: '已确认', rejected: '已驳回' },
649
683
  poItemCategory: {
650
- button: '纽扣',
684
+ button: '裤子纽扣',
651
685
  label: '商标',
652
686
  hangtag: '吊牌',
653
687
  zipper: '拉链',
@@ -663,18 +697,23 @@ export const ERP_BUSINESS_FLOW = {
663
697
  combineValue: '结合项附加值(尺寸/备注等)',
664
698
  combineMap: '多填结合或逐项尺寸 Map<label,value>',
665
699
  note: 'radio/combine 旁侧编号或备注(不是 input)',
700
+ colorCardCode: '色卡选料色号,后端按旧值补回,一般勿传',
701
+ materialCode: '色卡选料物料编码,同上',
666
702
  customOptions: '+新增自定义选项 chip(不是编号)'
667
703
  },
668
704
  craftGarmentPart: { jacket: '上衣', pants: '裤子' },
669
705
  craftModuleJacket: {
670
706
  style: '款式',
707
+ fuse: '粘衬',
671
708
  edge: '制边',
672
709
  sleeve: '袖子',
673
710
  collar: '领吊/领底',
674
711
  inner: '夹里',
675
712
  bartack: '套结',
676
- brand: '商标',
677
- mark: '标识'
713
+ mark: '标识',
714
+ print: '印花',
715
+ handmade: '特殊手工',
716
+ pack: '包装'
678
717
  },
679
718
  craftModulePants: {
680
719
  style: '款式',
@@ -682,29 +721,34 @@ export const ERP_BUSINESS_FLOW = {
682
721
  topstitch: '压明线',
683
722
  waistInner: '腰里/珠边',
684
723
  pocketFacing: '前后袋门巾',
685
- pocketCloth: '袋布',
686
724
  bartack: '套结',
687
725
  mark: '标识',
726
+ print: '印花',
688
727
  accessory: '辅料',
689
- crease: '挺缝线'
728
+ crease: '挺缝线',
729
+ pack: '包装'
690
730
  }
691
731
  },
692
732
  constraints: [
693
- '客户存在关联PI时不可删除',
733
+ '客户删除:PI 关联的订单仍在时不可删;关联订单已删的 PI 随客户一起清掉',
734
+ '订单删除:级联清该单 PI、款式树和生产单,订单号改为 _原ID 释放号码可复用',
694
735
  '每订单仅一条生产单(erp_production_create_from_order)',
695
736
  'erp_production_create_from_order 仅 orderStatus=quoted|pre_prod_confirmed|po_drafting 且尚无生产单时可调用;成功后订单强制 orderStatus=po_drafting',
696
737
  '生产单仅draft可修改/编辑明细',
697
738
  '生产单submit_review: draft→reviewing',
698
739
  '生产单reject_review: reviewing→draft(需review权限)',
699
740
  '生产单issue: reviewing→issued(需issue权限),同步订单→po_issued',
700
- '已下发(issued)生产单不可删除',
741
+ '已下发(issued)生产单且订单仍在时不可删除;订单删除会连带清生产单',
701
742
  '工艺选项与怎么选见 erp_order_craft_schema.selectionGuide 或资源 yindie://erp/craft-guide',
702
743
  '工艺选项固定模板(对齐打样单),不用 sys_dict;fieldValue 存 JSON(见 craftValueJson / fieldValueExample)',
703
744
  '工艺 fieldKey/craftModule 以 erp_order_craft_schema.fields 为准,勿再用旧字典码如 single_1',
704
745
  '款式面料色号:material.list/add → style_save colors[].fabricMaterialId,colorCode=物料 color,colorName 必填;勿传 fabricColorNo',
705
- '色卡选料:material.list(category_code=fieldKey)/add → style_save colors[].colorCardJson;色卡备注:color_card_save(常规 items:[]);PDF/工艺 Excel 导出无 MCP,走前端',
706
- '物料删除:有绑定订单/生产单引用/比价快照时不可删',
746
+ '色卡选料:material.list(category_code=fieldKey)/add → style_save colors[].colorCardJson;色卡备注:color_card_save(常规 items:[])',
747
+ 'Excel 导出/预览都是二进制:工艺单/色卡用 erp_order action=craft_export/color_card_export,PI 用 erp_pi action=export,均落本机返回 filePath',
748
+ '物料删除:有绑定订单/生产单引用/比价快照时不可删;删前用 material.bound_orders 查引用',
749
+ '物料新增:编码命中已删除记录时直接恢复原记录并覆盖写入,不报重复',
707
750
  'craft_save 全量替换:同 styleId 的 jacket+pants 必须一次写入;分次调用后一次会删前一次',
751
+ 'craft_save 会连商标位置一起全量替换:已有标位时须回传 labelPositions,漏传即清空',
708
752
  'craft_save 键名 crafts/craftModule;兼容历史 craft/module;旁框用 note;保存后 craft_get 校验',
709
753
  '商标位置 labelType/positionSide 仍用系统字典',
710
754
  '轮次可选:询盘可 inquiry→quoted 跳过打样(返单/大货);记在 erp_order action=sample_*,不强制每单建轮次',
@@ -720,16 +764,16 @@ export const ERP_BUSINESS_FLOW = {
720
764
  /** MCP 服务端内置说明,连接时注入模型上下文 */
721
765
  export const ERP_MCP_INSTRUCTIONS = `银蝶 ERP MCP(域工具 + action 模式)。
722
766
 
723
- 【开场】用户问待办/订单/我是谁 → erp_workbench action=summary 或 current_user;需跟单员 ID → action=merchandiser_options。
767
+ 【开场】用户问待办/订单/我是谁 → erp_workbench action=summary 或 current_user;需跟单员 ID → action=merchandiser_options;任务负责人 → action=user_options。
724
768
  【先读资源】复杂写操作前读 yindie://erp/usage-guide(工具用法)和 yindie://erp/business-flow(状态机/约束);写工艺再读 yindie://erp/craft-guide 或 erp_order action=craft_schema;不确定可用 action 时读 yindie://erp/modules。
725
769
 
726
770
  【调用格式】选域工具(erp_customer/erp_order/erp_material 等),必填 action。有子实体时 action 写 target.action,如 customer.add、material.add、price_snapshot.list、thread.link。写操作用 payload 传 JSON 字符串;删用 ids;单条查改 id。所有雪花 ID 必须字符串。
727
771
 
728
772
  【主链路】customer.add → pi.add → order.add → order.link_pi →(面料 material.list/add)order.style_save(colors.fabricMaterialId/colorCode/colorName/colorCardJson) → order.craft_schema → order.craft_save(processNote?) →(可选)order.color_card_save(备注) →(尺寸表)order.size_chart_save + oss.upload×N + attachment_add×3 → order.change_status → production.create_from_order(订单→po_drafting)→ production.check → production.submit_review → production.issue。
729
773
 
730
- 【工艺】先 craft_schema 拿 fieldKey 与 fieldValueExample,fieldValue 存 JSON 字符串;色卡选料写在 style_save colors[].colorCardJson;色卡页脚备注走 color_card_save(items:[]);商标位置 labelType/positionSide 用系统字典。
774
+ 【工艺】先 craft_schema 拿 fieldKey 与 fieldValueExample,fieldValue 存 JSON 字符串;各模块末尾 xxNote 是备注(control=input,如 packNote)。craft_save 会连商标位置一起全量替换,已有标位须回传 labelPositions(materialId 选商标物料);色卡选料写在 style_save colors[].colorCardJson;色卡页脚备注走 color_card_save(items:[]);商标位置 labelType/positionSide 用系统字典;导出用 craft_export/color_card_export,附件下载用 attachment_export(ossId 取自 attachment_list),均返回本机落盘路径,前两者默认不写附件,archive=true 才归档。
731
775
 
732
- 【物料】新增 material.add(materialCode 手填;fabric 必填 color 色号;lining 必填 composition,前后端均校验);选已有 material.list → 面料色号行填 colors[].fabricMaterialId(colorCode=color,colorName=materialName),色卡选料填 colorCardJson。
776
+ 【物料】新增 material.add(materialCode 手填;fabric 必填 color 色号;lining 必填 composition,前后端均校验;编码命中已删记录会恢复原记录);选已有 material.list → 面料色号行填 colors[].fabricMaterialId(colorCode=color,colorName=materialName),色卡选料填 colorCardJson;删前 material.bound_orders 查引用。
733
777
 
734
778
  【制单】create_from_order(成功后订单强制 po_drafting)→ 补交期等 → check(校验面料/色卡物料)→ submit_review → issue;辅料工艺只读,来自订单 style_save 已选面料与 colorCardJson,一般不调 save_items。
735
779
 
@@ -747,14 +791,14 @@ export const ERP_USAGE_GUIDE = {
747
791
  ],
748
792
  domains: {
749
793
  erp_workbench: {
750
- actions: ['summary', 'current_user', 'merchandiser_options'],
751
- note: '入口,优先 summary;建单/分派前 merchandiser_options 取 assigneeId'
794
+ actions: ['summary', 'current_user', 'merchandiser_options', 'user_options'],
795
+ note: '入口,优先 summary;建单/分派前 merchandiser_options 取 assigneeId;任务负责人用 user_options'
752
796
  },
753
797
  erp_customer: {
754
798
  actions: ['customer.*', 'contact.*', 'address.*', 'brand.*'],
755
- note: 'action 示例 customer.add、contact.list;list/get 用 customerId 等筛选'
799
+ note: 'action 示例 customer.add、contact.list、contact.get;list/get 用 customerId 等筛选'
756
800
  },
757
- erp_pi: { actions: ['list', 'get', 'add', 'edit', 'remove'] },
801
+ erp_pi: { actions: ['list', 'get', 'add', 'edit', 'remove', 'export'] },
758
802
  erp_oss: {
759
803
  actions: ['upload'],
760
804
  note: 'filePath=本机绝对路径;返回 ossId 再 order.attachment_add'
@@ -775,9 +819,11 @@ export const ERP_USAGE_GUIDE = {
775
819
  'craft_schema',
776
820
  'craft_get',
777
821
  'craft_save',
822
+ 'craft_export',
778
823
  'size_chart_save',
779
824
  'color_card_list',
780
825
  'color_card_save',
826
+ 'color_card_export',
781
827
  'timeline_list',
782
828
  'sample_list',
783
829
  'sample_add',
@@ -790,9 +836,10 @@ export const ERP_USAGE_GUIDE = {
790
836
  'shipment_remove',
791
837
  'attachment_list',
792
838
  'attachment_add',
839
+ 'attachment_export',
793
840
  'attachment_remove'
794
841
  ],
795
- note: 'style_save 成功后返回 styles 含 styleId;craft_save 需 styleId'
842
+ note: 'style_save 成功后返回 styles 含 styleId;craft_save 需 styleId;craft_export/color_card_export/attachment_export 返回本机落盘路径,前两者 archive=true 才写订单附件'
796
843
  },
797
844
  erp_production: {
798
845
  actions: [
@@ -816,8 +863,8 @@ export const ERP_USAGE_GUIDE = {
816
863
  },
817
864
  erp_task: { actions: ['list', 'get', 'add', 'edit', 'remove'] },
818
865
  erp_material: {
819
- actions: ['material.*', 'category.*', 'supplier.*', 'price_snapshot.*', 'price_quote.*'],
820
- note: 'action 示例 material.add、category.list、supplier.list、price_snapshot.add;payload 见 payloadByEntity'
866
+ actions: ['material.*', 'category.*', 'supplier.*', 'price_snapshot.*', 'price_quote.*', 'price_point.*'],
867
+ note: 'action 示例 material.add、material.bound_orders、price_quote.get、price_point.series;payload 见 payloadByEntity'
821
868
  },
822
869
  erp_supplier: { actions: ['list', 'get', 'add', 'edit', 'remove'] },
823
870
  erp_bid: {
@@ -872,6 +919,10 @@ export const ERP_USAGE_GUIDE = {
872
919
  }
873
920
  }
874
921
  },
922
+ {
923
+ intent: '导出工艺单/色卡',
924
+ call: { tool: 'erp_order', action: 'craft_export', styleId: '字符串' }
925
+ },
875
926
  {
876
927
  intent: '从订单生成生产单',
877
928
  call: { tool: 'erp_production', action: 'create_from_order', orderId: '字符串' }
@@ -914,13 +965,16 @@ export const ERP_USAGE_GUIDE = {
914
965
  'order.change_status': WORKFLOW_META.erp_order_change_status,
915
966
  'order.assign': WORKFLOW_META.erp_order_assign,
916
967
  'order.link_pi': WORKFLOW_META.erp_order_link_pi,
968
+ 'pi.export': READ_META.erp_pi_export,
917
969
  'order.copy': WORKFLOW_META.erp_order_copy,
918
970
  'order.style_save': WORKFLOW_META.erp_order_style_save,
919
971
  'order.craft_get': READ_META.erp_order_style_craft_list,
920
972
  'order.craft_save': WORKFLOW_META.erp_order_style_craft_save,
973
+ 'order.craft_export': READ_META.erp_order_style_craft_export,
921
974
  'order.size_chart_save': WORKFLOW_META.erp_order_style_size_chart_save,
922
975
  'order.color_card_list': READ_META.erp_order_style_color_card_list,
923
976
  'order.color_card_save': WORKFLOW_META.erp_order_style_color_card_save,
977
+ 'order.color_card_export': READ_META.erp_order_style_color_card_export,
924
978
  'oss.upload': WORKFLOW_META.erp_oss_upload,
925
979
  'order.sample_add': WORKFLOW_META.erp_order_sample_add,
926
980
  'order.sample_edit': WORKFLOW_META.erp_order_sample_edit,
@@ -17,7 +17,8 @@ const DOMAIN_TEMPLATES = [
17
17
  routes: {
18
18
  summary: { oldName: 'erp_workbench_summary' },
19
19
  current_user: { oldName: 'erp_current_user' },
20
- merchandiser_options: { oldName: 'erp_merchandiser_options' }
20
+ merchandiser_options: { oldName: 'erp_merchandiser_options' },
21
+ user_options: { oldName: 'erp_user_options' }
21
22
  }
22
23
  },
23
24
  {
@@ -34,18 +35,21 @@ const DOMAIN_TEMPLATES = [
34
35
  },
35
36
  contact: {
36
37
  list: { oldName: 'erp_customer_contact_list' },
38
+ get: { oldName: 'erp_customer_contact_get' },
37
39
  add: { oldName: 'erp_customer_contact_add' },
38
40
  edit: { oldName: 'erp_customer_contact_edit' },
39
41
  remove: { oldName: 'erp_customer_contact_remove' }
40
42
  },
41
43
  address: {
42
44
  list: { oldName: 'erp_customer_address_list' },
45
+ get: { oldName: 'erp_customer_address_get' },
43
46
  add: { oldName: 'erp_customer_address_add' },
44
47
  edit: { oldName: 'erp_customer_address_edit' },
45
48
  remove: { oldName: 'erp_customer_address_remove' }
46
49
  },
47
50
  brand: {
48
51
  list: { oldName: 'erp_customer_brand_list' },
52
+ get: { oldName: 'erp_customer_brand_get' },
49
53
  add: { oldName: 'erp_customer_brand_add' },
50
54
  edit: { oldName: 'erp_customer_brand_edit' },
51
55
  remove: { oldName: 'erp_customer_brand_remove' }
@@ -61,7 +65,8 @@ const DOMAIN_TEMPLATES = [
61
65
  get: { oldName: 'erp_pi_get' },
62
66
  add: { oldName: 'erp_pi_add' },
63
67
  edit: { oldName: 'erp_pi_edit' },
64
- remove: { oldName: 'erp_pi_remove' }
68
+ remove: { oldName: 'erp_pi_remove' },
69
+ export: { oldName: 'erp_pi_export' }
65
70
  }
66
71
  },
67
72
  {
@@ -91,9 +96,11 @@ const DOMAIN_TEMPLATES = [
91
96
  craft_schema: { oldName: 'erp_order_craft_schema' },
92
97
  craft_get: { oldName: 'erp_order_style_craft_list' },
93
98
  craft_save: { oldName: 'erp_order_style_craft_save' },
99
+ craft_export: { oldName: 'erp_order_style_craft_export' },
94
100
  size_chart_save: { oldName: 'erp_order_style_size_chart_save' },
95
101
  color_card_list: { oldName: 'erp_order_style_color_card_list' },
96
102
  color_card_save: { oldName: 'erp_order_style_color_card_save' },
103
+ color_card_export: { oldName: 'erp_order_style_color_card_export' },
97
104
  timeline_list: { oldName: 'erp_order_timeline_list' },
98
105
  sample_list: { oldName: 'erp_order_sample_list' },
99
106
  sample_add: { oldName: 'erp_order_sample_add' },
@@ -106,6 +113,7 @@ const DOMAIN_TEMPLATES = [
106
113
  shipment_remove: { oldName: 'erp_order_shipment_remove' },
107
114
  attachment_list: { oldName: 'erp_order_attachment_list' },
108
115
  attachment_add: { oldName: 'erp_order_attachment_add' },
116
+ attachment_export: { oldName: 'erp_order_attachment_export' },
109
117
  attachment_remove: { oldName: 'erp_order_attachment_remove' }
110
118
  }
111
119
  },
@@ -151,6 +159,7 @@ const DOMAIN_TEMPLATES = [
151
159
  material: {
152
160
  list: { oldName: 'erp_material_list' },
153
161
  get: { oldName: 'erp_material_get' },
162
+ bound_orders: { oldName: 'erp_material_bound_orders' },
154
163
  add: { oldName: 'erp_material_add' },
155
164
  edit: { oldName: 'erp_material_edit' },
156
165
  remove: { oldName: 'erp_material_remove' }
@@ -176,9 +185,13 @@ const DOMAIN_TEMPLATES = [
176
185
  },
177
186
  price_quote: {
178
187
  list: { oldName: 'erp_material_price_quote_list' },
188
+ get: { oldName: 'erp_material_price_quote_get' },
179
189
  add: { oldName: 'erp_material_price_quote_add' },
180
190
  edit: { oldName: 'erp_material_price_quote_edit' },
181
191
  remove: { oldName: 'erp_material_price_quote_remove' }
192
+ },
193
+ price_point: {
194
+ series: { oldName: 'erp_material_price_point_series' }
182
195
  }
183
196
  }
184
197
  },
@@ -330,8 +343,13 @@ function inputSchemaFor(tpl, allowed) {
330
343
  orderId: optionalId.describe('订单 ID'),
331
344
  styleId: optionalId.describe('款式 ID(craft_get 必填)'),
332
345
  customerId: optionalId.describe('客户 ID'),
346
+ brandId: optionalId.describe('品牌 ID'),
347
+ bizLine: z.string().optional().describe('业务线 direct/trader'),
333
348
  orderStatus: z.string().optional().describe('订单状态'),
334
- assigneeId: optionalId.describe('跟单员 ID')
349
+ assigneeId: optionalId.describe('跟单员 ID'),
350
+ fileName: z.string().optional().describe('导出类 action:落盘文件名'),
351
+ archive: z.boolean().optional().describe('导出类 action:true 归档到订单附件'),
352
+ ossId: optionalId.describe('OSS 文件 ID(attachment_export 必填)')
335
353
  };
336
354
  }
337
355
  if (tpl.name === 'erp_production') {
@@ -360,9 +378,12 @@ function inputSchemaFor(tpl, allowed) {
360
378
  materialId: optionalId.describe('物料 ID'),
361
379
  supplierId: optionalId.describe('供应商 ID'),
362
380
  snapshotId: optionalId.describe('比价快照 ID'),
381
+ subjectKey: z.string().optional().describe('比价标的键'),
363
382
  productName: z.string().optional().describe('品名'),
364
383
  categoryName: z.string().optional().describe('品类'),
365
384
  domain: z.string().optional().describe('比价口径 domestic/export'),
385
+ startDate: z.string().optional().describe('起始日期 yyyy-MM-dd'),
386
+ endDate: z.string().optional().describe('截止日期 yyyy-MM-dd'),
366
387
  matchLevel: z.string().optional().describe('匹配度 A/B/C/D'),
367
388
  platform: z.string().optional().describe('渠道/平台'),
368
389
  riskLevel: z.string().optional().describe('风险级别 high/mid/low/info'),
package/dist/tools.js CHANGED
@@ -67,6 +67,13 @@ const READ_TOOLS = [
67
67
  inputSchema: {},
68
68
  handler: (client) => client.get('/erp/common/merchandiserOptions')
69
69
  },
70
+ {
71
+ name: 'erp_user_options',
72
+ description: '查询全部用户下拉选项(userId/userName/nickName),任务负责人等场景取 userId',
73
+ permission: null,
74
+ inputSchema: {},
75
+ handler: (client) => client.get('/erp/common/userOptions')
76
+ },
70
77
  {
71
78
  name: 'erp_customer_list',
72
79
  description: '分页查询客户列表,可按客户编码、名称、类型、状态筛选',
@@ -136,6 +143,33 @@ const READ_TOOLS = [
136
143
  status: args.status
137
144
  })
138
145
  },
146
+ {
147
+ name: 'erp_customer_contact_get',
148
+ description: '按 ID 查询客户联系人',
149
+ permission: 'erp:customer:query',
150
+ inputSchema: {
151
+ id: idField.describe('联系人 ID,字符串')
152
+ },
153
+ handler: (client, args) => client.get(`/erp/customer/contact/${asId(args.id)}`)
154
+ },
155
+ {
156
+ name: 'erp_customer_address_get',
157
+ description: '按 ID 查询客户地址',
158
+ permission: 'erp:customer:query',
159
+ inputSchema: {
160
+ id: idField.describe('地址 ID,字符串')
161
+ },
162
+ handler: (client, args) => client.get(`/erp/customer/address/${asId(args.id)}`)
163
+ },
164
+ {
165
+ name: 'erp_customer_brand_get',
166
+ description: '按 ID 查询客户品牌',
167
+ permission: 'erp:customer:query',
168
+ inputSchema: {
169
+ id: idField.describe('品牌 ID,字符串')
170
+ },
171
+ handler: (client, args) => client.get(`/erp/customer/brand/${asId(args.id)}`)
172
+ },
139
173
  {
140
174
  name: 'erp_pi_list',
141
175
  description: '分页查询 PI(形式发票)列表',
@@ -162,6 +196,18 @@ const READ_TOOLS = [
162
196
  },
163
197
  handler: (client, args) => client.get(`/erp/pi/${asId(args.id)}`)
164
198
  },
199
+ {
200
+ name: 'erp_pi_export',
201
+ description: '按 ID 导出 PI Excel 到本机并返回落盘路径',
202
+ permission: 'erp:pi:export',
203
+ inputSchema: {
204
+ id: idField.describe('PI ID,字符串')
205
+ },
206
+ handler: (client, args) => {
207
+ const id = asId(args.id);
208
+ return client.download('POST', `/erp/pi/${id}/export-excel`, `PI-${id}.xlsx`);
209
+ }
210
+ },
165
211
  {
166
212
  name: 'erp_order_list',
167
213
  description: '分页查询订单列表',
@@ -170,6 +216,8 @@ const READ_TOOLS = [
170
216
  ...pageShape,
171
217
  orderNo: z.string().optional().describe('订单号'),
172
218
  customerId: optionalId.describe('客户 ID,字符串'),
219
+ brandId: optionalId.describe('品牌 ID,字符串'),
220
+ bizLine: z.string().optional().describe('业务线 direct/trader'),
173
221
  orderStatus: z.string().optional().describe('订单状态'),
174
222
  assigneeId: optionalId.describe('跟单员 ID,字符串')
175
223
  },
@@ -177,6 +225,8 @@ const READ_TOOLS = [
177
225
  ...pageParams(args),
178
226
  orderNo: args.orderNo,
179
227
  customerId: asId(args.customerId),
228
+ brandId: asId(args.brandId),
229
+ bizLine: args.bizLine,
180
230
  orderStatus: args.orderStatus,
181
231
  assigneeId: asId(args.assigneeId)
182
232
  })
@@ -230,6 +280,20 @@ const READ_TOOLS = [
230
280
  },
231
281
  handler: (client, args) => client.get('/erp/order/style/craft/list', { styleId: asId(args.styleId) })
232
282
  },
283
+ {
284
+ name: 'erp_order_style_craft_export',
285
+ description: '按 styleId 导出工艺单 Excel 到本机并返回落盘路径;默认 preview 不写附件,archive=true 才归档到订单附件',
286
+ permission: 'erp:order:query',
287
+ inputSchema: {
288
+ styleId: idField.describe('款式 ID,字符串'),
289
+ fileName: z.string().optional().describe('落盘文件名,默认 craft-<styleId>.xlsx'),
290
+ archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export 权限')
291
+ },
292
+ handler: (client, args) => {
293
+ const styleId = asId(args.styleId);
294
+ return client.download('POST', args.archive ? '/erp/order/style/craft/export-excel' : '/erp/order/style/craft/preview-excel', args.fileName ?? `craft-${styleId}.xlsx`, { params: { styleId } });
295
+ }
296
+ },
233
297
  {
234
298
  name: 'erp_order_style_color_card_list',
235
299
  description: '按 styleId 打开色卡编号清单(工艺项 + colorCardCode/materialCode)',
@@ -239,6 +303,17 @@ const READ_TOOLS = [
239
303
  },
240
304
  handler: (client, args) => client.get('/erp/order/style/color-card', { styleId: asId(args.styleId) })
241
305
  },
306
+ {
307
+ name: 'erp_order_style_color_card_export',
308
+ description: '按 styleId 导出色卡 Excel 到本机并返回落盘路径;默认 preview 不写附件,archive=true 才归档到订单附件',
309
+ permission: 'erp:order:query',
310
+ inputSchema: {
311
+ styleId: idField.describe('款式 ID,字符串'),
312
+ fileName: z.string().optional().describe('落盘文件名,默认 color-card-<styleId>.xlsx'),
313
+ archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export 权限')
314
+ },
315
+ handler: (client, args) => client.download('POST', args.archive ? '/erp/order/style/color-card/export' : '/erp/order/style/color-card/preview', args.fileName ?? `color-card-${asId(args.styleId)}.xlsx`, { body: { styleIds: [asId(args.styleId)] } })
316
+ },
242
317
  {
243
318
  name: 'erp_production_craft_schema',
244
319
  description: '生产单工艺 schema + selectionGuide(同订单)',
@@ -273,6 +348,16 @@ const READ_TOOLS = [
273
348
  },
274
349
  handler: (client, args) => client.get('/erp/order/attachment/list', { orderId: asId(args.orderId) })
275
350
  },
351
+ {
352
+ name: 'erp_order_attachment_export',
353
+ description: '按 ossId 下载订单附件到本机并返回落盘路径;ossId 取自 attachment_list',
354
+ permission: 'system:oss:download',
355
+ inputSchema: {
356
+ ossId: idField.describe('OSS 文件 ID,字符串,取自 attachment_list'),
357
+ fileName: z.string().optional().describe('落盘文件名,默认取 OSS 原始文件名')
358
+ },
359
+ handler: (client, args) => client.download('GET', `/resource/oss/download/${asId(args.ossId)}`, args.fileName)
360
+ },
276
361
  {
277
362
  name: 'erp_order_sample_list',
278
363
  description: '查询订单轮次(可为空,可不建轮次)',
@@ -369,13 +454,15 @@ const READ_TOOLS = [
369
454
  ...pageShape,
370
455
  materialCode: z.string().optional().describe('物料编码(面料分类即面料号)'),
371
456
  materialName: z.string().optional().describe('物料名称'),
372
- categoryId: optionalId.describe('分类 ID,字符串')
457
+ categoryId: optionalId.describe('分类 ID,字符串'),
458
+ status: z.string().optional().describe('状态 active/inactive')
373
459
  },
374
460
  handler: (client, args) => client.get('/erp/material/list', {
375
461
  ...pageParams(args),
376
462
  materialCode: args.materialCode,
377
463
  materialName: args.materialName,
378
- categoryId: asId(args.categoryId)
464
+ categoryId: asId(args.categoryId),
465
+ status: args.status
379
466
  })
380
467
  },
381
468
  {
@@ -387,6 +474,15 @@ const READ_TOOLS = [
387
474
  },
388
475
  handler: (client, args) => client.get(`/erp/material/${asId(args.id)}`)
389
476
  },
477
+ {
478
+ name: 'erp_material_bound_orders',
479
+ description: '查询引用该物料的订单(含款号/绑定来源),删除或停用物料前先查',
480
+ permission: 'erp:material:query',
481
+ inputSchema: {
482
+ id: idField.describe('物料 ID,字符串')
483
+ },
484
+ handler: (client, args) => client.get(`/erp/material/${asId(args.id)}/boundOrders`)
485
+ },
390
486
  {
391
487
  name: 'erp_material_supplier_list',
392
488
  description: '查询物料可供应商清单',
@@ -449,6 +545,36 @@ const READ_TOOLS = [
449
545
  riskLevel: args.riskLevel
450
546
  })
451
547
  },
548
+ {
549
+ name: 'erp_material_price_quote_get',
550
+ description: '按 ID 查询比价明细单条',
551
+ permission: 'erp:material:price:query',
552
+ inputSchema: {
553
+ id: idField.describe('比价明细 ID,字符串')
554
+ },
555
+ handler: (client, args) => client.get(`/erp/material/price/quote/${asId(args.id)}`)
556
+ },
557
+ {
558
+ name: 'erp_material_price_point_series',
559
+ description: '查询物料比价历史点序列(趋势曲线),传 snapshotId 由服务端解析标的',
560
+ permission: 'erp:material:price:query',
561
+ inputSchema: {
562
+ snapshotId: optionalId.describe('比价快照 ID,传了它服务端据此解析标的键'),
563
+ subjectKey: z.string().optional().describe('标的键'),
564
+ materialId: optionalId.describe('物料 ID,subjectKey 缺失时兜底'),
565
+ domain: z.string().optional().describe('比价口径 domestic/export'),
566
+ startDate: z.string().optional().describe('起始日期 yyyy-MM-dd'),
567
+ endDate: z.string().optional().describe('截止日期 yyyy-MM-dd')
568
+ },
569
+ handler: (client, args) => client.get('/erp/material/price/point/series', {
570
+ snapshotId: asId(args.snapshotId),
571
+ subjectKey: args.subjectKey,
572
+ materialId: asId(args.materialId),
573
+ domain: args.domain,
574
+ startDate: args.startDate,
575
+ endDate: args.endDate
576
+ })
577
+ },
452
578
  {
453
579
  name: 'erp_bid_list',
454
580
  description: '分页查询物料招标列表',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yindie-erp-mcp",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "银蝶 ERP 独立 MCP 服务,通过 RuoYi API 提供查询与写操作能力",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,8 @@
12
12
  },
13
13
  "scripts": {
14
14
  "build": "tsc",
15
+ "check": "node scripts/check-consistency.mjs",
16
+ "check:download": "node scripts/check-download.mjs",
15
17
  "start": "node dist/index.js",
16
18
  "dev": "tsc && node dist/index.js",
17
19
  "prepublishOnly": "npm run build",