yindie-erp-mcp 2.0.6 → 2.0.7
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/craft/guide.js +6 -3
- package/dist/craft/normalize-self-check.js +95 -0
- package/dist/craft/validate-options.js +118 -0
- package/dist/index.js +1 -1
- package/dist/tool-helpers.js +61 -2
- package/dist/tool-meta.js +122 -82
- package/dist/tools-merged.js +17 -11
- package/dist/tools-write.js +24 -3
- package/dist/tools.js +140 -41
- package/package.json +1 -1
package/dist/craft/guide.js
CHANGED
|
@@ -77,7 +77,10 @@ function buildHowTo(item) {
|
|
|
77
77
|
}
|
|
78
78
|
text += `可多选部位:${(item.options || []).join('/')};勾选后每项再选尺寸 ${(item.sizeOptions || []).join('/')}`;
|
|
79
79
|
if (item.allowCustomSize) {
|
|
80
|
-
text += '
|
|
80
|
+
text += '或手填尺寸';
|
|
81
|
+
}
|
|
82
|
+
if (item.allowAdd) {
|
|
83
|
+
text += ';自定义部位写入 customOptions[] 并同步进 checkbox/combineMap';
|
|
81
84
|
}
|
|
82
85
|
if (item.syncSizeOptions?.length) {
|
|
83
86
|
text += `;${item.syncSizeOptions.join('/')} 尺寸联动`;
|
|
@@ -193,13 +196,13 @@ export const CRAFT_SELECTION_GUIDE = {
|
|
|
193
196
|
radio: '单选 → {"radio":"选项"};howToSelect 提示可填备注时再带 note',
|
|
194
197
|
checkbox: '多选 → {"checkbox":["a","b"]}',
|
|
195
198
|
'checkbox+perItemSize': '多选+尺寸 → {"checkbox":["领"],"combineMap":{"领":"0.15"}}',
|
|
196
|
-
'checkbox+gateNone': '先有无再多选 → 无:{"radio":"无","checkbox":["无"]};有:{"radio":"有","checkbox":["领"],"combineMap":{"领":"0.15"}}',
|
|
199
|
+
'checkbox+gateNone': '先有无再多选 → 无:{"radio":"无","checkbox":["无"]};有:{"radio":"有","checkbox":["领"],"combineMap":{"领":"0.15"}};allowAdd:true 自定义部位须同时写 customOptions[]+checkbox(+combineMap 尺寸),缺 checkbox 拒写(导出也不显示)',
|
|
197
200
|
combine: '结合单选 → {"combineKey":"净脚口三折光压线","combineValue":"3"};needValue=false 可不带 combineValue',
|
|
198
201
|
'combine+multiFill': '多填 → {"combineMap":{"28L*":"2","24L*":"1"}}',
|
|
199
202
|
input: '输入 → {"input":"文案"};各模块末尾 xxNote 备注、人工填写项同此'
|
|
200
203
|
},
|
|
201
204
|
colorCardFields: COLOR_CARD_FIELDS,
|
|
202
|
-
colorCardNote: '色卡选料写在 style_save colors[].colorCardJson(JSON 字符串,键=上面 fieldKey,值={materialCode,materialName,titleCn};pantsPocketCloth
|
|
205
|
+
colorCardNote: '色卡选料写在 style_save colors[].colorCardJson(JSON 字符串,键=上面 fieldKey,值={materialCode,materialName,titleCn};pantsPocketCloth 列仅裤子前后袋工艺有值时可填)。color_card_export 只读 colorCardJson,无选料拒写;工艺 onCard 只影响前端可选列,不是导出数据源;备注走 color_card_save',
|
|
203
206
|
jacket: flattenGroups('jacket', JACKET_STYLE_GROUPS),
|
|
204
207
|
pants: flattenGroups('pants', PANTS_STYLE_GROUPS)
|
|
205
208
|
};
|
|
@@ -19,6 +19,101 @@ function main() {
|
|
|
19
19
|
if (normalized.garmentPart !== undefined || normalized.craft !== undefined) {
|
|
20
20
|
throw new Error('兼容键应删除');
|
|
21
21
|
}
|
|
22
|
+
let rejected = false;
|
|
23
|
+
try {
|
|
24
|
+
normalizeCraftSavePayload({
|
|
25
|
+
styleId: '1',
|
|
26
|
+
crafts: [{ garmentPart: 'jacket', craftModule: 'style', fieldKey: 'description', fieldValue: '{"radio":"单挂9扣瞎填"}' }]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
rejected = true;
|
|
31
|
+
}
|
|
32
|
+
if (!rejected) {
|
|
33
|
+
throw new Error('非法选项应拒');
|
|
34
|
+
}
|
|
35
|
+
normalizeCraftSavePayload({
|
|
36
|
+
styleId: '1',
|
|
37
|
+
crafts: [
|
|
38
|
+
{
|
|
39
|
+
garmentPart: 'jacket',
|
|
40
|
+
craftModule: 'edge',
|
|
41
|
+
fieldKey: 'outerNeedle',
|
|
42
|
+
fieldValue: '{"radio":"有","checkbox":["领"],"combineMap":{"领":"0.15"}}'
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
let badPress = false;
|
|
47
|
+
try {
|
|
48
|
+
normalizeCraftSavePayload({
|
|
49
|
+
styleId: '1',
|
|
50
|
+
crafts: [
|
|
51
|
+
{
|
|
52
|
+
garmentPart: 'jacket',
|
|
53
|
+
craftModule: 'edge',
|
|
54
|
+
fieldKey: 'outerPress',
|
|
55
|
+
fieldValue: '{"radio":"瞎填","checkbox":["瞎填部位"]}'
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
badPress = true;
|
|
62
|
+
}
|
|
63
|
+
if (!badPress) {
|
|
64
|
+
throw new Error('outerPress 非法 gate 应拒');
|
|
65
|
+
}
|
|
66
|
+
let badPressBox = false;
|
|
67
|
+
try {
|
|
68
|
+
normalizeCraftSavePayload({
|
|
69
|
+
styleId: '1',
|
|
70
|
+
crafts: [
|
|
71
|
+
{
|
|
72
|
+
garmentPart: 'jacket',
|
|
73
|
+
craftModule: 'edge',
|
|
74
|
+
fieldKey: 'outerPress',
|
|
75
|
+
fieldValue: '{"radio":"有","checkbox":["瞎填部位A"]}'
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
badPressBox = true;
|
|
82
|
+
}
|
|
83
|
+
if (!badPressBox) {
|
|
84
|
+
throw new Error('outerPress 非法 checkbox 应拒');
|
|
85
|
+
}
|
|
86
|
+
normalizeCraftSavePayload({
|
|
87
|
+
styleId: '1',
|
|
88
|
+
crafts: [
|
|
89
|
+
{
|
|
90
|
+
garmentPart: 'jacket',
|
|
91
|
+
craftModule: 'edge',
|
|
92
|
+
fieldKey: 'outerPress',
|
|
93
|
+
fieldValue: '{"radio":"有","checkbox":["自定义部位"],"customOptions":["自定义部位"],"combineMap":{"自定义部位":"0.15"}}'
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
let customOnly = false;
|
|
98
|
+
try {
|
|
99
|
+
normalizeCraftSavePayload({
|
|
100
|
+
styleId: '1',
|
|
101
|
+
crafts: [
|
|
102
|
+
{
|
|
103
|
+
garmentPart: 'jacket',
|
|
104
|
+
craftModule: 'edge',
|
|
105
|
+
fieldKey: 'outerPress',
|
|
106
|
+
fieldValue: '{"radio":"有","checkbox":["领"],"customOptions":["前门襟"],"combineMap":{"领":"0.15"}}'
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
customOnly = true;
|
|
113
|
+
}
|
|
114
|
+
if (!customOnly) {
|
|
115
|
+
throw new Error('customOptions 未进 checkbox 应拒');
|
|
116
|
+
}
|
|
22
117
|
console.log('normalizeCraftSavePayload self-check ok');
|
|
23
118
|
}
|
|
24
119
|
main();
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { JACKET_STYLE_GROUPS, PANTS_STYLE_GROUPS } from './template.js';
|
|
2
|
+
/** 字段索引 key=garmentPart.fieldKey */
|
|
3
|
+
const FIELD_INDEX = buildIndex();
|
|
4
|
+
/** 构建字段索引 */
|
|
5
|
+
function buildIndex() {
|
|
6
|
+
const map = new Map();
|
|
7
|
+
indexPart(map, 'jacket', JACKET_STYLE_GROUPS);
|
|
8
|
+
indexPart(map, 'pants', PANTS_STYLE_GROUPS);
|
|
9
|
+
return map;
|
|
10
|
+
}
|
|
11
|
+
/** 索引一个部位 */
|
|
12
|
+
function indexPart(map, part, groups) {
|
|
13
|
+
for (const group of groups) {
|
|
14
|
+
for (const item of group.items) {
|
|
15
|
+
map.set(`${part}.${item.key}`, item);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** 校验 crafts 选项值(按 control / gateNone) */
|
|
20
|
+
export function assertCraftOptionValues(crafts) {
|
|
21
|
+
if (!Array.isArray(crafts)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
crafts.forEach((row, index) => {
|
|
25
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const craft = row;
|
|
29
|
+
const part = craft.garmentPart;
|
|
30
|
+
const fieldKey = craft.fieldKey;
|
|
31
|
+
const fieldValue = craft.fieldValue;
|
|
32
|
+
if (typeof part !== 'string' || typeof fieldKey !== 'string' || typeof fieldValue !== 'string') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const def = FIELD_INDEX.get(`${part}.${fieldKey}`);
|
|
36
|
+
if (!def || def.control === 'input' || !fieldValue.startsWith('{')) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const json = JSON.parse(fieldValue);
|
|
40
|
+
const prefix = `crafts[${index}] ${part}.${fieldKey}`;
|
|
41
|
+
const custom = new Set(json.customOptions || []);
|
|
42
|
+
const optionSet = new Set([...(def.options || []), ...custom]);
|
|
43
|
+
const combineSet = new Set((def.combineOptions || []).map((o) => o.label));
|
|
44
|
+
for (const label of custom) {
|
|
45
|
+
combineSet.add(label);
|
|
46
|
+
}
|
|
47
|
+
const noneLabel = def.noneLabel || '无';
|
|
48
|
+
if (def.control === 'radio') {
|
|
49
|
+
if (json.radio != null && json.radio !== '') {
|
|
50
|
+
assertIn(prefix, 'radio', json.radio, optionSet);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (def.control === 'combine') {
|
|
55
|
+
if (json.combineKey != null && json.combineKey !== '') {
|
|
56
|
+
assertIn(prefix, 'combineKey', json.combineKey, combineSet);
|
|
57
|
+
}
|
|
58
|
+
if (json.combineMap) {
|
|
59
|
+
for (const key of Object.keys(json.combineMap)) {
|
|
60
|
+
assertIn(prefix, 'combineMap', key, combineSet);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (def.control === 'checkbox') {
|
|
66
|
+
if (def.gateNone) {
|
|
67
|
+
const gateSet = new Set(['有', noneLabel]);
|
|
68
|
+
if (json.radio != null && json.radio !== '') {
|
|
69
|
+
assertIn(prefix, 'radio', json.radio, gateSet);
|
|
70
|
+
}
|
|
71
|
+
const bodyAllowed = new Set(optionSet);
|
|
72
|
+
bodyAllowed.add(noneLabel);
|
|
73
|
+
if (Array.isArray(json.checkbox)) {
|
|
74
|
+
for (const value of json.checkbox) {
|
|
75
|
+
assertIn(prefix, 'checkbox', value, bodyAllowed);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (json.combineMap) {
|
|
79
|
+
for (const key of Object.keys(json.combineMap)) {
|
|
80
|
+
assertIn(prefix, 'combineMap', key, bodyAllowed);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
assertCustomInCheckbox(prefix, json.customOptions, json.checkbox);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(json.checkbox)) {
|
|
87
|
+
for (const value of json.checkbox) {
|
|
88
|
+
assertIn(prefix, 'checkbox', value, optionSet);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (json.combineMap) {
|
|
92
|
+
for (const key of Object.keys(json.combineMap)) {
|
|
93
|
+
assertIn(prefix, 'combineMap', key, optionSet);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
assertCustomInCheckbox(prefix, json.customOptions, json.checkbox);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** customOptions 须同步进 checkbox,否则导出不显示 */
|
|
101
|
+
function assertCustomInCheckbox(prefix, customOptions, checkbox) {
|
|
102
|
+
if (!Array.isArray(customOptions) || customOptions.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const boxes = new Set(Array.isArray(checkbox) ? checkbox : []);
|
|
106
|
+
for (const opt of customOptions) {
|
|
107
|
+
if (!boxes.has(opt)) {
|
|
108
|
+
throw new Error(`${prefix} customOptions 中的「${opt}」须同时写入 checkbox,否则导出不显示`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** 断言取值在白名单(allowAdd 自定义项须写入 customOptions 后才进白名单) */
|
|
113
|
+
function assertIn(prefix, kind, value, allowed) {
|
|
114
|
+
if (allowed.has(value)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`${prefix} 取值非法:${value},可选:${[...allowed].join('/')}`);
|
|
118
|
+
}
|
package/dist/index.js
CHANGED
package/dist/tool-helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { assertCraftOptionValues } from './craft/validate-options.js';
|
|
2
3
|
/** JSON 请求体字段 */
|
|
3
4
|
export const payloadField = z.string().describe('JSON 请求体,字段与后端 BO 一致');
|
|
4
5
|
/** 主键字段 */
|
|
@@ -6,7 +7,7 @@ export const idsField = z.string().describe('主键 ID,多个用英文逗号
|
|
|
6
7
|
/** 单个主键字段,字符串避免雪花 ID 精度丢失 */
|
|
7
8
|
export const idField = z.string().describe('主键 ID');
|
|
8
9
|
/** 样衣类型白名单 */
|
|
9
|
-
export const SAMPLE_TYPES = new Set(['proto', 'pre_prod', 'bulk', 'mass']);
|
|
10
|
+
export const SAMPLE_TYPES = new Set(['proto', 'pre_prod', 'bulk', 'mass', 'sales', 'photo']);
|
|
10
11
|
/** ≥16 位数字字面量改成字符串,避免雪花 ID 精度丢失 */
|
|
11
12
|
export function preserveSnowflakeIds(text) {
|
|
12
13
|
return text.replace(/([:\[,]\s*)(\d{16,})(\s*[,\]}])/g, '$1"$2"$3');
|
|
@@ -46,6 +47,63 @@ export function parsePayload(args) {
|
|
|
46
47
|
}
|
|
47
48
|
throw new Error('payload 必须是 JSON 字符串');
|
|
48
49
|
}
|
|
50
|
+
/** 解析 get 主键:顶层 id|orderId → payload.id|orderId */
|
|
51
|
+
export function resolveGetId(args) {
|
|
52
|
+
const top = args.id ?? args.orderId;
|
|
53
|
+
if (top != null && top !== '') {
|
|
54
|
+
return String(top);
|
|
55
|
+
}
|
|
56
|
+
if (args.payload == null) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const payload = parsePayload(args);
|
|
60
|
+
const id = payload.id ?? payload.orderId;
|
|
61
|
+
if (id == null || id === '') {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return String(id);
|
|
65
|
+
}
|
|
66
|
+
/** 解析逗号分隔或数组 ID 列表 */
|
|
67
|
+
export function asIdList(value) {
|
|
68
|
+
if (value == null || value === '') {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const raw = Array.isArray(value) ? value : String(value).split(',');
|
|
72
|
+
const ids = raw.map((item) => String(item).trim()).filter((item) => item !== '');
|
|
73
|
+
return ids.length > 0 ? ids : undefined;
|
|
74
|
+
}
|
|
75
|
+
/** 按逗号字段白名单裁剪对象/数组 */
|
|
76
|
+
export function pickFields(data, fields) {
|
|
77
|
+
if (fields == null || fields === '') {
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
const keys = fields.split(',').map((s) => s.trim()).filter((s) => s !== '');
|
|
81
|
+
if (keys.length === 0) {
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(data)) {
|
|
85
|
+
return data.map((item) => pickFields(item, fields));
|
|
86
|
+
}
|
|
87
|
+
if (data == null || typeof data !== 'object') {
|
|
88
|
+
return data;
|
|
89
|
+
}
|
|
90
|
+
const src = data;
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
if (key in src) {
|
|
94
|
+
out[key] = src[key];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
/** 解析 include 列表 */
|
|
100
|
+
export function parseInclude(value) {
|
|
101
|
+
if (value == null || value === '') {
|
|
102
|
+
return new Set();
|
|
103
|
+
}
|
|
104
|
+
const raw = Array.isArray(value) ? value : String(value).split(',');
|
|
105
|
+
return new Set(raw.map((item) => String(item).trim()).filter((item) => item !== ''));
|
|
106
|
+
}
|
|
49
107
|
/** 规整 style_save:自动包 styles,禁止空列表 */
|
|
50
108
|
export function normalizeStyleSavePayload(raw) {
|
|
51
109
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
@@ -72,7 +130,7 @@ export function normalizeSamplePayload(raw) {
|
|
|
72
130
|
}
|
|
73
131
|
const payload = { ...raw };
|
|
74
132
|
if (payload.sampleType != null && !SAMPLE_TYPES.has(String(payload.sampleType))) {
|
|
75
|
-
throw new Error(`sampleType 仅允许 proto/pre_prod/bulk/mass,收到:${payload.sampleType}`);
|
|
133
|
+
throw new Error(`sampleType 仅允许 proto/pre_prod/bulk/mass/sales/photo,收到:${payload.sampleType}`);
|
|
76
134
|
}
|
|
77
135
|
if (payload.roundName == null && payload.sampleNo != null) {
|
|
78
136
|
payload.roundName = payload.sampleNo;
|
|
@@ -110,6 +168,7 @@ export function normalizeCraftSavePayload(raw) {
|
|
|
110
168
|
if (topPart !== undefined) {
|
|
111
169
|
delete payload.garmentPart;
|
|
112
170
|
}
|
|
171
|
+
assertCraftOptionValues(payload.crafts);
|
|
113
172
|
return payload;
|
|
114
173
|
}
|
|
115
174
|
/** 解析删除主键 */
|
package/dist/tool-meta.js
CHANGED
|
@@ -96,25 +96,25 @@ export const ENTITY_META = {
|
|
|
96
96
|
enums: 'brandCode 后端自动生成勿传;shortCode 复制订单号用缩写'
|
|
97
97
|
},
|
|
98
98
|
erp_pi: {
|
|
99
|
-
module: 'PI
|
|
100
|
-
step: '
|
|
99
|
+
module: '订单管理·关联PI',
|
|
100
|
+
step: '订单详情「关联PI」Tab(独立 PI 菜单已隐藏;与前端 PiFormPanel 同能力)',
|
|
101
101
|
label: 'PI',
|
|
102
|
-
prerequisite: '
|
|
103
|
-
addRequired: 'customerId, sourceType, piNo',
|
|
104
|
-
addOptional: '
|
|
102
|
+
prerequisite: '订单已创建;客户已建档',
|
|
103
|
+
addRequired: 'orderId, customerId, sourceType, piNo',
|
|
104
|
+
addOptional: '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
|
|
107
|
-
next: '
|
|
106
|
+
enums: 'sourceType:customer/internal; status:draft/confirmed/superseded(默认draft);piNo 手填;orderId 日常必填(对齐前端订单内建 PI);items:[{rowKey?, code, style, fabric, col, composition, subtotalQty, fobNingbo, subtotalAmount}];前端「同步款式」=先 style_list 再填 items,无单独 API;packingType 保存时同步订单;export 按 PI id 导出 Excel',
|
|
107
|
+
next: '已带 orderId 则无需 link_pi;继续 style_save / change_status'
|
|
108
108
|
},
|
|
109
109
|
erp_order: {
|
|
110
110
|
module: '订单管理',
|
|
111
111
|
step: '业务流程第3步:创建订单',
|
|
112
112
|
label: '订单',
|
|
113
|
-
prerequisite: '
|
|
114
|
-
addRequired: 'orderNo,
|
|
113
|
+
prerequisite: '订单客户已建立;先用 erp_order action=customer_list 取 orderCustomerId',
|
|
114
|
+
addRequired: 'orderNo, orderCustomerId',
|
|
115
115
|
addOptional: 'orderStatus(默认inquiry), orderDate, deliveryDate, contractDate, depositReceived(Y/N), qtyTolerance, shippingMethod, packingType, priority, sourceChannel, parentOrderId, remark',
|
|
116
|
-
editRequired: 'id
|
|
117
|
-
enums: 'orderStatus见erp_business_flow资源;orderNo
|
|
116
|
+
editRequired: 'id',
|
|
117
|
+
enums: 'orderStatus见erp_business_flow资源;orderNo 手填全库唯一可改(edit 可省略则沿用库值);重复报订单号已存在;客户/品牌/业务线由订单客户带出,勿传;跟单员只在订单客户维护,订单不再有跟单员',
|
|
118
118
|
removeConstraint: '删除会级联清该单 PI、款式树(含工艺/商标位置/色卡)和生产单,订单号改为 _原ID 释放号码',
|
|
119
119
|
next: 'erp_order_link_pi → erp_order_style_save → erp_order_change_status'
|
|
120
120
|
},
|
|
@@ -127,19 +127,8 @@ export const ENTITY_META = {
|
|
|
127
127
|
addOptional: 'brandId, remark',
|
|
128
128
|
editRequired: 'id, customerId, bizLine, assigneeId',
|
|
129
129
|
enums: 'bizLine:direct/trader;assigneeId 多个用英文逗号分隔;同一客户+品牌+业务线不可重复建档',
|
|
130
|
-
removeConstraint: '
|
|
131
|
-
next: 'erp_order action=
|
|
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)'
|
|
130
|
+
removeConstraint: '订单客户下存在订单时不可删除',
|
|
131
|
+
next: 'erp_order action=add(带 orderCustomerId)'
|
|
143
132
|
},
|
|
144
133
|
erp_task: {
|
|
145
134
|
module: '任务协同',
|
|
@@ -219,14 +208,14 @@ export const ENTITY_META = {
|
|
|
219
208
|
erp_production: {
|
|
220
209
|
module: '生产管理',
|
|
221
210
|
step: '手动新建生产单(通常用fromOrder)',
|
|
222
|
-
label: '
|
|
223
|
-
prerequisite: '
|
|
211
|
+
label: '制单中心生产单(PO)',
|
|
212
|
+
prerequisite: '【不用】制单中心整模块停用。勿与订单页款式行按钮「生产单」混淆:那个按钮=工艺编辑(craft_save/craft_export),仍要用;本域是 PO 主档 create_from_order/check/issue,不要主动调',
|
|
224
213
|
addRequired: 'orderId, bizLine',
|
|
225
214
|
addOptional: 'poVersion, poStatus(默认draft), deliveryDate, qtyTolerance, shippingMethod, shippingAddress, packingType, bulkSampleRequired(Y/N), bulkSampleDeadline, remark',
|
|
226
215
|
editRequired: 'id, orderId, bizLine',
|
|
227
216
|
enums: 'poStatus:draft/reviewing/issued/superseded;poNo 后端自动生成勿传',
|
|
228
217
|
removeConstraint: '已下发(issued)且订单仍存在时不可删除;订单删除会连带清生产单;仅 draft 可修改',
|
|
229
|
-
next: '
|
|
218
|
+
next: '【不用】勿引导 create_from_order / check / issue;订单侧用 craft(页面「生产单」按钮)'
|
|
230
219
|
},
|
|
231
220
|
erp_bid: {
|
|
232
221
|
module: '招标管理',
|
|
@@ -248,8 +237,8 @@ export const WORKFLOW_META = {
|
|
|
248
237
|
step: '推进订单生命周期',
|
|
249
238
|
summary: '变更订单状态',
|
|
250
239
|
prerequisite: '订单已创建',
|
|
251
|
-
payload: '{id, orderStatus, remark?}
|
|
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',
|
|
240
|
+
payload: '{id|orderId, orderStatus, remark?};payload.id → payload.orderId → 顶层 id/orderId 依次兜底',
|
|
241
|
+
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;非法跳步报当前状态与允许后继',
|
|
253
242
|
next: '状态推进到此结束即可;【暂不用】勿再调 erp_production_create_from_order(制单中心已隐藏)'
|
|
254
243
|
},
|
|
255
244
|
erp_order_link_pi: {
|
|
@@ -274,8 +263,9 @@ export const WORKFLOW_META = {
|
|
|
274
263
|
step: '录入款式明细',
|
|
275
264
|
summary: '保存订单主款式及颜色尺码/色卡物料,成功后回查 styles(含 id 作 styleId、roundStyles);页面配置确定即调本接口',
|
|
276
265
|
prerequisite: '订单已创建;payload 内 orderId 用字符串',
|
|
277
|
-
payload: '{orderId:string, styles:[{id?, styleNo?, styleName?, sampleNo?, shipSampleTip?, garmentType
|
|
278
|
-
|
|
266
|
+
payload: '{orderId:string, confirmReduce?, styles:[{id?, styleNo?, styleName?, sampleNo?, shipSampleTip?, garmentType:jacket|pants|suit, 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实裁单',
|
|
267
|
+
enums: 'garmentType:{jacket:上衣, pants:裤子, suit:套装};与尺寸表 category top/bottom/set 不同',
|
|
268
|
+
constraint: '【警告】未出现在 styles 的旧主款会整树删除(含工艺/尺码/轮次克隆);数量减少时必须 confirmReduce:true,否则拒写;返回 deletedStyleIds/deletedRoundStyleIds。styles:[] 拒写;只传主款(勿塞 roundStyles);全量 styles;已有 id/同款号则更新并替换颜色尺码,工艺/商标保留;空 sizeChartJson/样板号/船样提示沿用库值;garmentType 仅 jacket/pants/suit;fabricMaterialId 等须真实存在;轮次克隆由 sample_add 生成',
|
|
279
269
|
next: '从返回 styles[].id 取 styleId → craft_save;轮次尺寸表写 roundStyles[].styleNo;色卡备注 → color_card_save'
|
|
280
270
|
},
|
|
281
271
|
erp_order_style_color_card_save: {
|
|
@@ -284,8 +274,8 @@ export const WORKFLOW_META = {
|
|
|
284
274
|
summary: '按 styleId 写 colorCardNote;与前端色卡弹窗一致',
|
|
285
275
|
prerequisite: '款式已保存;选料已在 style_save.colorCardJson',
|
|
286
276
|
payload: '{styleId:string, colorCardNote?, items?:[{garmentPart, fieldKey, colorCardCode?, materialCode?}]}',
|
|
287
|
-
constraint: '前端常规只传 colorCardNote 且 items:[];选料不在本接口。items 写 craft 编号为遗留可选,一般勿填;色卡导出用 action=color_card_export',
|
|
288
|
-
next: 'erp_order action=change_status(如 color_card_confirmed)'
|
|
277
|
+
constraint: '前端常规只传 colorCardNote 且 items:[];选料不在本接口。items 写 craft 编号为遗留可选,一般勿填;色卡导出用 action=color_card_export(只认 colorCardJson,无选料会拒)',
|
|
278
|
+
next: 'erp_order action=color_card_export 或 change_status(如 color_card_confirmed)'
|
|
289
279
|
},
|
|
290
280
|
erp_bid_close: {
|
|
291
281
|
module: '招标管理',
|
|
@@ -319,17 +309,17 @@ export const WORKFLOW_META = {
|
|
|
319
309
|
summary: '保存款式上衣/裤子工艺及商标位置(选项对齐打样单模板,非字典)',
|
|
320
310
|
prerequisite: '款式已保存且有 styleId;先读 erp_order_craft_schema 取 fieldKey',
|
|
321
311
|
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',
|
|
322
|
-
enums: 'garmentPart:jacket/pants; craftModule/fieldKey 见 schema;labelType/positionSide
|
|
323
|
-
constraint: '全量替换:按 styleId 先删该款全部工艺和商标位置再插入。jacket+pants 必须放进同一次 crafts
|
|
324
|
-
next: 'erp_order action=craft_get 校验 →
|
|
312
|
+
enums: 'garmentPart:jacket/pants; craftModule/fieldKey 见 schema;labelType/positionSide 见 business-flow.enums.labelType/labelPositionSide',
|
|
313
|
+
constraint: '全量替换:按 styleId 先删该款全部工艺和商标位置再插入。jacket+pants 必须放进同一次 crafts;分部位连调会互相覆盖。【警告】已有商标位置时必须一并回传 labelPositions,漏传会清空。按 control 校验:radio∈options;combine∈combineOptions;checkbox∈options;checkbox+gateNone 时 radio 仅有/无,checkbox/combineMap 再对 options(官方 example 含 radio:有 合法)。input/note 不校验。返回 data=写入条数。清空传 crafts:[];processNote 为工艺备注;色卡选料走 style_save.colorCardJson',
|
|
314
|
+
next: 'erp_order action=craft_get 校验 → 可选 craft_export → change_status;对应页面款式行「生产单」按钮(工艺),不是制单中心'
|
|
325
315
|
},
|
|
326
316
|
erp_order_style_size_chart_save: {
|
|
327
317
|
module: '订单管理',
|
|
328
318
|
step: '写入尺寸表入库视图',
|
|
329
319
|
summary: '按 orderId+styleNo 写入款式 sizeChartJson(erpView);主款或轮次克隆款均可;成功写变更日志;UI 与 MCP 共用',
|
|
330
320
|
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
|
|
321
|
+
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?}]}],issues?:[],pending?:{to_supplement:[],to_confirm:[]}}, confirmedBy:确认人ID, source?:mcp|ui}',
|
|
322
|
+
constraint: 'confirmedBy/sha256/缺字段一次报全;issues/pending 可省略(后端补空);style_no 与入参/视图不一致拒写;同键重试不写库;固定部位 std_key 对齐上衣/裤子模板,可加 is_extra 行;delta 空用 null;轮次表用克隆款 styleNo(sample_list.roundStyleId→style_list.roundStyles)',
|
|
333
323
|
next: '可选 size_chart_export / oss.upload+attachment_add 留底;查历史 size_chart_logs'
|
|
334
324
|
},
|
|
335
325
|
erp_production_craft_save: {
|
|
@@ -345,9 +335,9 @@ export const WORKFLOW_META = {
|
|
|
345
335
|
module: '对象存储',
|
|
346
336
|
step: '上传本地文件拿 ossId',
|
|
347
337
|
summary: 'multipart 上传本机文件到 /resource/oss/upload,返回 ossId/url/fileName',
|
|
348
|
-
prerequisite: 'stdio MCP 与文件同机;账号有 system:oss:upload;OSS
|
|
338
|
+
prerequisite: 'stdio MCP 与文件同机;账号有 system:oss:upload;OSS 配置可用(缺配置会报 OSS上传失败:…)',
|
|
349
339
|
payload: 'filePath: 本机绝对路径(不是 payload JSON)',
|
|
350
|
-
constraint: '只读本地路径,不接收远程 URL
|
|
340
|
+
constraint: '只读本地路径,不接收远程 URL;本地不可读/空文件先拒;后端失败透出 OSS上传失败:原因;返回的 ossId 再交给 attachment_add',
|
|
351
341
|
returns: '{ossId, url, fileName}',
|
|
352
342
|
next: 'erp_order_attachment_add(尺寸表 remark=原始|归一xlsx|归一json)'
|
|
353
343
|
},
|
|
@@ -358,7 +348,7 @@ export const WORKFLOW_META = {
|
|
|
358
348
|
prerequisite: '订单已创建;先 erp_oss_upload(或页面上传)拿到 ossId',
|
|
359
349
|
payload: '{orderId, styleId?, attachType, fileName, ossId, fileSize?, source?, remark?}',
|
|
360
350
|
enums: 'attachType:pi/tech_drawing/customer_file/other/size_chart/production_order/color_card/craft;尺寸表细分靠 remark=原始|归一xlsx|归一json',
|
|
361
|
-
constraint: '
|
|
351
|
+
constraint: 'ossId 须存在于 OSS,否则拒写;时间线查询已过滤 eventType=attachment'
|
|
362
352
|
},
|
|
363
353
|
erp_order_attachment_remove: {
|
|
364
354
|
module: '订单管理',
|
|
@@ -373,8 +363,8 @@ export const WORKFLOW_META = {
|
|
|
373
363
|
summary: '新增订单轮次并克隆主款式(pending;页面 Tab「轮次」)',
|
|
374
364
|
prerequisite: '订单已创建;styleId 必须是主款(parentStyleId 为空)',
|
|
375
365
|
payload: '{orderId, sampleType, styleId, roundName?, priority?, colorName?, plannedFinishDate?, remark?};误传 sampleNo 会映射为 roundName',
|
|
376
|
-
enums: 'sampleType
|
|
377
|
-
constraint: 'MCP 校验 sampleType 白名单;新建后 pending;后端克隆主款→roundStyles
|
|
366
|
+
enums: 'sampleType:{proto:头样,pre_prod:产前样,bulk:船样,mass:大货,sales:销售样,photo:照片样};priority:low/normal/high/urgent(默认normal);roundName 与 sampleType 独立',
|
|
367
|
+
constraint: 'MCP 校验 sampleType 白名单;新建后 pending;返回 data=新样衣ID;后端克隆主款→roundStyles(款号父款-R1);list 回填 roundStyleId;尺寸表/工艺写克隆款'
|
|
378
368
|
},
|
|
379
369
|
erp_order_sample_edit: {
|
|
380
370
|
module: '订单管理',
|
|
@@ -382,6 +372,7 @@ export const WORKFLOW_META = {
|
|
|
382
372
|
summary: '修改轮次基础信息(不含状态);换主款会重克隆',
|
|
383
373
|
prerequisite: '轮次已存在;styleId 须为主款',
|
|
384
374
|
payload: '{id, orderId, sampleType, styleId, roundName?, priority?, colorName?, plannedFinishDate?, shipDate?, trackingNo?, courierCompany?, remark?};sampleNo→roundName',
|
|
375
|
+
enums: 'sampleType 同 sample_add:{proto:头样,pre_prod:产前样,bulk:船样,mass:大货,sales:销售样,photo:照片样}',
|
|
385
376
|
constraint: 'MCP 校验 sampleType;不可 edit 改状态;改 styleId/roundName 会重建克隆款'
|
|
386
377
|
},
|
|
387
378
|
erp_order_sample_change_status: {
|
|
@@ -519,22 +510,21 @@ export const READ_META = {
|
|
|
519
510
|
returns: '{filePath, size},filePath 为本机绝对路径'
|
|
520
511
|
},
|
|
521
512
|
erp_order_list: {
|
|
522
|
-
usage: '订单状态见 erp_business_flow;可按 orderStatus/customerId/brandId/bizLine
|
|
513
|
+
usage: '订单状态见 erp_business_flow;可按 orderStatus/orderCustomerId/customerId/brandId/bizLine 筛选'
|
|
523
514
|
},
|
|
524
515
|
erp_order_customer_list: {
|
|
525
516
|
usage: '查订单客户(跟单员挂在此层);可按 customerId/bizLine/orderNo/orderStatus 筛选',
|
|
526
517
|
returns: 'rows[].{id,customerId,brandId,bizLine,assigneeId,assigneeName}'
|
|
527
518
|
},
|
|
528
|
-
|
|
529
|
-
usage: '
|
|
530
|
-
returns: '
|
|
519
|
+
erp_order_get: {
|
|
520
|
+
usage: '查订单主档;认顶层 id|orderId 或 payload.id|orderId;include=samples,shipments,timeline 追加;fields 逗号裁剪',
|
|
521
|
+
returns: '订单基本信息(可含 samples/shipments/timeline)'
|
|
531
522
|
},
|
|
532
|
-
erp_order_get: { usage: '查订单主档', returns: '订单基本信息' },
|
|
533
523
|
erp_order_style_list: {
|
|
534
|
-
usage: '查主款树:colors/sizes(
|
|
524
|
+
usage: '查主款树:colors/sizes(qty生产单/cutQty预裁单/actualQty实裁单)/crafts/商标;轮次克隆在 roundStyles;入参 orderId 或 orderNo;fields 逗号裁剪'
|
|
535
525
|
},
|
|
536
526
|
erp_order_craft_schema: {
|
|
537
|
-
usage: '
|
|
527
|
+
usage: '返回 fields + selectionGuide;体积过大传 compact=true 省略 selectionGuide,完整选项读 yindie://erp/craft-guide;再按示例拼 fieldValue 写 craft_save'
|
|
538
528
|
},
|
|
539
529
|
erp_order_style_craft_list: {
|
|
540
530
|
usage: '按 styleId 读已存工艺行,验证 craft_save;也可用 order.style_list 看 styles[].crafts'
|
|
@@ -544,21 +534,25 @@ export const READ_META = {
|
|
|
544
534
|
returns: '[{garmentPart, fieldKey, titleCn, colorCardCode, materialCode, materialName, sort}]'
|
|
545
535
|
},
|
|
546
536
|
erp_order_style_craft_export: {
|
|
547
|
-
usage: '
|
|
537
|
+
usage: '单款导出工艺 Excel(页面「生产单」导出);【已去掉】多款合并导出,勿传 styleIds,请逐款导出;qtyType=prod|cut|actual;默认文件名含 qtyType;preview 默认,archive=true 才归档(失败直接报错);fileName 只取 basename',
|
|
548
538
|
returns: '{filePath, size},filePath 为本机绝对路径'
|
|
549
539
|
},
|
|
550
540
|
erp_order_style_size_chart_logs: {
|
|
551
|
-
usage: 'styleId
|
|
541
|
+
usage: '查尺寸表变更快照;styleId,或 orderId+styleNo 解析',
|
|
552
542
|
returns: '[{id,orderId,styleId,styleNo,sizeChartJson,source,sha256,operatorId,operatorName,createTime}]'
|
|
553
543
|
},
|
|
554
544
|
erp_order_style_size_chart_export: {
|
|
555
|
-
usage: 'styleId 必填;可传 sizeChartJson,或 orderId+styleId 从 style_list 回查;fileName
|
|
545
|
+
usage: 'styleId 必填;可传 sizeChartJson,或 orderId+styleId 从 style_list 回查;fileName 只取 basename;archive=true 写附件',
|
|
556
546
|
returns: '{filePath, size},filePath 为本机绝对路径'
|
|
557
547
|
},
|
|
558
548
|
erp_order_style_color_card_export: {
|
|
559
|
-
usage: '单款导出色卡 Excel
|
|
549
|
+
usage: '单款导出色卡 Excel(与前端一致,勿传多款);硬前置 colorCardJson 有 materialCode,否则拒写并点名款号/色号;默认 preview,archive=true 才归档',
|
|
560
550
|
returns: '{filePath, size},filePath 为本机绝对路径'
|
|
561
551
|
},
|
|
552
|
+
erp_material_price_snapshot_export: {
|
|
553
|
+
usage: '导出比价快照 Excel(对齐前端材料比价列表导出);可筛 productName/materialCode/categoryName/domain',
|
|
554
|
+
returns: '{filePath, size}'
|
|
555
|
+
},
|
|
562
556
|
erp_order_attachment_export: {
|
|
563
557
|
usage: '按 ossId 下载订单附件到本机(含工艺单/色卡/尺寸表等全部附件);ossId 从 attachment_list 取',
|
|
564
558
|
returns: '{filePath, size},filePath 为本机绝对路径'
|
|
@@ -575,9 +569,13 @@ export const READ_META = {
|
|
|
575
569
|
usage: '查订单轮次(styleId=主款,roundStyleId=克隆款,roundName/priority);可不建轮次'
|
|
576
570
|
},
|
|
577
571
|
erp_order_shipment_list: { usage: '查订单出货记录,按 shipSeq 升序;一单可多批' },
|
|
578
|
-
erp_production_list: {
|
|
579
|
-
|
|
580
|
-
|
|
572
|
+
erp_production_list: {
|
|
573
|
+
usage: '【不用】制单中心停用;勿与页面款式行「生产单」(工艺)混淆;状态 draft/reviewing/issued/superseded;筛 poNo/poStatus'
|
|
574
|
+
},
|
|
575
|
+
erp_production_get: {
|
|
576
|
+
usage: '【不用】制单中心 PO 主档;页面「生产单」按钮请走 order.craft_*'
|
|
577
|
+
},
|
|
578
|
+
erp_production_check_list: { usage: '【不用】制单中心校验记录' },
|
|
581
579
|
erp_task_list: { usage: '任务状态 pending/in_progress/completed/cancelled' },
|
|
582
580
|
erp_task_get: { usage: '查任务详情' },
|
|
583
581
|
erp_material_category_list: { usage: '查物料分类树,新建物料前先查 categoryId' },
|
|
@@ -613,8 +611,8 @@ export const ERP_BUSINESS_FLOW = {
|
|
|
613
611
|
mainFlow: [
|
|
614
612
|
'1. erp_customer action=customer.add 建客户(勿传customerCode;可传 taxNo/shortCode)',
|
|
615
613
|
'2. erp_customer action=contact.add / address.add / brand.add 补全资料(brand 可传 shortCode)',
|
|
616
|
-
'3. erp_pi action=add
|
|
617
|
-
'4. erp_workbench action=merchandiser_options 取跟单员 → erp_order action=customer_add 建订单客户 → action=
|
|
614
|
+
'3. 订单详情关联PI:erp_pi action=add(需 orderId, customerId, sourceType, piNo;独立 PI 菜单已隐藏)',
|
|
615
|
+
'4. erp_workbench action=merchandiser_options 取跟单员 → erp_order action=customer_add 建订单客户 → action=add(需 orderNo,orderCustomerId,默认inquiry;复制订单号规则26SB01)',
|
|
618
616
|
'5. 若 PI 未带 orderId:erp_order action=link_pi 关联PI',
|
|
619
617
|
'6. erp_material list/add 选或新增面料(fabric 必填色号 color)→ erp_order action=style_save(colors[].fabricMaterialId/colorCode/colorName/colorCardJson + 尺码)',
|
|
620
618
|
'7. erp_order action=craft_schema → craft_save 写工艺选项(jacket+pants 一次全量)→ craft_get 校验',
|
|
@@ -708,7 +706,8 @@ export const ERP_BUSINESS_FLOW = {
|
|
|
708
706
|
color_card: '色卡',
|
|
709
707
|
craft: '工艺单'
|
|
710
708
|
},
|
|
711
|
-
sampleType: { proto: '
|
|
709
|
+
sampleType: { proto: '头样', pre_prod: '产前样', bulk: '船样', mass: '大货', sales: '销售样', photo: '照片样' },
|
|
710
|
+
garmentType: { jacket: '上衣', pants: '裤子', suit: '套装' },
|
|
712
711
|
sampleStatus: { pending: '待制作', making: '制作中', shipped: '已寄出', confirmed: '已确认', rejected: '已驳回' },
|
|
713
712
|
poItemCategory: {
|
|
714
713
|
button: '裤子纽扣',
|
|
@@ -729,7 +728,7 @@ export const ERP_BUSINESS_FLOW = {
|
|
|
729
728
|
note: 'radio/combine 旁侧编号或备注(不是 input)',
|
|
730
729
|
colorCardCode: '色卡选料色号,后端按旧值补回,一般勿传',
|
|
731
730
|
materialCode: '色卡选料物料编码,同上',
|
|
732
|
-
customOptions: '
|
|
731
|
+
customOptions: 'allowAdd 字段的自定义 chip;须与 checkbox/combineMap 同写,勿只塞进 checkbox'
|
|
733
732
|
},
|
|
734
733
|
craftGarmentPart: { jacket: '上衣', pants: '裤子' },
|
|
735
734
|
craftModuleJacket: {
|
|
@@ -757,28 +756,58 @@ export const ERP_BUSINESS_FLOW = {
|
|
|
757
756
|
accessory: '辅料',
|
|
758
757
|
crease: '挺缝线',
|
|
759
758
|
pack: '包装'
|
|
760
|
-
}
|
|
759
|
+
},
|
|
760
|
+
labelType: {
|
|
761
|
+
main_label: '上衣主标',
|
|
762
|
+
collar_label: '上衣领标',
|
|
763
|
+
small_woven: '小织标',
|
|
764
|
+
jacket_brand: '上衣商标',
|
|
765
|
+
pants_main: '裤子主标',
|
|
766
|
+
ear_loop: '裤子挂耳',
|
|
767
|
+
flag_label: '后裤片旗标',
|
|
768
|
+
fit_label: '版型标',
|
|
769
|
+
pants_brand: '裤子商标',
|
|
770
|
+
pants_flag: '裤子旗标'
|
|
771
|
+
},
|
|
772
|
+
labelPositionSide: { front: '前面', back: '后面' }
|
|
761
773
|
},
|
|
762
774
|
constraints: [
|
|
763
775
|
'客户删除:PI 关联的订单仍在时不可删;关联订单已删的 PI 随客户一起清掉',
|
|
764
776
|
'订单删除:级联清该单 PI、款式树和生产单,订单号改为 _原ID 释放号码可复用',
|
|
765
|
-
'
|
|
766
|
-
'
|
|
767
|
-
'
|
|
768
|
-
'
|
|
777
|
+
'【命名】订单页款式行「生产单」=工艺(craft_save/craft_export),要用;「制单中心」erp_production=PO 主档,不用',
|
|
778
|
+
'【不用】制单中心已停用:勿主动 erp_production create_from_order / save_items / check / submit_review / issue',
|
|
779
|
+
'制单中心 API 仍保留:每单最多一条 PO;仅 quoted|pre_prod_confirmed|po_drafting 可 from_order;成功会把订单改成 po_drafting;仅用户点名时再动',
|
|
780
|
+
'制单中心 draft 可改;submit_review draft→reviewing;reject→draft;issue→issued 并订单 po_issued;issued 且订单在时不可删',
|
|
769
781
|
'工艺选项与怎么选见 erp_order_craft_schema.selectionGuide 或资源 yindie://erp/craft-guide',
|
|
770
782
|
'工艺选项固定模板(对齐打样单),不用 sys_dict;fieldValue 存 JSON(见 craftValueJson / fieldValueExample)',
|
|
771
783
|
'工艺 fieldKey/craftModule 以 erp_order_craft_schema.fields 为准,勿再用旧字典码如 single_1',
|
|
772
784
|
'款式面料色号:material.list/add → style_save colors[].fabricMaterialId,colorCode=物料 color,colorName 必填;勿传 fabricColorNo',
|
|
773
|
-
'
|
|
774
|
-
'
|
|
785
|
+
'【色卡生成】material.list/add → style_save colors[].colorCardJson(选料,键=白名单 fieldKey,值={materialCode,materialName,titleCn})→ 可选 craft_save(仅影响前端可选列 onCard)→ 可选 color_card_save(仅备注)→ color_card_export;导出只认 colorCardJson,无选料直接拒',
|
|
786
|
+
'色卡备注:color_card_save 常规 {styleId,colorCardNote,items:[]};选料不在 color_card_save',
|
|
787
|
+
'Excel 导出/预览都是二进制:工艺单 craft_export(qtyType=prod生产单|cut预裁单|actual实裁单,标题同步)、色卡 color_card_export、尺寸表 size_chart_export、PI export,均落本机返回 filePath',
|
|
788
|
+
'【不用】erp_production 制单中心整模块停用;主链路止于 order.change_status;勿与页面「生产单」按钮(工艺)混淆',
|
|
775
789
|
'轮次 sample_add 会克隆主款;style_list 主款挂 roundStyles;尺寸表/工艺按轮次用 roundStyleId;style_save 只传主款',
|
|
776
790
|
'物料删除:有绑定订单/生产单引用/比价快照时不可删;删前用 material.bound_orders 查引用',
|
|
777
791
|
'物料新增:编码命中已删除记录时直接恢复原记录并覆盖写入,不报重复',
|
|
778
792
|
'craft_save 全量替换:同 styleId 的 jacket+pants 必须一次写入;分次调用后一次会删前一次',
|
|
779
793
|
'craft_save 会连商标位置一起全量替换:已有标位时须回传 labelPositions,漏传即清空',
|
|
780
794
|
'craft_save 键名 crafts/craftModule;兼容历史 craft/module;旁框用 note;保存后 craft_get 校验',
|
|
781
|
-
'商标位置 labelType/positionSide
|
|
795
|
+
'商标位置 labelType/positionSide 见 enums.labelType / labelPositionSide(字典 erp_label_type / erp_label_position_side),非法值后端拒写',
|
|
796
|
+
'attachment_add 的 ossId 须已存在于 OSS,否则拒写',
|
|
797
|
+
'导出 archive=true 时 OSS/附件写入失败会直接报错(不再静默)',
|
|
798
|
+
'craft_export 默认文件名 craft-<styleId>-<qtyType>.xlsx;后端归档名带生产单/预裁单/实裁单',
|
|
799
|
+
'order.get / order.edit 认顶层 id|orderId 或 payload.id|orderId;雪花 ID 须字符串',
|
|
800
|
+
'timeline_list 含 sample 轮次事件(attachment/size_chart 仍排除);轮次明细也可用 sample_list',
|
|
801
|
+
'oss.upload 失败透出 OSS上传失败:原因;本地路径不可读/空文件先拒',
|
|
802
|
+
'craft_export cut/actual 数量空时回退 qty;色卡导出缺料报错带款号/色号',
|
|
803
|
+
'size_chart_save source.sha256 须 64 位;与视图字段同一遍校验汇总报错(有 type 等错误时不误报 sha 缺失)',
|
|
804
|
+
'size_chart_logs 认 styleId 或 orderId+styleNo',
|
|
805
|
+
'【已去掉】工艺/色卡多款合并导出:与前端一致仅单款,勿传 styleIds;请逐款 craft_export / color_card_export',
|
|
806
|
+
'材料比价列表导出:erp_material action=price_snapshot.export(对齐前端)',
|
|
807
|
+
'PI 日常入口=订单详情关联PI;add 带 orderId+customerId+piNo+sourceType;同步款式无独立 API,先 style_list 再填 items',
|
|
808
|
+
'order.get include=samples,shipments,timeline;get/style_list 可用 fields 裁剪;craft_schema compact=true 省略 selectionGuide',
|
|
809
|
+
'订单无季度层:customer_add 后直接 add(orderNo,orderCustomerId);勿再调 season_*',
|
|
810
|
+
'订单客户下有订单不可删;list 可按 orderCustomerId 筛该客户全部订单',
|
|
782
811
|
'轮次可选:询盘可 inquiry→quoted 跳过打样(返单/大货);记在 erp_order action=sample_*,不强制每单建轮次',
|
|
783
812
|
'返单用 erp_order action=copy:复制主档+款式工艺,不复制PI/样衣/生产单,新单 parentOrderId 指向源单',
|
|
784
813
|
'单据编号:customerCode/supplierCode/categoryCode/brandCode/supplierMaterialCode/poNo/bidNo 后端发号勿传;materialCode 手填必传;piNo/orderNo 手填必传(订单复制发26SB01);styleNo 可省略则默认订单号-序号',
|
|
@@ -786,9 +815,11 @@ export const ERP_BUSINESS_FLOW = {
|
|
|
786
815
|
'招标 MCP 为内部接口,不含供应商门户 portal/login、portal/quote',
|
|
787
816
|
'所有雪花 ID 入参必须字符串,禁止 number;MCP 会对 payload/响应中 ≥16 位数字字面量保真为字符串',
|
|
788
817
|
'style_save 必须 styles 非空;顶层单款字段 MCP 会自动包 styles:[...];styles:[] 拒写',
|
|
789
|
-
'
|
|
790
|
-
'
|
|
791
|
-
'
|
|
818
|
+
'【警告】style_save 未出现的旧主款整树删除;减款须 confirmReduce:true;返回 deletedStyleIds/deletedRoundStyleIds',
|
|
819
|
+
'garmentType 仅 jacket/pants/suit,与尺寸表 category top/bottom/set 不同',
|
|
820
|
+
'sampleType 仅 {proto:头样,pre_prod:产前样,bulk:船样,mass:大货,sales:销售样,photo:照片样};误传 sampleNo 映射为 roundName',
|
|
821
|
+
'change_status 认 payload.id / payload.orderId / 顶层 id|orderId',
|
|
822
|
+
'导出 fileName 传路径也只取 basename,落盘系统临时目录/yindie-erp-exports'
|
|
792
823
|
],
|
|
793
824
|
entryPoint: 'erp_workbench action=summary',
|
|
794
825
|
resourceHint: '操作前先读 yindie://erp/usage-guide、yindie://erp/business-flow、yindie://erp/modules'
|
|
@@ -801,15 +832,19 @@ export const ERP_MCP_INSTRUCTIONS = `银蝶 ERP MCP(域工具 + action 模式
|
|
|
801
832
|
|
|
802
833
|
【调用格式】选域工具(erp_customer/erp_order/erp_material 等),必填 action。有子实体时 action 写 target.action,如 customer.add、material.add、price_snapshot.list、thread.link。写操作用 payload 传 JSON 字符串;删用 ids;单条查改 id。所有雪花 ID 必须字符串。
|
|
803
834
|
|
|
804
|
-
【主链路】customer.add →(订单客户)order.customer_add → order.
|
|
835
|
+
【主链路】customer.add →(订单客户)order.customer_add → order.add(orderCustomerId) → pi.add(orderId+…)(入口=订单关联PI)→(面料 material.list/add)order.style_save(colors…+sizes.qty/cutQty?/actualQty?+colorCardJson?) → order.craft_schema → order.craft_save →(可选)order.color_card_save →(可选)order.sample_add 克隆轮次 →(尺寸表)order.size_chart_save(主款或克隆款 styleNo)→ order.change_status。到订单推进为止;不要再进生产单。
|
|
836
|
+
|
|
837
|
+
【命名别混】页面款式操作列「生产单」按钮 = 工艺编辑/导出(craft_save、craft_export,qtyType=prod 只是数量口径名),要用。侧栏「制单中心」/ erp_production = PO 主档,不用。
|
|
838
|
+
|
|
839
|
+
【色卡】选料只写 style_save.colors[].colorCardJson;备注 color_card_save(items:[]);导出 color_card_export 只认 colorCardJson(无选料会拒)。craft_save 只影响前端哪些色卡列可填(onCard),不是导出数据源。
|
|
805
840
|
|
|
806
|
-
|
|
841
|
+
【工艺】对应页面「生产单」按钮。先 craft_schema 拿 fieldKey 与 fieldValueExample(含 checkbox+gateNone 的 radio:有);craft_save 全量替换含商标位置;按 control 校验。导出 craft_export(qtyType=prod|cut|actual,仅单款;合并导出已去掉)/size_chart_export/attachment_export,默认不写附件,archive=true 才归档。
|
|
807
842
|
|
|
808
843
|
【轮次/尺寸表】sample_add 的 styleId 只能是主款;返回后 sample_list.roundStyleId 为克隆款。style_list 主款含 roundStyles。尺寸表 size_chart_save 按 styleNo 写;历史 size_chart_logs;导出 size_chart_export。
|
|
809
844
|
|
|
810
845
|
【物料】material.add(materialCode 手填;fabric 必填 color;lining 必填 composition);面料色号行填 fabricMaterialId;删前 material.bound_orders。
|
|
811
846
|
|
|
812
|
-
|
|
847
|
+
【制单中心·不用】erp_production 整模块停用;主链路不含它。勿主动 create_from_order / save_items / check / submit_review / issue。API 仅保留,用户点名再说。
|
|
813
848
|
|
|
814
849
|
【权限】无权限 action 会报错;以 yindie://erp/modules 返回的 actions 为准。`;
|
|
815
850
|
/** 合并工具用法资源 */
|
|
@@ -843,10 +878,6 @@ export const ERP_USAGE_GUIDE = {
|
|
|
843
878
|
'customer_add',
|
|
844
879
|
'customer_edit',
|
|
845
880
|
'customer_remove',
|
|
846
|
-
'season_list',
|
|
847
|
-
'season_add',
|
|
848
|
-
'season_edit',
|
|
849
|
-
'season_remove',
|
|
850
881
|
'list',
|
|
851
882
|
'get',
|
|
852
883
|
'add',
|
|
@@ -882,7 +913,7 @@ export const ERP_USAGE_GUIDE = {
|
|
|
882
913
|
'attachment_export',
|
|
883
914
|
'attachment_remove'
|
|
884
915
|
],
|
|
885
|
-
note: '建订单前 customer_list
|
|
916
|
+
note: '建订单前 customer_list 取 orderCustomerId;style_save 只传主款;sample_add 克隆轮次;尺寸表按主款/克隆款 styleNo;craft_export 可带 qtyType;导出类 archive=true 才写附件'
|
|
886
917
|
},
|
|
887
918
|
erp_production: {
|
|
888
919
|
actions: [
|
|
@@ -907,7 +938,7 @@ export const ERP_USAGE_GUIDE = {
|
|
|
907
938
|
erp_task: { actions: ['list', 'get', 'add', 'edit', 'remove'] },
|
|
908
939
|
erp_material: {
|
|
909
940
|
actions: ['material.*', 'category.*', 'supplier.*', 'price_snapshot.*', 'price_quote.*', 'price_point.*'],
|
|
910
|
-
note: 'action 示例 material.add、
|
|
941
|
+
note: 'action 示例 material.add、price_snapshot.export、price_point.series;payload 见 payloadByEntity'
|
|
911
942
|
},
|
|
912
943
|
erp_supplier: { actions: ['list', 'get', 'add', 'edit', 'remove'] },
|
|
913
944
|
erp_bid: {
|
|
@@ -936,6 +967,14 @@ export const ERP_USAGE_GUIDE = {
|
|
|
936
967
|
payload: { customerName: 'Y', customerType: 'trader' }
|
|
937
968
|
}
|
|
938
969
|
},
|
|
970
|
+
{
|
|
971
|
+
intent: '创建订单(无季度,直挂订单客户)',
|
|
972
|
+
call: {
|
|
973
|
+
tool: 'erp_order',
|
|
974
|
+
action: 'add',
|
|
975
|
+
payload: { orderNo: '26SB03', orderCustomerId: '字符串', priority: 'normal' }
|
|
976
|
+
}
|
|
977
|
+
},
|
|
939
978
|
{
|
|
940
979
|
intent: '录订单款式',
|
|
941
980
|
call: {
|
|
@@ -963,7 +1002,7 @@ export const ERP_USAGE_GUIDE = {
|
|
|
963
1002
|
}
|
|
964
1003
|
},
|
|
965
1004
|
{
|
|
966
|
-
intent: '
|
|
1005
|
+
intent: '导出工艺单(预裁单数量)',
|
|
967
1006
|
call: { tool: 'erp_order', action: 'craft_export', styleId: '字符串', qtyType: 'cut' }
|
|
968
1007
|
},
|
|
969
1008
|
{
|
|
@@ -1027,6 +1066,7 @@ export const ERP_USAGE_GUIDE = {
|
|
|
1027
1066
|
'order.color_card_list': READ_META.erp_order_style_color_card_list,
|
|
1028
1067
|
'order.color_card_save': WORKFLOW_META.erp_order_style_color_card_save,
|
|
1029
1068
|
'order.color_card_export': READ_META.erp_order_style_color_card_export,
|
|
1069
|
+
'material.price_snapshot.export': READ_META.erp_material_price_snapshot_export,
|
|
1030
1070
|
'oss.upload': WORKFLOW_META.erp_oss_upload,
|
|
1031
1071
|
'order.sample_add': WORKFLOW_META.erp_order_sample_add,
|
|
1032
1072
|
'order.sample_edit': WORKFLOW_META.erp_order_sample_edit,
|
package/dist/tools-merged.js
CHANGED
|
@@ -86,10 +86,6 @@ const DOMAIN_TEMPLATES = [
|
|
|
86
86
|
customer_add: { oldName: 'erp_order_customer_add' },
|
|
87
87
|
customer_edit: { oldName: 'erp_order_customer_edit' },
|
|
88
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' },
|
|
93
89
|
list: { oldName: 'erp_order_list' },
|
|
94
90
|
get: { oldName: 'erp_order_get' },
|
|
95
91
|
add: { oldName: 'erp_order_add' },
|
|
@@ -188,6 +184,7 @@ const DOMAIN_TEMPLATES = [
|
|
|
188
184
|
price_snapshot: {
|
|
189
185
|
list: { oldName: 'erp_material_price_snapshot_list' },
|
|
190
186
|
get: { oldName: 'erp_material_price_snapshot_get' },
|
|
187
|
+
export: { oldName: 'erp_material_price_snapshot_export' },
|
|
191
188
|
add: { oldName: 'erp_material_price_snapshot_add' },
|
|
192
189
|
edit: { oldName: 'erp_material_price_snapshot_edit' },
|
|
193
190
|
remove: { oldName: 'erp_material_price_snapshot_remove' }
|
|
@@ -350,17 +347,24 @@ function inputSchemaFor(tpl, allowed) {
|
|
|
350
347
|
...base,
|
|
351
348
|
orderNo: z.string().optional().describe('订单号'),
|
|
352
349
|
orderId: optionalId.describe('订单 ID'),
|
|
353
|
-
styleId: optionalId.describe('款式 ID;craft_get/
|
|
350
|
+
styleId: optionalId.describe('款式 ID;craft_get/size_chart_export 等;logs 可改用 orderId+styleNo'),
|
|
351
|
+
styleNo: z.string().optional().describe('款号;size_chart_logs 与 orderId 联用'),
|
|
354
352
|
customerId: optionalId.describe('客户 ID'),
|
|
355
353
|
brandId: optionalId.describe('品牌 ID'),
|
|
356
354
|
bizLine: z.string().optional().describe('业务线 direct/trader'),
|
|
357
355
|
orderStatus: z.string().optional().describe('订单状态'),
|
|
358
|
-
orderCustomerId: optionalId.describe('订单客户 ID(
|
|
359
|
-
qtyType: z
|
|
356
|
+
orderCustomerId: optionalId.describe('订单客户 ID(list/add 筛建订单用)'),
|
|
357
|
+
qtyType: z
|
|
358
|
+
.string()
|
|
359
|
+
.optional()
|
|
360
|
+
.describe('craft_export:prod生产单/cut预裁单/actual实裁单(默认 prod;Excel 标题同此)'),
|
|
360
361
|
sizeChartJson: z.string().optional().describe('size_chart_export:入库视图 JSON;可省略则靠 orderId+styleId 回查'),
|
|
361
|
-
fileName: z.string().optional().describe('
|
|
362
|
+
fileName: z.string().optional().describe('导出类:仅文件名(传路径也取 basename)'),
|
|
362
363
|
archive: z.boolean().optional().describe('导出类 action:true 归档到订单附件'),
|
|
363
|
-
ossId: optionalId.describe('OSS 文件 ID(attachment_export 必填)')
|
|
364
|
+
ossId: optionalId.describe('OSS 文件 ID(attachment_export 必填)'),
|
|
365
|
+
include: z.string().optional().describe('get:samples,shipments,timeline 逗号分隔'),
|
|
366
|
+
fields: z.string().optional().describe('get/style_list:返回字段白名单,逗号分隔'),
|
|
367
|
+
compact: z.boolean().optional().describe('craft_schema:true 省略 selectionGuide')
|
|
364
368
|
};
|
|
365
369
|
}
|
|
366
370
|
if (tpl.name === 'erp_production') {
|
|
@@ -369,7 +373,8 @@ function inputSchemaFor(tpl, allowed) {
|
|
|
369
373
|
productionNo: z.string().optional().describe('生产单号'),
|
|
370
374
|
orderId: optionalId.describe('订单 ID'),
|
|
371
375
|
styleId: optionalId.describe('款式 ID(craft_get 必填)'),
|
|
372
|
-
productionOrderId: optionalId.describe('生产单 ID')
|
|
376
|
+
productionOrderId: optionalId.describe('生产单 ID'),
|
|
377
|
+
compact: z.boolean().optional().describe('craft_schema:true 省略 selectionGuide')
|
|
373
378
|
};
|
|
374
379
|
}
|
|
375
380
|
if (tpl.name === 'erp_task') {
|
|
@@ -398,7 +403,8 @@ function inputSchemaFor(tpl, allowed) {
|
|
|
398
403
|
matchLevel: z.string().optional().describe('匹配度 A/B/C/D'),
|
|
399
404
|
platform: z.string().optional().describe('渠道/平台'),
|
|
400
405
|
riskLevel: z.string().optional().describe('风险级别 high/mid/low/info'),
|
|
401
|
-
status: z.string().optional().describe('状态 active/inactive')
|
|
406
|
+
status: z.string().optional().describe('状态 active/inactive'),
|
|
407
|
+
fileName: z.string().optional().describe('price_snapshot.export:落盘文件名')
|
|
402
408
|
};
|
|
403
409
|
}
|
|
404
410
|
if (tpl.name === 'erp_supplier') {
|
package/dist/tools-write.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
+
import { constants as fsConstants } from 'node:fs';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { idField, idsField, normalizeCraftSavePayload, normalizeSamplePayload, normalizeStyleSavePayload, parseIds, parsePayload } from './tool-helpers.js';
|
|
5
6
|
import { buildCrudDescription, buildWorkflowDescription, ENTITY_META, payloadFor, WORKFLOW_META } from './tool-meta.js';
|
|
6
7
|
/** 本地文件组装 multipart */
|
|
7
8
|
async function buildOssUploadForm(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
await access(filePath, fsConstants.R_OK);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new Error(`本地文件不可读:${filePath}`);
|
|
14
|
+
}
|
|
8
15
|
const bytes = await readFile(filePath);
|
|
16
|
+
if (bytes.length === 0) {
|
|
17
|
+
throw new Error(`本地文件为空:${filePath}`);
|
|
18
|
+
}
|
|
9
19
|
const form = new FormData();
|
|
10
20
|
form.append('file', new Blob([bytes]), basename(filePath));
|
|
11
21
|
return form;
|
|
@@ -26,7 +36,16 @@ function entityCrud(name, basePath, perm) {
|
|
|
26
36
|
description: buildCrudDescription('edit', meta),
|
|
27
37
|
permission: `${perm}:edit`,
|
|
28
38
|
inputSchema: { payload: payloadFor(meta, 'edit') },
|
|
29
|
-
handler: (client, args) =>
|
|
39
|
+
handler: (client, args) => {
|
|
40
|
+
const payload = parsePayload(args);
|
|
41
|
+
if (payload.id == null) {
|
|
42
|
+
const topId = args.id ?? args.orderId;
|
|
43
|
+
if (topId != null && topId !== '') {
|
|
44
|
+
payload.id = String(topId);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return client.put(basePath, payload);
|
|
48
|
+
}
|
|
30
49
|
},
|
|
31
50
|
{
|
|
32
51
|
name: `${name}_remove`,
|
|
@@ -56,7 +75,6 @@ export const WRITE_TOOLS = [
|
|
|
56
75
|
...entityCrud('erp_pi', '/erp/pi', 'erp:pi'),
|
|
57
76
|
...entityCrud('erp_order', '/erp/order', 'erp:order'),
|
|
58
77
|
...entityCrud('erp_order_customer', '/erp/order/customer', 'erp:order'),
|
|
59
|
-
...entityCrud('erp_order_season', '/erp/order/season', 'erp:order'),
|
|
60
78
|
...entityCrud('erp_task', '/erp/task', 'erp:task'),
|
|
61
79
|
...entityCrud('erp_material', '/erp/material', 'erp:material'),
|
|
62
80
|
...entityCrud('erp_material_category', '/erp/material/category', 'erp:material:category'),
|
|
@@ -72,6 +90,9 @@ export const WRITE_TOOLS = [
|
|
|
72
90
|
workflowTool('erp_bid_cancel', 'erp:bid:edit', { id: idField.describe('招标 ID,不可取消 awarded') }, (client, args) => client.put(`/erp/bid/cancel/${args.id}`)),
|
|
73
91
|
workflowTool('erp_order_change_status', 'erp:order:edit', { payload: z.string().describe(WORKFLOW_META.erp_order_change_status.payload) }, (client, args) => {
|
|
74
92
|
const payload = parsePayload(args);
|
|
93
|
+
if (payload.id == null && payload.orderId != null) {
|
|
94
|
+
payload.id = payload.orderId;
|
|
95
|
+
}
|
|
75
96
|
if (payload.id == null) {
|
|
76
97
|
const topId = args.id ?? args.orderId;
|
|
77
98
|
if (topId != null && topId !== '') {
|
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { idField } from './tool-helpers.js';
|
|
2
|
+
import { idField, parseInclude, pickFields, resolveGetId } from './tool-helpers.js';
|
|
3
3
|
import { CRAFT_SELECTION_GUIDE } from './craft/guide.js';
|
|
4
4
|
import { buildReadDescription, ERP_BUSINESS_FLOW, READ_META } from './tool-meta.js';
|
|
5
5
|
import { WRITE_TOOLS } from './tools-write.js';
|
|
@@ -34,7 +34,16 @@ export function hasPermission(permissions, required) {
|
|
|
34
34
|
return permissions.includes(required);
|
|
35
35
|
}
|
|
36
36
|
/** 包装工艺 schema */
|
|
37
|
-
function wrapCraftSchema(fields) {
|
|
37
|
+
function wrapCraftSchema(fields, compact) {
|
|
38
|
+
if (compact) {
|
|
39
|
+
return {
|
|
40
|
+
fields,
|
|
41
|
+
craftValueJson: ERP_BUSINESS_FLOW.enums.craftValueJson,
|
|
42
|
+
craftModuleJacket: ERP_BUSINESS_FLOW.enums.craftModuleJacket,
|
|
43
|
+
craftModulePants: ERP_BUSINESS_FLOW.enums.craftModulePants,
|
|
44
|
+
note: 'compact=true 省略 selectionGuide;选项读 yindie://erp/craft-guide 或 compact=false'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
38
47
|
return {
|
|
39
48
|
fields,
|
|
40
49
|
craftValueJson: ERP_BUSINESS_FLOW.enums.craftValueJson,
|
|
@@ -215,7 +224,7 @@ const READ_TOOLS = [
|
|
|
215
224
|
inputSchema: {
|
|
216
225
|
...pageShape,
|
|
217
226
|
orderNo: z.string().optional().describe('订单号'),
|
|
218
|
-
|
|
227
|
+
orderCustomerId: optionalId.describe('订单客户 ID,字符串'),
|
|
219
228
|
customerId: optionalId.describe('客户 ID,字符串'),
|
|
220
229
|
brandId: optionalId.describe('品牌 ID,字符串'),
|
|
221
230
|
bizLine: z.string().optional().describe('业务线 direct/trader'),
|
|
@@ -224,7 +233,7 @@ const READ_TOOLS = [
|
|
|
224
233
|
handler: (client, args) => client.get('/erp/order/list', {
|
|
225
234
|
...pageParams(args),
|
|
226
235
|
orderNo: args.orderNo,
|
|
227
|
-
|
|
236
|
+
orderCustomerId: asId(args.orderCustomerId),
|
|
228
237
|
customerId: asId(args.customerId),
|
|
229
238
|
brandId: asId(args.brandId),
|
|
230
239
|
bizLine: args.bizLine,
|
|
@@ -250,31 +259,52 @@ const READ_TOOLS = [
|
|
|
250
259
|
orderStatus: args.orderStatus
|
|
251
260
|
})
|
|
252
261
|
},
|
|
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
|
-
},
|
|
262
262
|
{
|
|
263
263
|
name: 'erp_order_get',
|
|
264
|
-
description: '按 ID
|
|
264
|
+
description: '按 ID 查询订单详情;认顶层 id|orderId 或 payload.id|orderId;include=samples,shipments,timeline 追加聚合;fields 逗号裁剪返回字段',
|
|
265
265
|
permission: 'erp:order:query',
|
|
266
266
|
inputSchema: {
|
|
267
|
-
id:
|
|
267
|
+
id: optionalId.describe('订单 ID,字符串'),
|
|
268
|
+
include: z.string().optional().describe('追加聚合:samples,shipments,timeline 逗号分隔'),
|
|
269
|
+
fields: z.string().optional().describe('只返回这些字段,逗号分隔')
|
|
268
270
|
},
|
|
269
|
-
handler: (client, args) =>
|
|
271
|
+
handler: async (client, args) => {
|
|
272
|
+
const id = resolveGetId(args);
|
|
273
|
+
if (!id) {
|
|
274
|
+
throw new Error('需要 id(顶层 id|orderId 或 payload.id|orderId)');
|
|
275
|
+
}
|
|
276
|
+
const order = (await client.get(`/erp/order/${id}`));
|
|
277
|
+
const include = parseInclude(args.include);
|
|
278
|
+
const jobs = [];
|
|
279
|
+
if (include.has('samples')) {
|
|
280
|
+
jobs.push(client.get('/erp/order/sample/list', { orderId: id }).then((rows) => {
|
|
281
|
+
order.samples = rows;
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
if (include.has('shipments')) {
|
|
285
|
+
jobs.push(client.get('/erp/order/shipment/list', { orderId: id }).then((rows) => {
|
|
286
|
+
order.shipments = rows;
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
if (include.has('timeline')) {
|
|
290
|
+
jobs.push(client.get('/erp/order/timeline/list', { orderId: id }).then((rows) => {
|
|
291
|
+
order.timeline = rows;
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
if (jobs.length > 0) {
|
|
295
|
+
await Promise.all(jobs);
|
|
296
|
+
}
|
|
297
|
+
return pickFields(order, args.fields);
|
|
298
|
+
}
|
|
270
299
|
},
|
|
271
300
|
{
|
|
272
301
|
name: 'erp_order_style_list',
|
|
273
|
-
description: '查询订单主款式树(含 colors/sizes/crafts/商标;轮次克隆挂在 roundStyles);orderId 或 orderNo
|
|
302
|
+
description: '查询订单主款式树(含 colors/sizes/crafts/商标;轮次克隆挂在 roundStyles);orderId 或 orderNo 二选一;fields 逗号裁剪',
|
|
274
303
|
permission: 'erp:order:query',
|
|
275
304
|
inputSchema: {
|
|
276
305
|
orderId: optionalId.describe('订单 ID,字符串雪花 ID'),
|
|
277
|
-
orderNo: z.string().optional().describe('订单号,无 orderId 时用此解析')
|
|
306
|
+
orderNo: z.string().optional().describe('订单号,无 orderId 时用此解析'),
|
|
307
|
+
fields: z.string().optional().describe('只返回这些字段,逗号分隔(如 id,styleNo,colors)')
|
|
278
308
|
},
|
|
279
309
|
handler: async (client, args) => {
|
|
280
310
|
let orderId = asId(args.orderId);
|
|
@@ -289,15 +319,18 @@ const READ_TOOLS = [
|
|
|
289
319
|
if (!orderId) {
|
|
290
320
|
throw new Error('需要 orderId(字符串)或 orderNo');
|
|
291
321
|
}
|
|
292
|
-
|
|
322
|
+
const styles = await client.get('/erp/order/style/list', { orderId });
|
|
323
|
+
return pickFields(styles, args.fields);
|
|
293
324
|
}
|
|
294
325
|
},
|
|
295
326
|
{
|
|
296
327
|
name: 'erp_order_craft_schema',
|
|
297
|
-
description: '查询工艺字段定义 +
|
|
328
|
+
description: '查询工艺字段定义 + 选择指南。compact=true 省略 selectionGuide(体积大时用);完整选项也可读 yindie://erp/craft-guide',
|
|
298
329
|
permission: 'erp:order:query',
|
|
299
|
-
inputSchema: {
|
|
300
|
-
|
|
330
|
+
inputSchema: {
|
|
331
|
+
compact: z.boolean().optional().describe('true 时省略 selectionGuide,显著减小返回体')
|
|
332
|
+
},
|
|
333
|
+
handler: async (client, args) => wrapCraftSchema(await client.get('/erp/order/style/craft/schema'), args.compact === true)
|
|
301
334
|
},
|
|
302
335
|
{
|
|
303
336
|
name: 'erp_order_style_craft_list',
|
|
@@ -310,31 +343,59 @@ const READ_TOOLS = [
|
|
|
310
343
|
},
|
|
311
344
|
{
|
|
312
345
|
name: 'erp_order_style_craft_export',
|
|
313
|
-
description: '
|
|
346
|
+
description: '单款导出工艺单 Excel(合并导出已去掉,勿传 styleIds);qtyType=prod|cut|actual;默认 preview,archive=true 才归档;fileName 只取 basename',
|
|
314
347
|
permission: 'erp:order:query',
|
|
315
348
|
inputSchema: {
|
|
316
|
-
styleId: idField.describe('款式 ID
|
|
317
|
-
qtyType: z
|
|
349
|
+
styleId: idField.describe('款式 ID,字符串;仅单款'),
|
|
350
|
+
qtyType: z
|
|
351
|
+
.string()
|
|
352
|
+
.optional()
|
|
353
|
+
.describe('数量类型 prod生产单/cut预裁单/actual实裁单(默认 prod;Excel 标题同此文案)'),
|
|
318
354
|
fileName: z
|
|
319
355
|
.string()
|
|
320
356
|
.optional()
|
|
321
|
-
.describe('
|
|
322
|
-
archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export
|
|
357
|
+
.describe('仅文件名(传路径也只取 basename),默认 craft-<styleId>-<qtyType>.xlsx'),
|
|
358
|
+
archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export 权限;失败直接报错')
|
|
323
359
|
},
|
|
324
360
|
handler: (client, args) => {
|
|
361
|
+
if (args.styleIds != null && args.styleIds !== '') {
|
|
362
|
+
throw new Error('工艺合并导出已去掉,请逐款传 styleId 调用 craft_export');
|
|
363
|
+
}
|
|
325
364
|
const styleId = asId(args.styleId);
|
|
365
|
+
if (!styleId) {
|
|
366
|
+
throw new Error('需要 styleId');
|
|
367
|
+
}
|
|
326
368
|
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 } });
|
|
369
|
+
return client.download('POST', args.archive ? '/erp/order/style/craft/export-excel' : '/erp/order/style/craft/preview-excel', args.fileName ?? `craft-${styleId}-${qtyType}.xlsx`, { params: { styleId, qtyType } });
|
|
328
370
|
}
|
|
329
371
|
},
|
|
330
372
|
{
|
|
331
373
|
name: 'erp_order_style_size_chart_logs',
|
|
332
|
-
description: '
|
|
374
|
+
description: '查尺寸表变更记录;styleId,或 orderId+styleNo 解析款式',
|
|
333
375
|
permission: 'erp:order:query',
|
|
334
376
|
inputSchema: {
|
|
335
|
-
styleId:
|
|
377
|
+
styleId: optionalId.describe('款式 ID(主款或轮次克隆)'),
|
|
378
|
+
orderId: optionalId.describe('订单 ID;与 styleNo 联用解析 styleId'),
|
|
379
|
+
styleNo: z.string().optional().describe('款号;与 orderId 联用')
|
|
336
380
|
},
|
|
337
|
-
handler: (client, args) =>
|
|
381
|
+
handler: async (client, args) => {
|
|
382
|
+
let styleId = asId(args.styleId);
|
|
383
|
+
if (!styleId) {
|
|
384
|
+
const orderId = asId(args.orderId);
|
|
385
|
+
const styleNo = typeof args.styleNo === 'string' ? args.styleNo : undefined;
|
|
386
|
+
if (!orderId || !styleNo) {
|
|
387
|
+
throw new Error('需要 styleId,或 orderId+styleNo');
|
|
388
|
+
}
|
|
389
|
+
const styles = (await client.get('/erp/order/style/list', { orderId }));
|
|
390
|
+
const hit = styles.find((s) => s.styleNo === styleNo) ||
|
|
391
|
+
styles.flatMap((s) => s.roundStyles || []).find((s) => s.styleNo === styleNo);
|
|
392
|
+
styleId = asId(hit?.id);
|
|
393
|
+
if (!styleId) {
|
|
394
|
+
throw new Error(`未找到款式:orderId=${orderId} styleNo=${styleNo}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return client.get('/erp/order/style/size-chart/logs', { styleId });
|
|
398
|
+
}
|
|
338
399
|
},
|
|
339
400
|
{
|
|
340
401
|
name: 'erp_order_style_size_chart_export',
|
|
@@ -391,21 +452,32 @@ const READ_TOOLS = [
|
|
|
391
452
|
},
|
|
392
453
|
{
|
|
393
454
|
name: 'erp_order_style_color_card_export',
|
|
394
|
-
description: '
|
|
455
|
+
description: '单款导出色卡 Excel(与前端一致,勿传多款 styleIds);默认 preview,archive=true 才归档;fileName 只取 basename',
|
|
395
456
|
permission: 'erp:order:query',
|
|
396
457
|
inputSchema: {
|
|
397
|
-
styleId: idField.describe('款式 ID
|
|
398
|
-
fileName: z.string().optional().describe('
|
|
458
|
+
styleId: idField.describe('款式 ID,字符串;仅单款'),
|
|
459
|
+
fileName: z.string().optional().describe('落盘文件名(传路径也只取 basename)'),
|
|
399
460
|
archive: z.boolean().optional().describe('true 时写入订单附件,需 erp:order:export 权限')
|
|
400
461
|
},
|
|
401
|
-
handler: (client, args) =>
|
|
462
|
+
handler: (client, args) => {
|
|
463
|
+
if (args.styleIds != null && args.styleIds !== '') {
|
|
464
|
+
throw new Error('色卡导出与前端一致仅支持单款,请传 styleId');
|
|
465
|
+
}
|
|
466
|
+
const styleId = asId(args.styleId);
|
|
467
|
+
if (!styleId) {
|
|
468
|
+
throw new Error('需要 styleId');
|
|
469
|
+
}
|
|
470
|
+
return client.download('POST', args.archive ? '/erp/order/style/color-card/export' : '/erp/order/style/color-card/preview', args.fileName ?? `color-card-${styleId}.xlsx`, { body: { styleIds: [styleId] } });
|
|
471
|
+
}
|
|
402
472
|
},
|
|
403
473
|
{
|
|
404
474
|
name: 'erp_production_craft_schema',
|
|
405
|
-
description: '生产单工艺 schema + selectionGuide
|
|
475
|
+
description: '生产单工艺 schema + selectionGuide(同订单);compact=true 省略 selectionGuide',
|
|
406
476
|
permission: 'erp:production:query',
|
|
407
|
-
inputSchema: {
|
|
408
|
-
|
|
477
|
+
inputSchema: {
|
|
478
|
+
compact: z.boolean().optional().describe('true 时省略 selectionGuide')
|
|
479
|
+
},
|
|
480
|
+
handler: async (client, args) => wrapCraftSchema(await client.get('/erp/production/craft/schema'), args.compact === true)
|
|
409
481
|
},
|
|
410
482
|
{
|
|
411
483
|
name: 'erp_production_craft_list',
|
|
@@ -562,12 +634,19 @@ const READ_TOOLS = [
|
|
|
562
634
|
},
|
|
563
635
|
{
|
|
564
636
|
name: 'erp_material_bound_orders',
|
|
565
|
-
description: '
|
|
637
|
+
description: '查询引用该物料的订单(含款号/绑定来源),删除或停用物料前先查;认 materialId 或 id',
|
|
566
638
|
permission: 'erp:material:query',
|
|
567
639
|
inputSchema: {
|
|
568
|
-
|
|
640
|
+
materialId: optionalId.describe('物料 ID,字符串(推荐)'),
|
|
641
|
+
id: optionalId.describe('同 materialId,兼容旧参')
|
|
569
642
|
},
|
|
570
|
-
handler: (client, args) =>
|
|
643
|
+
handler: (client, args) => {
|
|
644
|
+
const materialId = asId(args.materialId) ?? asId(args.id);
|
|
645
|
+
if (!materialId) {
|
|
646
|
+
throw new Error('bound_orders 请传 materialId(物料 ID)');
|
|
647
|
+
}
|
|
648
|
+
return client.get(`/erp/material/${materialId}/boundOrders`);
|
|
649
|
+
}
|
|
571
650
|
},
|
|
572
651
|
{
|
|
573
652
|
name: 'erp_material_supplier_list',
|
|
@@ -612,6 +691,26 @@ const READ_TOOLS = [
|
|
|
612
691
|
},
|
|
613
692
|
handler: (client, args) => client.get(`/erp/material/price/snapshot/${asId(args.id)}`)
|
|
614
693
|
},
|
|
694
|
+
{
|
|
695
|
+
name: 'erp_material_price_snapshot_export',
|
|
696
|
+
description: '导出材料比价快照 Excel(与前端材料比价列表导出一致);可按品名/编码/品类/口径筛选',
|
|
697
|
+
permission: 'erp:material:price:export',
|
|
698
|
+
inputSchema: {
|
|
699
|
+
productName: z.string().optional().describe('品名'),
|
|
700
|
+
materialCode: z.string().optional().describe('物料编码'),
|
|
701
|
+
categoryName: z.string().optional().describe('品类'),
|
|
702
|
+
domain: z.string().optional().describe('比价口径 domestic/export'),
|
|
703
|
+
fileName: z.string().optional().describe('落盘文件名,默认 price-snapshot.xlsx')
|
|
704
|
+
},
|
|
705
|
+
handler: (client, args) => client.download('POST', '/erp/material/price/snapshot/export', args.fileName ?? 'price-snapshot.xlsx', {
|
|
706
|
+
params: {
|
|
707
|
+
productName: args.productName,
|
|
708
|
+
materialCode: args.materialCode,
|
|
709
|
+
categoryName: args.categoryName,
|
|
710
|
+
domain: args.domain
|
|
711
|
+
}
|
|
712
|
+
})
|
|
713
|
+
},
|
|
615
714
|
{
|
|
616
715
|
name: 'erp_material_price_quote_list',
|
|
617
716
|
description: '分页查询比价明细列表',
|