yindie-erp-mcp 2.0.5 → 2.0.6

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,6 +1,7 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { basename, join } from 'node:path';
4
+ import { parseJsonSafe } from './tool-helpers.js';
4
5
  /** ERP API 客户端 */
5
6
  export class YindieClient {
6
7
  /** 后端根地址 */
@@ -86,7 +87,7 @@ export class YindieClient {
86
87
  const response = await fetch(url, init);
87
88
  const contentType = response.headers.get('content-type') ?? '';
88
89
  if (contentType.includes('application/json')) {
89
- const result = (await response.json());
90
+ const result = (await parseJsonSafe(await response.text()));
90
91
  if (result.code === 401 && !retried) {
91
92
  this.token = await this.relogin();
92
93
  return this.download(method, path, fileName, options, true);
@@ -125,7 +126,7 @@ export class YindieClient {
125
126
  init.body = JSON.stringify(body);
126
127
  }
127
128
  const response = await fetch(url, init);
128
- const result = (await response.json());
129
+ const result = (await parseJsonSafe(await response.text()));
129
130
  if (result.code === 401 && !retried) {
130
131
  this.token = await this.relogin();
131
132
  return this.request(method, path, body, params, true);
@@ -151,7 +152,7 @@ export class YindieClient {
151
152
  };
152
153
  const form = await formFactory();
153
154
  const response = await fetch(url, { method: 'POST', headers, body: form });
154
- const result = (await response.json());
155
+ const result = (await parseJsonSafe(await response.text()));
155
156
  if (result.code === 401 && !retried) {
156
157
  this.token = await this.relogin();
157
158
  return this.requestForm(path, formFactory, true);
@@ -5,17 +5,81 @@ export const payloadField = z.string().describe('JSON 请求体,字段与后
5
5
  export const idsField = z.string().describe('主键 ID,多个用英文逗号分隔');
6
6
  /** 单个主键字段,字符串避免雪花 ID 精度丢失 */
7
7
  export const idField = z.string().describe('主键 ID');
8
+ /** 样衣类型白名单 */
9
+ export const SAMPLE_TYPES = new Set(['proto', 'pre_prod', 'bulk', 'mass']);
10
+ /** ≥16 位数字字面量改成字符串,避免雪花 ID 精度丢失 */
11
+ export function preserveSnowflakeIds(text) {
12
+ return text.replace(/([:\[,]\s*)(\d{16,})(\s*[,\]}])/g, '$1"$2"$3');
13
+ }
14
+ /** 解析保真后的 JSON */
15
+ export function parseJsonSafe(text) {
16
+ return JSON.parse(preserveSnowflakeIds(text));
17
+ }
18
+ /** 校验对象内 id 类字段不为 number */
19
+ function assertNoNumberIds(value, path = '') {
20
+ if (value == null || typeof value !== 'object') {
21
+ return;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ value.forEach((item, i) => assertNoNumberIds(item, `${path}[${i}]`));
25
+ return;
26
+ }
27
+ for (const [key, child] of Object.entries(value)) {
28
+ const here = path ? `${path}.${key}` : key;
29
+ if (typeof child === 'number' && (key === 'id' || key === 'ids' || /Id$/.test(key))) {
30
+ throw new Error(`雪花 ID 必须字符串:${here}=${child}(勿用 JSON number)`);
31
+ }
32
+ assertNoNumberIds(child, here);
33
+ }
34
+ }
8
35
  /** 解析 JSON 请求体 */
9
36
  export function parsePayload(args) {
10
37
  const raw = args.payload;
11
38
  if (typeof raw === 'string') {
12
- return JSON.parse(raw);
39
+ const parsed = parseJsonSafe(raw);
40
+ assertNoNumberIds(parsed);
41
+ return parsed;
13
42
  }
14
43
  if (raw && typeof raw === 'object') {
44
+ assertNoNumberIds(raw);
15
45
  return raw;
16
46
  }
17
47
  throw new Error('payload 必须是 JSON 字符串');
18
48
  }
49
+ /** 规整 style_save:自动包 styles,禁止空列表 */
50
+ export function normalizeStyleSavePayload(raw) {
51
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
52
+ throw new Error('style_save payload 须为对象');
53
+ }
54
+ const input = raw;
55
+ let styles = input.styles;
56
+ if (!Array.isArray(styles)) {
57
+ if (input.styleNo != null || input.colors != null || input.styleName != null) {
58
+ const { orderId, ...rest } = input;
59
+ return { orderId, styles: [rest] };
60
+ }
61
+ throw new Error('style_save 须 payload.styles:[{...}],勿把款式字段放顶层');
62
+ }
63
+ if (styles.length === 0) {
64
+ throw new Error('style_save 的 styles 不能为空数组');
65
+ }
66
+ return { ...input, styles };
67
+ }
68
+ /** 规整样衣写入:类型白名单 + sampleNo→roundName */
69
+ export function normalizeSamplePayload(raw) {
70
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
71
+ throw new Error('样衣 payload 须为对象');
72
+ }
73
+ const payload = { ...raw };
74
+ if (payload.sampleType != null && !SAMPLE_TYPES.has(String(payload.sampleType))) {
75
+ throw new Error(`sampleType 仅允许 proto/pre_prod/bulk/mass,收到:${payload.sampleType}`);
76
+ }
77
+ if (payload.roundName == null && payload.sampleNo != null) {
78
+ payload.roundName = payload.sampleNo;
79
+ delete payload.sampleNo;
80
+ }
81
+ return payload;
82
+ }
19
83
  /** 规整工艺保存入参 */
20
84
  export function normalizeCraftSavePayload(raw) {
21
85
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
package/dist/tool-meta.js CHANGED
@@ -110,14 +110,37 @@ export const ENTITY_META = {
110
110
  module: '订单管理',
111
111
  step: '业务流程第3步:创建订单',
112
112
  label: '订单',
113
- prerequisite: '客户已创建;assigneeId 来自 erp_workbench action=merchandiser_options',
114
- addRequired: 'orderNo, customerId, bizLine, assigneeId',
115
- addOptional: 'brandId, orderStatus(默认inquiry), orderDate, deliveryDate, contractDate, depositReceived(Y/N), qtyTolerance, shippingMethod, packingType, priority, sourceChannel, parentOrderId, remark',
116
- editRequired: 'id, orderNo, customerId, bizLine, assigneeId',
117
- enums: 'bizLine:direct/trader; orderStatus见erp_business_flow资源;orderNo 手填全库唯一可改;重复报订单号已存在',
113
+ prerequisite: '订单客户与季度已建立;先用 erp_order action=customer_list 找订单客户,再 action=season_list 取 seasonId',
114
+ addRequired: 'orderNo, seasonId',
115
+ addOptional: 'orderStatus(默认inquiry), orderDate, deliveryDate, contractDate, depositReceived(Y/N), qtyTolerance, shippingMethod, packingType, priority, sourceChannel, parentOrderId, remark',
116
+ editRequired: 'id, orderNo',
117
+ enums: 'orderStatus见erp_business_flow资源;orderNo 手填全库唯一可改;重复报订单号已存在;客户/品牌/业务线由季度所属订单客户带出,勿传;跟单员只在订单客户维护,订单不再有跟单员',
118
118
  removeConstraint: '删除会级联清该单 PI、款式树(含工艺/商标位置/色卡)和生产单,订单号改为 _原ID 释放号码',
119
119
  next: 'erp_order_link_pi → erp_order_style_save → erp_order_change_status'
120
120
  },
121
+ erp_order_customer: {
122
+ module: '订单管理',
123
+ step: '业务流程第3步前置:订单客户建档,跟单员挂在这一层',
124
+ label: '订单客户',
125
+ prerequisite: '客户已建档;跟单员取自 erp_workbench action=merchandiser_options',
126
+ addRequired: 'customerId, bizLine, assigneeId',
127
+ addOptional: 'brandId, remark',
128
+ editRequired: 'id, customerId, bizLine, assigneeId',
129
+ enums: 'bizLine:direct/trader;assigneeId 多个用英文逗号分隔;同一客户+品牌+业务线不可重复建档',
130
+ removeConstraint: '订单客户下存在季度时不可删除',
131
+ next: 'erp_order action=season_add'
132
+ },
133
+ erp_order_season: {
134
+ module: '订单管理',
135
+ step: '业务流程第3步前置:在订单客户下建季度',
136
+ label: '订单季度',
137
+ prerequisite: '订单客户已建档;orderCustomerId 来自 erp_order action=customer_list',
138
+ addRequired: 'orderCustomerId, seasonName',
139
+ editRequired: 'id, orderCustomerId, seasonName',
140
+ enums: 'seasonName 手填,如 2026SS / 历史订单',
141
+ removeConstraint: '季度下存在订单时不可删除',
142
+ next: 'erp_order action=add(带 seasonId)'
143
+ },
121
144
  erp_task: {
122
145
  module: '任务协同',
123
146
  step: '订单跟进过程中创建待办',
@@ -197,13 +220,13 @@ export const ENTITY_META = {
197
220
  module: '生产管理',
198
221
  step: '手动新建生产单(通常用fromOrder)',
199
222
  label: '生产单',
200
- prerequisite: '订单已存在',
223
+ prerequisite: '订单已存在;【暂不用】前端制单中心菜单已隐藏,常规业务勿新建/推进生产单',
201
224
  addRequired: 'orderId, bizLine',
202
225
  addOptional: 'poVersion, poStatus(默认draft), deliveryDate, qtyTolerance, shippingMethod, shippingAddress, packingType, bulkSampleRequired(Y/N), bulkSampleDeadline, remark',
203
226
  editRequired: 'id, orderId, bizLine',
204
227
  enums: 'poStatus:draft/reviewing/issued/superseded;poNo 后端自动生成勿传',
205
228
  removeConstraint: '已下发(issued)且订单仍存在时不可删除;订单删除会连带清生产单;仅 draft 可修改',
206
- next: 'erp_production craft_save(可选)→ check → submit_review → issue;辅料工艺只读展示款式配置已选面料与色卡,一般不必 save_items'
229
+ next: '【暂不用】勿引导 create_from_order / check / issue;订单侧款式工艺色卡尺寸表即可'
207
230
  },
208
231
  erp_bid: {
209
232
  module: '招标管理',
@@ -225,17 +248,9 @@ export const WORKFLOW_META = {
225
248
  step: '推进订单生命周期',
226
249
  summary: '变更订单状态',
227
250
  prerequisite: '订单已创建',
228
- payload: '{id, orderStatus, remark?}',
251
+ payload: '{id, orderStatus, remark?};优先 payload.id,顶层 id/orderId 仅兜底写入 payload.id',
229
252
  constraint: '流转: inquiry→sampling|doc_preparing|quoted(跳过打样); sampling→sample_shipped; sample_shipped→sample_confirmed; sample_confirmed→quoted; quoted→po_drafting; doc_preparing→pre_prod_sample; pre_prod_sample→pre_prod_confirmed; pre_prod_confirmed→po_drafting; po_drafting→po_ai_checking; po_ai_checking→color_card_confirmed|po_drafting; color_card_confirmed→po_reviewing; po_reviewing→po_issued',
230
- next: 'quoted/pre_prod_confirmed/po_drafting 时 erp_production_create_from_order'
231
- },
232
- erp_order_assign: {
233
- module: '订单管理',
234
- step: '订单分派跟单员',
235
- summary: '分派或变更跟单员',
236
- prerequisite: '订单已创建;assigneeId 来自 erp_workbench action=merchandiser_options',
237
- payload: '{id, assigneeId}',
238
- next: 'erp_order_style_save'
253
+ next: '状态推进到此结束即可;【暂不用】勿再调 erp_production_create_from_order(制单中心已隐藏)'
239
254
  },
240
255
  erp_order_link_pi: {
241
256
  module: '订单管理',
@@ -257,11 +272,11 @@ export const WORKFLOW_META = {
257
272
  erp_order_style_save: {
258
273
  module: '订单管理',
259
274
  step: '录入款式明细',
260
- summary: '保存订单款式及颜色尺码/色卡物料,成功后回查 styles(含 id 作 styleId);页面配置确定即调本接口',
275
+ summary: '保存订单主款式及颜色尺码/色卡物料,成功后回查 styles(含 id 作 styleId、roundStyles);页面配置确定即调本接口',
261
276
  prerequisite: '订单已创建;payload 内 orderId 用字符串',
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 必填',
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,不在本接口',
264
- next: '从返回 styles[].id 取 styleId → craft_save;色卡备注 → color_card_save'
277
+ 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, cutQty?, actualQty?}]}]}]};必须 styles 非空数组;顶层误放单款字段时 MCP 会自动包一层;styleNo 可省略则默认订单号-序号;colors[].colorName 必填;qty/cutQty?/actualQty?',
278
+ constraint: 'styles:[] 拒写;只传主款(勿塞 roundStyles);全量 styles;已有 id/同款号则更新并替换颜色尺码,工艺/商标保留;空 sizeChartJson/样板号/船样提示沿用库值;未出现的旧主款整树删除;轮次克隆由 sample_add 生成',
279
+ next: '从返回 styles[].id 取 styleId → craft_save;轮次尺寸表写 roundStyles[].styleNo;色卡备注 → color_card_save'
265
280
  },
266
281
  erp_order_style_color_card_save: {
267
282
  module: '订单管理',
@@ -306,24 +321,24 @@ export const WORKFLOW_META = {
306
321
  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',
307
322
  enums: 'garmentPart:jacket/pants; craftModule/fieldKey 见 schema;labelType/positionSide 仍用字典 erp_label_*',
308
323
  constraint: '全量替换:按 styleId 先删该款全部工艺和商标位置再插入。jacket+pants 必须放进同一次 crafts;分部位连调会互相覆盖。已有商标位置时必须一并回传 labelPositions,漏传会清空。返回 data=写入条数。清空传 crafts:[];processNote 为工艺备注(订单页会传);色卡列 fieldValue 里的 colorCardCode/materialCode 由后端沿用旧值,勿手填;商标图片来自物料:materialId 选商标物料,diagramOssId 取该物料 imageOssId;色卡选料不在本接口,走 style_save colors[].colorCardJson',
309
- next: 'erp_order action=craft_get 校验 → erp_order_change_status 推进后再 erp_production_create_from_order'
324
+ next: 'erp_order action=craft_get 校验 → erp_order_change_status 推进即可;【暂不用】勿再调生产单'
310
325
  },
311
326
  erp_order_style_size_chart_save: {
312
327
  module: '订单管理',
313
328
  step: '写入尺寸表入库视图',
314
- summary: '按 orderId+styleNo 写入款式 sizeChartJson(erpView);幂等键含 source.sha256;确认后才调',
315
- prerequisite: '款式已存在;payload 为入库视图 doc.erp,不是整包;用户已在会话确认',
316
- payload: '{orderId:string, styleNo, sizeChartJson:erpView对象, confirmedBy:确认人ID, source?:mcp|ui}',
317
- constraint: 'sha256 为空拒写;style_no 与入参不一致拒写;同键重试不写库不打时间线',
318
- next: 'erp_oss_upload 拿 ossId → erp_order_attachment_add×3 留底 → erp_production_check'
329
+ summary: '按 orderId+styleNo 写入款式 sizeChartJson(erpView);主款或轮次克隆款均可;成功写变更日志;UI 与 MCP 共用',
330
+ prerequisite: '款式已存在;payload 为入库视图,不是整包;MCP 侧需用户确认,UI 点保存即可',
331
+ payload: '{orderId:string, styleNo, sizeChartJson:{view_version,style_no,category:top|bottom|set,unit,source:{sha256},sizes:[{raw}],rows:[{std_key,std_zh,category:top|bottom,raw_name?,raw_name_zh?,delta?,is_extra?,values:[{size,type,raw,value_cm?}]}]}, confirmedBy:确认人ID, source?:mcp|ui}',
332
+ constraint: 'sha256 为空拒写;style_no 与入参/视图不一致拒写;同键重试不写库;固定部位 std_key 对齐上衣/裤子模板,可加 is_extra 行;delta 空用 null;轮次表用克隆款 styleNo(sample_list.roundStyleId→style_list.roundStyles)',
333
+ next: '可选 size_chart_export / oss.upload+attachment_add 留底;查历史 size_chart_logs'
319
334
  },
320
335
  erp_production_craft_save: {
321
336
  module: '生产管理',
322
337
  step: '填写结构化工艺',
323
- summary: '生产单草稿阶段保存款式工艺(同订单工艺模板与 JSON 存值)',
324
- prerequisite: '生产单 poStatus=draft,款式已存在',
338
+ summary: '生产单草稿阶段保存款式工艺(同订单工艺模板与 JSON 存值)【暂不用】',
339
+ prerequisite: '生产单 poStatus=draft,款式已存在;常规流程勿用',
325
340
  payload: '同订单 craft_save 的 crafts/craftModule/labelPositions;生产页通常不传 processNote',
326
- constraint: '同订单 craft_save:全量替换(含商标位置,漏传 labelPositions 会清空),返回写入条数',
341
+ constraint: '【暂不用】同订单 craft_save:全量替换(含商标位置,漏传 labelPositions 会清空),返回写入条数',
327
342
  next: 'erp_production action=craft_get 校验 → erp_production_check'
328
343
  },
329
344
  erp_oss_upload: {
@@ -355,19 +370,19 @@ export const WORKFLOW_META = {
355
370
  erp_order_sample_add: {
356
371
  module: '订单管理',
357
372
  step: '记录轮次',
358
- summary: '新增订单轮次(状态固定 pending;页面 Tab「轮次」)',
359
- prerequisite: '订单已创建',
360
- payload: '{orderId, sampleType, styleId?, roundName?, sizeName?, colorName?, plannedFinishDate?, remark?}',
361
- enums: 'sampleType(类型):proto打样/pre_prod产前样/bulk船样/mass大货;roundName 与 sampleType 独立',
362
- constraint: '新建后状态为 pending,不可传 sampleStatus;list 回填 styleName'
373
+ summary: '新增订单轮次并克隆主款式(pending;页面 Tab「轮次」)',
374
+ prerequisite: '订单已创建;styleId 必须是主款(parentStyleId 为空)',
375
+ payload: '{orderId, sampleType, styleId, roundName?, priority?, colorName?, plannedFinishDate?, remark?};误传 sampleNo 会映射为 roundName',
376
+ enums: 'sampleType 仅 proto/pre_prod/bulk/mass;priority:low/normal/high/urgent(默认normal);roundName 与 sampleType 独立',
377
+ constraint: 'MCP 校验 sampleType 白名单;新建后 pending;后端克隆主款→roundStyles;list 回填 roundStyleId;尺寸表/工艺写克隆款'
363
378
  },
364
379
  erp_order_sample_edit: {
365
380
  module: '订单管理',
366
381
  step: '更新轮次信息',
367
- summary: '修改轮次基础信息(不含状态)',
368
- prerequisite: '轮次已存在',
369
- payload: '{id, orderId, sampleType, styleId?, roundName?, sizeName?, colorName?, plannedFinishDate?, shipDate?, trackingNo?, courierCompany?, remark?}',
370
- constraint: '不可通过 edit 改状态,状态走 sample_change_status'
382
+ summary: '修改轮次基础信息(不含状态);换主款会重克隆',
383
+ prerequisite: '轮次已存在;styleId 须为主款',
384
+ payload: '{id, orderId, sampleType, styleId, roundName?, priority?, colorName?, plannedFinishDate?, shipDate?, trackingNo?, courierCompany?, remark?};sampleNo→roundName',
385
+ constraint: 'MCP 校验 sampleType;不可 edit 改状态;改 styleId/roundName 会重建克隆款'
371
386
  },
372
387
  erp_order_sample_change_status: {
373
388
  module: '订单管理',
@@ -381,7 +396,7 @@ export const WORKFLOW_META = {
381
396
  erp_order_sample_remove: {
382
397
  module: '订单管理',
383
398
  step: '删除轮次',
384
- summary: '删除订单轮次',
399
+ summary: '删除订单轮次及其克隆款式',
385
400
  prerequisite: '轮次已存在',
386
401
  payload: 'ids: 轮次ID,多个逗号分隔'
387
402
  },
@@ -411,11 +426,11 @@ export const WORKFLOW_META = {
411
426
  erp_production_create_from_order: {
412
427
  module: '生产管理',
413
428
  step: '从订单生成生产单草稿',
414
- summary: '一键创建生产单',
415
- prerequisite: '订单已存在、尚无生产单,且 orderStatus 为 quoted/pre_prod_confirmed/po_drafting',
429
+ summary: '一键创建生产单【暂不用:制单中心菜单已隐藏,勿主动调用】',
430
+ prerequisite: '订单已存在、尚无生产单,且 orderStatus 为 quoted/pre_prod_confirmed/po_drafting;常规流程勿用',
416
431
  payload: 'orderId: 订单ID',
417
- constraint: '仅 quoted/pre_prod_confirmed/po_drafting 可调用;若当前为 quoted/pre_prod_confirmed 会自动改订单为 po_drafting;生产单号=订单号-PO;每单仅一条',
418
- next: '完善交期等主档 → craft_save(可选)→ erp_production_check;辅料来自订单款式配置只读'
432
+ constraint: '【暂不用】前端制单中心已隐藏;仅 quoted/pre_prod_confirmed/po_drafting 可调用;若当前为 quoted/pre_prod_confirmed 会自动改订单为 po_drafting;生产单号=订单号-PO;每单仅一条',
433
+ next: '勿继续引导 check/submit_review/issue,除非用户明确要求'
419
434
  },
420
435
  erp_production_save_items: {
421
436
  module: '生产管理',
@@ -429,38 +444,38 @@ export const WORKFLOW_META = {
429
444
  erp_production_check: {
430
445
  module: '生产管理',
431
446
  step: '人工校验缺项',
432
- summary: '执行生产单完整性校验(含交期、面料/色卡物料、尺寸表、工艺选项等)',
433
- prerequisite: '生产单已存在',
447
+ summary: '执行生产单完整性校验【暂不用】',
448
+ prerequisite: '生产单已存在;常规勿用',
434
449
  payload: 'id: 生产单ID',
435
450
  returns: '校验分数、缺项列表(checkResult: pass/warning/fail)',
436
- next: 'erp_production_submit_review'
451
+ next: '【暂不用】勿继续 submit_review'
437
452
  },
438
453
  erp_production_submit_review: {
439
454
  module: '生产管理',
440
455
  step: '提交审核',
441
- summary: '生产单提交审核',
442
- prerequisite: '生产单poStatus=draft',
456
+ summary: '生产单提交审核【暂不用】',
457
+ prerequisite: '生产单poStatus=draft;常规勿用',
443
458
  payload: 'id: 生产单ID',
444
459
  constraint: '提交后poStatus→reviewing',
445
- next: 'erp_production_issue 或 erp_production_reject_review'
460
+ next: '【暂不用】'
446
461
  },
447
462
  erp_production_reject_review: {
448
463
  module: '生产管理',
449
464
  step: '审核退回',
450
- summary: '生产单审核退回草稿',
451
- prerequisite: '生产单poStatus=reviewing,需erp:production:review权限',
465
+ summary: '生产单审核退回草稿【暂不用】',
466
+ prerequisite: '生产单poStatus=reviewing,需erp:production:review权限;常规勿用',
452
467
  payload: 'id: 生产单ID',
453
468
  constraint: '退回后poStatus→draft',
454
- next: '改主档/工艺后重新 check → submit_review'
469
+ next: '【暂不用】'
455
470
  },
456
471
  erp_production_issue: {
457
472
  module: '生产管理',
458
473
  step: '下发工厂',
459
- summary: '生产单正式下发',
460
- prerequisite: '生产单poStatus=reviewing,需erp:production:issue权限',
474
+ summary: '生产单正式下发【暂不用】',
475
+ prerequisite: '生产单poStatus=reviewing,需erp:production:issue权限;常规勿用',
461
476
  payload: 'id: 生产单ID',
462
477
  constraint: '下发后poStatus→issued,关联订单状态→po_issued',
463
- next: '流程结束'
478
+ next: '【暂不用】'
464
479
  },
465
480
  erp_email_thread_link: {
466
481
  module: '邮件',
@@ -477,7 +492,7 @@ export const READ_META = {
477
492
  usage: '【入口】建议首先调用,获取待办任务、超期任务、缺项生产单、近期订单概览'
478
493
  },
479
494
  erp_merchandiser_options: {
480
- usage: '返回跟单员角色用户列表;建客户/订单或 order.assign 前取 assigneeId(字符串)',
495
+ usage: '返回跟单员角色用户列表;建订单客户前取 assigneeId(字符串)',
481
496
  returns: '[{userId, userName, nickName}]'
482
497
  },
483
498
  erp_user_options: {
@@ -504,11 +519,19 @@ export const READ_META = {
504
519
  returns: '{filePath, size},filePath 为本机绝对路径'
505
520
  },
506
521
  erp_order_list: {
507
- usage: '订单状态见 erp_business_flow;可按 orderStatus/assigneeId/customerId/brandId/bizLine 筛选'
522
+ usage: '订单状态见 erp_business_flow;可按 orderStatus/customerId/brandId/bizLine/seasonId 筛选'
523
+ },
524
+ erp_order_customer_list: {
525
+ usage: '查订单客户(跟单员挂在此层);可按 customerId/bizLine/orderNo/orderStatus 筛选',
526
+ returns: 'rows[].{id,customerId,brandId,bizLine,assigneeId,assigneeName}'
527
+ },
528
+ erp_order_season_list: {
529
+ usage: '按 orderCustomerId 查季度,建订单前取 seasonId',
530
+ returns: '[{id,orderCustomerId,seasonName}]'
508
531
  },
509
532
  erp_order_get: { usage: '查订单主档', returns: '订单基本信息' },
510
533
  erp_order_style_list: {
511
- usage: '查订单款式→颜色→尺码→工艺→商标位置;入参 orderId(字符串) 或 orderNo;返回含 styleId,制单/写工艺前必查'
534
+ usage: '查主款树:colors/sizes(含qty/cutQty/actualQty)/crafts/商标;轮次克隆在 roundStyles(parentStyleId/sampleId/roundName);入参 orderId 或 orderNo;写工艺/尺寸表前必查'
512
535
  },
513
536
  erp_order_craft_schema: {
514
537
  usage: '必读返回 selectionGuide:jacket/pants 每字段 options、howToSelect、fieldValueExample;再按示例拼 fieldValue JSON 字符串写入 craft_save;旁框键为 note'
@@ -521,7 +544,15 @@ export const READ_META = {
521
544
  returns: '[{garmentPart, fieldKey, titleCn, colorCardCode, materialCode, materialName, sort}]'
522
545
  },
523
546
  erp_order_style_craft_export: {
524
- usage: '单款导出工艺单 xlsx 到本机;默认走 preview-excel 不写附件,archive=true 才走 export-excel 写订单附件',
547
+ usage: '单款导出工艺单 xlsx;qtyType=prod|cut|actual(默认 prod);默认 preview 不写附件,archive=true 才归档',
548
+ returns: '{filePath, size},filePath 为本机绝对路径'
549
+ },
550
+ erp_order_style_size_chart_logs: {
551
+ usage: 'styleId 必填;查尺寸表变更快照(sizeChartJson/source/sha256/operatorName)',
552
+ returns: '[{id,orderId,styleId,styleNo,sizeChartJson,source,sha256,operatorId,operatorName,createTime}]'
553
+ },
554
+ erp_order_style_size_chart_export: {
555
+ usage: 'styleId 必填;可传 sizeChartJson,或 orderId+styleId 从 style_list 回查;fileName 仅文件名(落盘临时目录/yindie-erp-exports);archive=true 写附件',
525
556
  returns: '{filePath, size},filePath 为本机绝对路径'
526
557
  },
527
558
  erp_order_style_color_card_export: {
@@ -540,11 +571,13 @@ export const READ_META = {
540
571
  },
541
572
  erp_order_timeline_list: { usage: '查订单状态变更、生产事件等跟进时间线' },
542
573
  erp_order_attachment_list: { usage: '查订单附件列表' },
543
- erp_order_sample_list: { usage: '查订单轮次(含 styleId/styleName/roundName);可不建轮次' },
574
+ erp_order_sample_list: {
575
+ usage: '查订单轮次(styleId=主款,roundStyleId=克隆款,roundName/priority);可不建轮次'
576
+ },
544
577
  erp_order_shipment_list: { usage: '查订单出货记录,按 shipSeq 升序;一单可多批' },
545
- erp_production_list: { usage: '生产单状态 draft/reviewing/issued/superseded' },
546
- erp_production_get: { usage: '查生产单主档、styles(含 colors.colorCardJson)及缺项;辅料展示读款式配置已选面料与色卡' },
547
- erp_production_check_list: { usage: '查生产单历次校验记录及得分' },
578
+ erp_production_list: { usage: '【暂不用】制单中心已隐藏;生产单状态 draft/reviewing/issued/superseded' },
579
+ erp_production_get: { usage: '【暂不用】查生产单主档、styles(含 colors.colorCardJson)及缺项;辅料展示读款式配置已选面料与色卡' },
580
+ erp_production_check_list: { usage: '【暂不用】查生产单历次校验记录及得分' },
548
581
  erp_task_list: { usage: '任务状态 pending/in_progress/completed/cancelled' },
549
582
  erp_task_get: { usage: '查任务详情' },
550
583
  erp_material_category_list: { usage: '查物料分类树,新建物料前先查 categoryId' },
@@ -576,24 +609,21 @@ export const READ_META = {
576
609
  };
577
610
  /** ERP 业务流程文档 */
578
611
  export const ERP_BUSINESS_FLOW = {
579
- overview: '外贸服装ERP:客户建档 → PI形式发票 → 订单(款式/附件/跟单) → 生产单(校验/审核/下发)。物料/供应商为主数据,招标为物料询价,邮件/任务为协同手段。客户/供应商/分类/品牌/生产单/招标等编号后端发号;materialCode/订单号/款号手填(复制订单按年+缩写+序号发号)。',
612
+ overview: '外贸服装ERP:客户建档 → PI形式发票 → 订单(款式/附件/跟单)。【暂不用】制单中心菜单已隐藏,常规勿走生产单校验/审核/下发。物料/供应商为主数据,招标为物料询价,邮件/任务为协同手段。客户/供应商/分类/品牌/生产单/招标等编号后端发号;materialCode/订单号/款号手填(复制订单按年+缩写+序号发号)。',
580
613
  mainFlow: [
581
614
  '1. erp_customer action=customer.add 建客户(勿传customerCode;可传 taxNo/shortCode)',
582
615
  '2. erp_customer action=contact.add / address.add / brand.add 补全资料(brand 可传 shortCode)',
583
616
  '3. erp_pi action=add 创建PI(需customerId, sourceType, piNo;可选 orderId 直接关联订单)',
584
- '4. erp_workbench action=merchandiser_options 取跟单员,再 erp_order action=add(需 orderNo,customerId,bizLine,assigneeId,默认inquiry;复制订单号规则26SB01)',
617
+ '4. erp_workbench action=merchandiser_options 取跟单员 → erp_order action=customer_add 建订单客户 → action=season_add 建季度 → action=add(需 orderNo,seasonId,默认inquiry;复制订单号规则26SB01)',
585
618
  '5. 若 PI 未带 orderId:erp_order action=link_pi 关联PI',
586
619
  '6. erp_material list/add 选或新增面料(fabric 必填色号 color)→ erp_order action=style_save(colors[].fabricMaterialId/colorCode/colorName/colorCardJson + 尺码)',
587
620
  '7. erp_order action=craft_schema → craft_save 写工艺选项(jacket+pants 一次全量)→ craft_get 校验',
588
621
  '8. (可选)erp_order action=color_card_save 写色卡页脚备注(常规 {styleId,colorCardNote,items:[]});选料已在 style_save.colorCardJson',
589
- '9. (可选)erp_order action=size_chart_save 写入尺寸表视图;erp_oss action=upload 拿 ossId 后 attachment_add×3 留底',
590
- '10. erp_order action=change_status 按规则推进状态',
591
- '11. erp_production action=create_from_order 生成生产单草稿(订单须 quoted/pre_prod_confirmed/po_drafting 且无生产单;成功后订单→po_drafting)',
592
- '12. erp_production action=craft_save 补工艺(辅料工艺只读展示款式配置已选面料与色卡物料,不必 save_items 手录)',
593
- '13. erp_production action=check 人工校验(含面料/色卡物料/sizeChart 缺项)',
594
- '14. erp_production action=submit_review 提交审核',
595
- '15. erp_production action=issue 下发(订单→po_issued)',
596
- '16. erp_bid action=add 对物料发起明/暗标(materialId,qty,bidMode,openTime,closeTime)'
622
+ '9. (可选)erp_order action=sample_add 建轮次(自动克隆主款→roundStyleId);尺寸表/工艺按轮次写克隆款',
623
+ '10. (可选)erp_order action=size_chart_save 写入尺寸表视图(主款或克隆款 styleNo);size_chart_export 导出;size_chart_logs 查历史',
624
+ '11. erp_order action=change_status 按规则推进状态(主链路到此结束)',
625
+ '12. 【暂不用】erp_production create_from_order / check / submit_review / issue:制单中心已隐藏,勿主动调用',
626
+ '13. erp_bid action=add 对物料发起明/暗标(materialId,qty,bidMode,openTime,closeTime)'
597
627
  ],
598
628
  bidStatus: {
599
629
  default: 'draft',
@@ -732,19 +762,17 @@ export const ERP_BUSINESS_FLOW = {
732
762
  constraints: [
733
763
  '客户删除:PI 关联的订单仍在时不可删;关联订单已删的 PI 随客户一起清掉',
734
764
  '订单删除:级联清该单 PI、款式树和生产单,订单号改为 _原ID 释放号码可复用',
735
- '每订单仅一条生产单(erp_production_create_from_order)',
736
- 'erp_production_create_from_order 仅 orderStatus=quoted|pre_prod_confirmed|po_drafting 且尚无生产单时可调用;成功后订单强制 orderStatus=po_drafting',
737
- '生产单仅draft可修改/编辑明细',
738
- '生产单submit_review: draft→reviewing',
739
- '生产单reject_review: reviewing→draft(需review权限)',
740
- '生产单issue: reviewing→issued(需issue权限),同步订单→po_issued',
765
+ '【暂不用】制单中心菜单已隐藏:勿主动 erp_production create_from_order / check / submit_review / issue,除非用户明确要求',
766
+ '每订单仅一条生产单(erp_production_create_from_order);仅 orderStatus=quoted|pre_prod_confirmed|po_drafting 且尚无生产单时可调用;成功后订单强制 orderStatus=po_drafting',
767
+ '生产单仅draft可修改/编辑明细;submit_review draft→reviewing;reject_review reviewing→draft;issue reviewing→issued 并同步订单→po_issued',
741
768
  '已下发(issued)生产单且订单仍在时不可删除;订单删除会连带清生产单',
742
769
  '工艺选项与怎么选见 erp_order_craft_schema.selectionGuide 或资源 yindie://erp/craft-guide',
743
770
  '工艺选项固定模板(对齐打样单),不用 sys_dict;fieldValue 存 JSON(见 craftValueJson / fieldValueExample)',
744
771
  '工艺 fieldKey/craftModule 以 erp_order_craft_schema.fields 为准,勿再用旧字典码如 single_1',
745
772
  '款式面料色号:material.list/add → style_save colors[].fabricMaterialId,colorCode=物料 color,colorName 必填;勿传 fabricColorNo',
746
773
  '色卡选料: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',
774
+ 'Excel 导出/预览都是二进制:工艺单 craft_export(qtyType=prod|cut|actual)、色卡 color_card_export、尺寸表 size_chart_export、PI export,均落本机返回 filePath',
775
+ '轮次 sample_add 会克隆主款;style_list 主款挂 roundStyles;尺寸表/工艺按轮次用 roundStyleId;style_save 只传主款',
748
776
  '物料删除:有绑定订单/生产单引用/比价快照时不可删;删前用 material.bound_orders 查引用',
749
777
  '物料新增:编码命中已删除记录时直接恢复原记录并覆盖写入,不报重复',
750
778
  'craft_save 全量替换:同 styleId 的 jacket+pants 必须一次写入;分次调用后一次会删前一次',
@@ -756,7 +784,11 @@ export const ERP_BUSINESS_FLOW = {
756
784
  '单据编号:customerCode/supplierCode/categoryCode/brandCode/supplierMaterialCode/poNo/bidNo 后端发号勿传;materialCode 手填必传;piNo/orderNo 手填必传(订单复制发26SB01);styleNo 可省略则默认订单号-序号',
757
785
  '招标仅 draft 可改/可删(cancelled 可删);collecting 可 close/cancel;closed 可 award/cancel;awarded 不可取消;collecting 不可直接 award',
758
786
  '招标 MCP 为内部接口,不含供应商门户 portal/login、portal/quote',
759
- '所有雪花 ID 入参必须字符串,禁止 number(否则精度丢失导致静默空结果/挂错父对象)'
787
+ '所有雪花 ID 入参必须字符串,禁止 number;MCP 会对 payload/响应中 ≥16 位数字字面量保真为字符串',
788
+ 'style_save 必须 styles 非空;顶层单款字段 MCP 会自动包 styles:[...];styles:[] 拒写',
789
+ 'sampleType 仅 proto/pre_prod/bulk/mass;误传 sampleNo 映射为 roundName',
790
+ 'change_status 优先 payload.id;顶层 id/orderId 会兜底写入 payload.id',
791
+ '导出 fileName 仅取文件名,落盘系统临时目录/yindie-erp-exports'
760
792
  ],
761
793
  entryPoint: 'erp_workbench action=summary',
762
794
  resourceHint: '操作前先读 yindie://erp/usage-guide、yindie://erp/business-flow、yindie://erp/modules'
@@ -769,13 +801,15 @@ export const ERP_MCP_INSTRUCTIONS = `银蝶 ERP MCP(域工具 + action 模式
769
801
 
770
802
  【调用格式】选域工具(erp_customer/erp_order/erp_material 等),必填 action。有子实体时 action 写 target.action,如 customer.add、material.add、price_snapshot.list、thread.link。写操作用 payload 传 JSON 字符串;删用 ids;单条查改 id。所有雪花 ID 必须字符串。
771
803
 
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。
804
+ 【主链路】customer.add →(订单客户)order.customer_add → order.season_add → order.add(seasonId) → pi.add → order.link_pi →(面料 material.list/add)order.style_save(colors…+sizes.qty/cutQty?/actualQty?) → order.craft_schema → order.craft_save →(可选)order.color_card_save →(可选)order.sample_add 克隆轮次 →(尺寸表)order.size_chart_save(主款或克隆款 styleNo)→ order.change_status。到订单推进为止。
805
+
806
+ 【工艺】先 craft_schema 拿 fieldKey 与 fieldValueExample;craft_save 全量替换含商标位置;色卡选料写 style_save.colorCardJson;备注走 color_card_save;导出 craft_export(qtyType=prod|cut|actual)/color_card_export/size_chart_export/attachment_export,默认不写附件,archive=true 才归档。
773
807
 
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 才归档。
808
+ 【轮次/尺寸表】sample_add 的 styleId 只能是主款;返回后 sample_list.roundStyleId 为克隆款。style_list 主款含 roundStyles。尺寸表 size_chart_save 按 styleNo 写;历史 size_chart_logs;导出 size_chart_export。
775
809
 
776
- 【物料】新增 material.add(materialCode 手填;fabric 必填 color 色号;lining 必填 composition,前后端均校验;编码命中已删记录会恢复原记录);选已有 material.list → 面料色号行填 colors[].fabricMaterialId(colorCode=color,colorName=materialName),色卡选料填 colorCardJson;删前 material.bound_orders 查引用。
810
+ 【物料】material.add(materialCode 手填;fabric 必填 color;lining 必填 composition);面料色号行填 fabricMaterialId;删前 material.bound_orders。
777
811
 
778
- 【制单】create_from_order(成功后订单强制 po_drafting)→ 补交期等 → check(校验面料/色卡物料)→ submit_review → issue;辅料工艺只读,来自订单 style_save 已选面料与 colorCardJson,一般不调 save_items。
812
+ 【制单·暂不用】前端「制单中心」菜单已隐藏;勿主动 production.create_from_order / check / submit_review / issue。API 仍保留,仅用户明确要求时再动。
779
813
 
780
814
  【权限】无权限 action 会报错;以 yindie://erp/modules 返回的 actions 为准。`;
781
815
  /** 合并工具用法资源 */
@@ -792,7 +826,7 @@ export const ERP_USAGE_GUIDE = {
792
826
  domains: {
793
827
  erp_workbench: {
794
828
  actions: ['summary', 'current_user', 'merchandiser_options', 'user_options'],
795
- note: '入口,优先 summary;建单/分派前 merchandiser_options 取 assigneeId;任务负责人用 user_options'
829
+ note: '入口,优先 summary;建订单客户前 merchandiser_options 取 assigneeId;任务负责人用 user_options'
796
830
  },
797
831
  erp_customer: {
798
832
  actions: ['customer.*', 'contact.*', 'address.*', 'brand.*'],
@@ -805,13 +839,20 @@ export const ERP_USAGE_GUIDE = {
805
839
  },
806
840
  erp_order: {
807
841
  actions: [
842
+ 'customer_list',
843
+ 'customer_add',
844
+ 'customer_edit',
845
+ 'customer_remove',
846
+ 'season_list',
847
+ 'season_add',
848
+ 'season_edit',
849
+ 'season_remove',
808
850
  'list',
809
851
  'get',
810
852
  'add',
811
853
  'edit',
812
854
  'remove',
813
855
  'change_status',
814
- 'assign',
815
856
  'link_pi',
816
857
  'copy',
817
858
  'style_list',
@@ -821,6 +862,8 @@ export const ERP_USAGE_GUIDE = {
821
862
  'craft_save',
822
863
  'craft_export',
823
864
  'size_chart_save',
865
+ 'size_chart_logs',
866
+ 'size_chart_export',
824
867
  'color_card_list',
825
868
  'color_card_save',
826
869
  'color_card_export',
@@ -839,7 +882,7 @@ export const ERP_USAGE_GUIDE = {
839
882
  'attachment_export',
840
883
  'attachment_remove'
841
884
  ],
842
- note: 'style_save 成功后返回 styles 含 styleId;craft_save 需 styleId;craft_export/color_card_export/attachment_export 返回本机落盘路径,前两者 archive=true 才写订单附件'
885
+ note: '建订单前 customer_list→season_list 取 seasonId;style_save 只传主款;sample_add 克隆轮次;尺寸表按主款/克隆款 styleNo;craft_export 可带 qtyType;导出类 archive=true 才写附件'
843
886
  },
844
887
  erp_production: {
845
888
  actions: [
@@ -859,7 +902,7 @@ export const ERP_USAGE_GUIDE = {
859
902
  'reject_review',
860
903
  'issue'
861
904
  ],
862
- note: 'create_from_order 用 orderId,成功后订单→po_drafting;issue 需 reviewing 且 issue 权限'
905
+ note: '【暂不用】制单中心菜单已隐藏,勿主动调用;create_from_order 用 orderId,成功后订单→po_drafting;issue 需 reviewing 且 issue 权限'
863
906
  },
864
907
  erp_task: { actions: ['list', 'get', 'add', 'edit', 'remove'] },
865
908
  erp_material: {
@@ -911,7 +954,7 @@ export const ERP_USAGE_GUIDE = {
911
954
  colorCode: '物料色号',
912
955
  colorName: '颜色名称(必填)',
913
956
  colorCardJson: '{"lining":{"materialCode":"LIN-01","materialName":"里布","titleCn":"里布"}}',
914
- sizes: [{ sizeName: 'M', qty: 120 }]
957
+ sizes: [{ sizeName: 'M', qty: 120, cutQty: 125, actualQty: 118 }]
915
958
  }
916
959
  ]
917
960
  }
@@ -920,11 +963,19 @@ export const ERP_USAGE_GUIDE = {
920
963
  }
921
964
  },
922
965
  {
923
- intent: '导出工艺单/色卡',
924
- call: { tool: 'erp_order', action: 'craft_export', styleId: '字符串' }
966
+ intent: '导出工艺单(裁剪单数量)',
967
+ call: { tool: 'erp_order', action: 'craft_export', styleId: '字符串', qtyType: 'cut' }
968
+ },
969
+ {
970
+ intent: '导出尺寸表',
971
+ call: { tool: 'erp_order', action: 'size_chart_export', styleId: '字符串', orderId: '字符串' }
972
+ },
973
+ {
974
+ intent: '查尺寸表变更记录',
975
+ call: { tool: 'erp_order', action: 'size_chart_logs', styleId: '字符串' }
925
976
  },
926
977
  {
927
- intent: '从订单生成生产单',
978
+ intent: '从订单生成生产单【暂不用:制单中心已隐藏】',
928
979
  call: { tool: 'erp_production', action: 'create_from_order', orderId: '字符串' }
929
980
  },
930
981
  {
@@ -963,7 +1014,6 @@ export const ERP_USAGE_GUIDE = {
963
1014
  ])),
964
1015
  payloadByWorkflow: {
965
1016
  'order.change_status': WORKFLOW_META.erp_order_change_status,
966
- 'order.assign': WORKFLOW_META.erp_order_assign,
967
1017
  'order.link_pi': WORKFLOW_META.erp_order_link_pi,
968
1018
  'pi.export': READ_META.erp_pi_export,
969
1019
  'order.copy': WORKFLOW_META.erp_order_copy,
@@ -972,6 +1022,8 @@ export const ERP_USAGE_GUIDE = {
972
1022
  'order.craft_save': WORKFLOW_META.erp_order_style_craft_save,
973
1023
  'order.craft_export': READ_META.erp_order_style_craft_export,
974
1024
  'order.size_chart_save': WORKFLOW_META.erp_order_style_size_chart_save,
1025
+ 'order.size_chart_logs': READ_META.erp_order_style_size_chart_logs,
1026
+ 'order.size_chart_export': READ_META.erp_order_style_size_chart_export,
975
1027
  'order.color_card_list': READ_META.erp_order_style_color_card_list,
976
1028
  'order.color_card_save': WORKFLOW_META.erp_order_style_color_card_save,
977
1029
  'order.color_card_export': READ_META.erp_order_style_color_card_export,
@@ -82,13 +82,20 @@ const DOMAIN_TEMPLATES = [
82
82
  summary: '【订单管理】订单 CRUD、状态、款式工艺、轮次、附件',
83
83
  hasTarget: false,
84
84
  routes: {
85
+ customer_list: { oldName: 'erp_order_customer_list' },
86
+ customer_add: { oldName: 'erp_order_customer_add' },
87
+ customer_edit: { oldName: 'erp_order_customer_edit' },
88
+ customer_remove: { oldName: 'erp_order_customer_remove' },
89
+ season_list: { oldName: 'erp_order_season_list' },
90
+ season_add: { oldName: 'erp_order_season_add' },
91
+ season_edit: { oldName: 'erp_order_season_edit' },
92
+ season_remove: { oldName: 'erp_order_season_remove' },
85
93
  list: { oldName: 'erp_order_list' },
86
94
  get: { oldName: 'erp_order_get' },
87
95
  add: { oldName: 'erp_order_add' },
88
96
  edit: { oldName: 'erp_order_edit' },
89
97
  remove: { oldName: 'erp_order_remove' },
90
98
  change_status: { oldName: 'erp_order_change_status' },
91
- assign: { oldName: 'erp_order_assign' },
92
99
  link_pi: { oldName: 'erp_order_link_pi' },
93
100
  copy: { oldName: 'erp_order_copy' },
94
101
  style_list: { oldName: 'erp_order_style_list' },
@@ -98,6 +105,8 @@ const DOMAIN_TEMPLATES = [
98
105
  craft_save: { oldName: 'erp_order_style_craft_save' },
99
106
  craft_export: { oldName: 'erp_order_style_craft_export' },
100
107
  size_chart_save: { oldName: 'erp_order_style_size_chart_save' },
108
+ size_chart_logs: { oldName: 'erp_order_style_size_chart_logs' },
109
+ size_chart_export: { oldName: 'erp_order_style_size_chart_export' },
101
110
  color_card_list: { oldName: 'erp_order_style_color_card_list' },
102
111
  color_card_save: { oldName: 'erp_order_style_color_card_save' },
103
112
  color_card_export: { oldName: 'erp_order_style_color_card_export' },
@@ -119,7 +128,7 @@ const DOMAIN_TEMPLATES = [
119
128
  },
120
129
  {
121
130
  name: 'erp_production',
122
- summary: '【生产管理】生产单 CRUD、工艺、校验审核下发;辅料只读来自订单款式配置',
131
+ summary: '【生产管理·暂不用】制单中心已隐藏;勿主动建单/校验/下发;辅料只读来自订单款式配置',
123
132
  hasTarget: false,
124
133
  routes: {
125
134
  list: { oldName: 'erp_production_list' },
@@ -341,13 +350,15 @@ function inputSchemaFor(tpl, allowed) {
341
350
  ...base,
342
351
  orderNo: z.string().optional().describe('订单号'),
343
352
  orderId: optionalId.describe('订单 ID'),
344
- styleId: optionalId.describe('款式 ID(craft_get 必填)'),
353
+ styleId: optionalId.describe('款式 ID;craft_get/size_chart_logs/size_chart_export 必填'),
345
354
  customerId: optionalId.describe('客户 ID'),
346
355
  brandId: optionalId.describe('品牌 ID'),
347
356
  bizLine: z.string().optional().describe('业务线 direct/trader'),
348
357
  orderStatus: z.string().optional().describe('订单状态'),
349
- assigneeId: optionalId.describe('跟单员 ID'),
350
- fileName: z.string().optional().describe('导出类 action:落盘文件名'),
358
+ orderCustomerId: optionalId.describe('订单客户 ID(season_list 必填)'),
359
+ qtyType: z.string().optional().describe('craft_export:prod/cut/actual,默认 prod'),
360
+ sizeChartJson: z.string().optional().describe('size_chart_export:入库视图 JSON;可省略则靠 orderId+styleId 回查'),
361
+ fileName: z.string().optional().describe('导出类:仅文件名,落盘临时目录/yindie-erp-exports'),
351
362
  archive: z.boolean().optional().describe('导出类 action:true 归档到订单附件'),
352
363
  ossId: optionalId.describe('OSS 文件 ID(attachment_export 必填)')
353
364
  };
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { basename } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { idField, idsField, normalizeCraftSavePayload, parseIds, parsePayload } from './tool-helpers.js';
4
+ import { idField, idsField, normalizeCraftSavePayload, normalizeSamplePayload, normalizeStyleSavePayload, parseIds, parsePayload } from './tool-helpers.js';
5
5
  import { buildCrudDescription, buildWorkflowDescription, ENTITY_META, payloadFor, WORKFLOW_META } from './tool-meta.js';
6
6
  /** 本地文件组装 multipart */
7
7
  async function buildOssUploadForm(filePath) {
@@ -55,6 +55,8 @@ export const WRITE_TOOLS = [
55
55
  ...entityCrud('erp_customer_brand', '/erp/customer/brand', 'erp:customer'),
56
56
  ...entityCrud('erp_pi', '/erp/pi', 'erp:pi'),
57
57
  ...entityCrud('erp_order', '/erp/order', 'erp:order'),
58
+ ...entityCrud('erp_order_customer', '/erp/order/customer', 'erp:order'),
59
+ ...entityCrud('erp_order_season', '/erp/order/season', 'erp:order'),
58
60
  ...entityCrud('erp_task', '/erp/task', 'erp:task'),
59
61
  ...entityCrud('erp_material', '/erp/material', 'erp:material'),
60
62
  ...entityCrud('erp_material_category', '/erp/material/category', 'erp:material:category'),
@@ -68,12 +70,20 @@ export const WRITE_TOOLS = [
68
70
  workflowTool('erp_bid_close', 'erp:bid:edit', { id: idField.describe('招标 ID,需 collecting') }, (client, args) => client.put(`/erp/bid/close/${args.id}`)),
69
71
  workflowTool('erp_bid_award', 'erp:bid:edit', { payload: z.string().describe(WORKFLOW_META.erp_bid_award.payload) }, (client, args) => client.put('/erp/bid/award', parsePayload(args))),
70
72
  workflowTool('erp_bid_cancel', 'erp:bid:edit', { id: idField.describe('招标 ID,不可取消 awarded') }, (client, args) => client.put(`/erp/bid/cancel/${args.id}`)),
71
- workflowTool('erp_order_change_status', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_change_status.payload) }, (client, args) => client.put('/erp/order/status', parsePayload(args))),
72
- workflowTool('erp_order_assign', 'erp:order:assign', { payload: z.string().describe(WORKFLOW_META.erp_order_assign.payload) }, (client, args) => client.put('/erp/order/assign', parsePayload(args))),
73
+ workflowTool('erp_order_change_status', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_change_status.payload) }, (client, args) => {
74
+ const payload = parsePayload(args);
75
+ if (payload.id == null) {
76
+ const topId = args.id ?? args.orderId;
77
+ if (topId != null && topId !== '') {
78
+ payload.id = String(topId);
79
+ }
80
+ }
81
+ return client.put('/erp/order/status', payload);
82
+ }),
73
83
  workflowTool('erp_order_link_pi', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_link_pi.payload) }, (client, args) => client.put('/erp/order/pi/link', parsePayload(args))),
74
84
  workflowTool('erp_order_copy', 'erp:order:add', { payload: z.string().describe(WORKFLOW_META.erp_order_copy.payload) }, (client, args) => client.post('/erp/order/copy', parsePayload(args))),
75
85
  workflowTool('erp_order_style_save', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_style_save.payload) }, async (client, args) => {
76
- const payload = parsePayload(args);
86
+ const payload = normalizeStyleSavePayload(parsePayload(args));
77
87
  const result = await client.post('/erp/order/style/save', payload);
78
88
  const orderId = payload.orderId == null ? undefined : String(payload.orderId);
79
89
  if (!orderId) {
@@ -89,8 +99,8 @@ export const WRITE_TOOLS = [
89
99
  workflowTool('erp_order_style_size_chart_save', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_style_size_chart_save.payload) }, (client, args) => client.post('/erp/order/style/size-chart', parsePayload(args))),
90
100
  workflowTool('erp_order_style_color_card_save', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_style_color_card_save.payload) }, (client, args) => client.post('/erp/order/style/color-card/save', parsePayload(args))),
91
101
  workflowTool('erp_production_craft_save', 'erp:production:edit', { payload: z.string().describe(WORKFLOW_META.erp_production_craft_save.payload) }, (client, args) => client.post('/erp/production/craft/save', normalizeCraftSavePayload(parsePayload(args)))),
92
- workflowTool('erp_order_sample_add', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_sample_add.payload) }, (client, args) => client.post('/erp/order/sample', parsePayload(args))),
93
- workflowTool('erp_order_sample_edit', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_sample_edit.payload) }, (client, args) => client.put('/erp/order/sample', parsePayload(args))),
102
+ workflowTool('erp_order_sample_add', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_sample_add.payload) }, (client, args) => client.post('/erp/order/sample', normalizeSamplePayload(parsePayload(args)))),
103
+ workflowTool('erp_order_sample_edit', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_sample_edit.payload) }, (client, args) => client.put('/erp/order/sample', normalizeSamplePayload(parsePayload(args)))),
94
104
  workflowTool('erp_order_sample_change_status', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_sample_change_status.payload) }, (client, args) => client.put('/erp/order/sample/status', parsePayload(args))),
95
105
  workflowTool('erp_order_sample_remove', 'erp:order:remove', { ids: idsField }, (client, args) => client.delete(`/erp/order/sample/${parseIds(args)}`)),
96
106
  workflowTool('erp_order_shipment_add', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_shipment_add.payload) }, (client, args) => client.post('/erp/order/shipment', parsePayload(args))),
package/dist/tools.js CHANGED
@@ -62,7 +62,7 @@ const READ_TOOLS = [
62
62
  },
63
63
  {
64
64
  name: 'erp_merchandiser_options',
65
- description: '查询跟单员下拉选项(userId/userName/nickName),建单/分派前取 assigneeId',
65
+ description: '查询跟单员下拉选项(userId/userName/nickName),建订单客户前取 assigneeId',
66
66
  permission: null,
67
67
  inputSchema: {},
68
68
  handler: (client) => client.get('/erp/common/merchandiserOptions')
@@ -215,22 +215,50 @@ const READ_TOOLS = [
215
215
  inputSchema: {
216
216
  ...pageShape,
217
217
  orderNo: z.string().optional().describe('订单号'),
218
+ seasonId: optionalId.describe('季度 ID,字符串'),
218
219
  customerId: optionalId.describe('客户 ID,字符串'),
219
220
  brandId: optionalId.describe('品牌 ID,字符串'),
220
221
  bizLine: z.string().optional().describe('业务线 direct/trader'),
221
- orderStatus: z.string().optional().describe('订单状态'),
222
- assigneeId: optionalId.describe('跟单员 ID,字符串')
222
+ orderStatus: z.string().optional().describe('订单状态')
223
223
  },
224
224
  handler: (client, args) => client.get('/erp/order/list', {
225
225
  ...pageParams(args),
226
226
  orderNo: args.orderNo,
227
+ seasonId: asId(args.seasonId),
227
228
  customerId: asId(args.customerId),
228
229
  brandId: asId(args.brandId),
229
230
  bizLine: args.bizLine,
230
- orderStatus: args.orderStatus,
231
- assigneeId: asId(args.assigneeId)
231
+ orderStatus: args.orderStatus
232
232
  })
233
233
  },
234
+ {
235
+ name: 'erp_order_customer_list',
236
+ description: '分页查询订单客户,跟单员挂在这一层',
237
+ permission: 'erp:order:list',
238
+ inputSchema: {
239
+ ...pageShape,
240
+ customerId: optionalId.describe('客户 ID,字符串'),
241
+ bizLine: z.string().optional().describe('业务线 direct/trader'),
242
+ orderNo: z.string().optional().describe('订单号'),
243
+ orderStatus: z.string().optional().describe('订单状态')
244
+ },
245
+ handler: (client, args) => client.get('/erp/order/customer/list', {
246
+ ...pageParams(args),
247
+ customerId: asId(args.customerId),
248
+ bizLine: args.bizLine,
249
+ orderNo: args.orderNo,
250
+ orderStatus: args.orderStatus
251
+ })
252
+ },
253
+ {
254
+ name: 'erp_order_season_list',
255
+ description: '按订单客户查季度,创建订单前取 seasonId',
256
+ permission: 'erp:order:list',
257
+ inputSchema: {
258
+ orderCustomerId: idField.describe('订单客户 ID,字符串')
259
+ },
260
+ handler: (client, args) => client.get('/erp/order/season/list', { orderCustomerId: asId(args.orderCustomerId) })
261
+ },
234
262
  {
235
263
  name: 'erp_order_get',
236
264
  description: '按 ID 查询订单详情',
@@ -242,7 +270,7 @@ const READ_TOOLS = [
242
270
  },
243
271
  {
244
272
  name: 'erp_order_style_list',
245
- description: '查询订单款式及颜色尺码、结构化工艺、商标位置;orderId 或 orderNo 二选一,ID 必须字符串',
273
+ description: '查询订单主款式树(含 colors/sizes/crafts/商标;轮次克隆挂在 roundStyles);orderId 或 orderNo 二选一,ID 必须字符串',
246
274
  permission: 'erp:order:query',
247
275
  inputSchema: {
248
276
  orderId: optionalId.describe('订单 ID,字符串雪花 ID'),
@@ -282,16 +310,74 @@ const READ_TOOLS = [
282
310
  },
283
311
  {
284
312
  name: 'erp_order_style_craft_export',
285
- description: '按 styleId 导出工艺单 Excel 到本机并返回落盘路径;默认 preview 不写附件,archive=true 才归档到订单附件',
313
+ description: '按 styleId 导出工艺单 Excel 到本机并返回落盘路径;qtyType=prod|cut|actual 取尺码数量;默认 preview 不写附件,archive=true 才归档',
286
314
  permission: 'erp:order:query',
287
315
  inputSchema: {
288
316
  styleId: idField.describe('款式 ID,字符串'),
289
- fileName: z.string().optional().describe('落盘文件名,默认 craft-<styleId>.xlsx'),
317
+ qtyType: z.string().optional().describe('数量类型 prod生产单/cut裁剪单/actual实际单,默认 prod'),
318
+ fileName: z
319
+ .string()
320
+ .optional()
321
+ .describe('仅文件名(忽略目录),默认 craft-<styleId>.xlsx,落盘临时目录/yindie-erp-exports'),
290
322
  archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export 权限')
291
323
  },
292
324
  handler: (client, args) => {
293
325
  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 } });
326
+ const qtyType = args.qtyType || 'prod';
327
+ 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, qtyType } });
328
+ }
329
+ },
330
+ {
331
+ name: 'erp_order_style_size_chart_logs',
332
+ description: '按 styleId 查尺寸表变更记录(快照列表,含 sizeChartJson);styleId 必填',
333
+ permission: 'erp:order:query',
334
+ inputSchema: {
335
+ styleId: idField.describe('款式 ID,必填字符串(主款或轮次克隆)')
336
+ },
337
+ handler: (client, args) => client.get('/erp/order/style/size-chart/logs', { styleId: asId(args.styleId) })
338
+ },
339
+ {
340
+ name: 'erp_order_style_size_chart_export',
341
+ description: '导出尺寸表 Excel;styleId 必填;可传 sizeChartJson,或 orderId 从 style_list 回查;fileName 仅文件名(落盘临时目录);archive=true 写附件',
342
+ permission: 'erp:order:query',
343
+ inputSchema: {
344
+ styleId: idField.describe('款式 ID,必填字符串'),
345
+ orderId: optionalId.describe('订单 ID;未传 sizeChartJson 时用于回查'),
346
+ sizeChartJson: z.string().optional().describe('入库视图 JSON 字符串;可省略则从 style_list 取'),
347
+ fileName: z
348
+ .string()
349
+ .optional()
350
+ .describe('仅文件名(忽略目录),落盘到系统临时目录/yindie-erp-exports'),
351
+ archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export 权限')
352
+ },
353
+ handler: async (client, args) => {
354
+ const styleId = asId(args.styleId);
355
+ if (!styleId) {
356
+ throw new Error('size_chart_export 须传 styleId(字符串)');
357
+ }
358
+ let view = undefined;
359
+ if (typeof args.sizeChartJson === 'string' && args.sizeChartJson) {
360
+ view = JSON.parse(args.sizeChartJson);
361
+ }
362
+ let parentCount = 0;
363
+ let roundCount = 0;
364
+ if (view == null) {
365
+ const orderId = asId(args.orderId);
366
+ if (!orderId) {
367
+ throw new Error('缺少 sizeChartJson;或同时传 orderId 从 style_list 自动取');
368
+ }
369
+ const styles = (await client.get('/erp/order/style/list', { orderId }));
370
+ parentCount = styles.length;
371
+ roundCount = styles.reduce((n, s) => n + (s.roundStyles?.length || 0), 0);
372
+ const hit = styles.find((s) => asId(s.id) === styleId) ||
373
+ styles.flatMap((s) => s.roundStyles || []).find((s) => asId(s.id) === styleId);
374
+ const raw = hit?.sizeChartJson;
375
+ view = typeof raw === 'string' ? JSON.parse(raw) : raw;
376
+ }
377
+ if (view == null) {
378
+ throw new Error(`未找到尺寸表视图(styleId=${styleId},主款${parentCount}、轮次克隆${roundCount});请先 size_chart_save 或传入 sizeChartJson`);
379
+ }
380
+ return client.download('POST', args.archive ? '/erp/order/style/size-chart/export-excel' : '/erp/order/style/size-chart/preview-excel', args.fileName ?? `size-chart-${styleId}.xlsx`, { body: { styleId, sizeChartJson: view } });
295
381
  }
296
382
  },
297
383
  {
@@ -360,7 +446,7 @@ const READ_TOOLS = [
360
446
  },
361
447
  {
362
448
  name: 'erp_order_sample_list',
363
- description: '查询订单轮次(可为空,可不建轮次)',
449
+ description: '查询订单轮次(含 styleId 主款、roundStyleId 克隆款、priority;可不建轮次)',
364
450
  permission: 'erp:order:query',
365
451
  inputSchema: {
366
452
  orderId: idField.describe('订单 ID,字符串')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yindie-erp-mcp",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "银蝶 ERP 独立 MCP 服务,通过 RuoYi API 提供查询与写操作能力",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",