@qinsilk/scm-agent-tools 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +48 -2
  2. package/cli/api.js +114 -0
  3. package/cli/commands/log.js +152 -0
  4. package/cli/commands/query.js +229 -0
  5. package/cli/commands/report.js +213 -0
  6. package/cli/commands/staff.js +505 -0
  7. package/cli/config.js +46 -0
  8. package/cli/scm-agent.js +105 -0
  9. package/cli/skills.js +89 -0
  10. package/cli/table.js +43 -0
  11. package/docs/cli.md +180 -0
  12. package/package.json +27 -4
  13. package/reports/excel.js +107 -0
  14. package/reports/factoryEvaluation.js +128 -0
  15. package/reports/goodsBom.js +648 -0
  16. package/reports/goodsFirstOrder.js +409 -0
  17. package/reports/goodsInventory.js +220 -0
  18. package/reports/goodsSize.js +440 -0
  19. package/reports/image.js +216 -0
  20. package/reports/leakCheck.js +694 -0
  21. package/reports/materialPurchaseExport.js +393 -0
  22. package/reports/materialTransit.js +593 -0
  23. package/reports/processFollowup.js +312 -0
  24. package/reports/produceBoard.js +353 -0
  25. package/reports/produceCost.js +366 -0
  26. package/reports/produceCostDetail.js +622 -0
  27. package/reports/produceDaily.js +485 -0
  28. package/reports/storeInGoods.js +276 -0
  29. package/reports/supplierBalance.js +671 -0
  30. package/reports/ticketStatistics.js +289 -0
  31. package/reports/util.js +83 -0
  32. package/skills/bom/345/225/206/345/223/201/344/277/241/346/201/257/SKILL.md +40 -0
  33. package/skills/qinsilk-scm/SKILL.md +95 -0
  34. package/skills//344/276/233/345/272/224/345/225/206/345/257/271/350/264/246/345/257/274/345/207/272/SKILL.md +38 -0
  35. package/skills//345/225/206/345/223/201/345/260/272/345/257/270/344/277/241/346/201/257/SKILL.md +40 -0
  36. package/skills//345/225/206/345/223/201/351/246/226/345/215/225/351/235/242/346/226/231/345/267/245/345/272/217/345/215/225/344/273/267/SKILL.md +39 -0
  37. package/skills//345/267/245/345/216/202/350/257/204/344/274/260/346/212/245/350/241/250/SKILL.md +39 -0
  38. package/skills//346/210/220/345/223/201/345/205/245/345/272/223/346/212/245/350/241/250/SKILL.md +40 -0
  39. package/skills//346/234/215/350/243/205/346/210/220/345/223/201/347/233/230/345/255/230/350/241/250/SKILL.md +39 -0
  40. package/skills//346/274/217/346/226/231/346/243/200/346/265/213/346/227/245/346/212/245/SKILL.md +40 -0
  41. package/skills//347/211/251/346/226/231/345/234/250/351/200/224/347/212/266/346/200/201/346/212/245/350/241/250/SKILL.md +40 -0
  42. package/skills//347/211/251/346/226/231/351/207/207/350/264/255/345/257/274/345/207/272/SKILL.md +39 -0
  43. package/skills//347/224/237/344/272/247/345/215/225/345/267/245/345/272/217/350/267/237/350/277/233/350/241/250/SKILL.md +39 -0
  44. package/skills//347/224/237/344/272/247/346/210/220/346/234/254/346/212/245/350/241/250/SKILL.md +41 -0
  45. package/skills//347/224/237/344/272/247/346/210/220/346/234/254/346/212/245/350/241/250/346/230/216/347/273/206/SKILL.md +40 -0
  46. package/skills//347/224/237/344/272/247/346/227/245/346/212/245/SKILL.md +42 -0
  47. package/skills//347/224/237/344/272/247/347/212/266/345/206/265/347/273/237/350/256/241/350/241/250/SKILL.md +39 -0
  48. package/skills//347/224/237/344/272/247/350/212/202/347/202/271/347/234/213/346/235/277/SKILL.md +39 -0
package/README.md CHANGED
@@ -1,3 +1,49 @@
1
- # Temporary Holding Version
1
+ # scm-agent-tools —— 让 AI 帮你查秦丝生产ERP
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 配合秦丝生产ERP「AI 助手」使用的命令行工具与 AI 技能。装好之后,你对 AI 助手说一句话就能查数据:
4
+
5
+ - 「查一下款 A123 的生产单」
6
+ - 「A123 完成裁床了吗」
7
+ - 「帮我看下最近的物料采购单」
8
+ - 「找一下叫华达呢的物料」
9
+
10
+ 也能一句话生成官方报表文件:
11
+
12
+ - 「生成今天的生产日报」
13
+ - 「导出近一个月的工厂评估报表」
14
+ - 「跟明达厂出一份对账单」
15
+ - 「导出服装成品盘存表」
16
+
17
+ 官方技能库共 16 个报表技能(生产日报、生产节点看板、生产单工序跟进表、服装成品盘存表、生产成本报表、供应商对账导出等),全部只读、开箱即用;你还可以让 AI 按自己公司的业务定制专属报表。
18
+
19
+ ## 你需要先准备
20
+
21
+ 一个生产ERP账号,并在网页右上角「AI 助手」页面生成一个 AI Key(页面有一键复制的话术,把它粘贴给你的 AI 助手即可全自动安装;下文是手动安装步骤)。
22
+
23
+ ## 手动安装
24
+
25
+ 电脑需要 Node.js 18+。终端执行:
26
+
27
+ ```bash
28
+ npm install -g @qinsilk/scm-agent-tools
29
+ scm-agent skills install
30
+ scm-agent login --key qsk-你的Key
31
+ scm-agent status
32
+ ```
33
+
34
+ 看到「生产ERP CLI 已配置完成」就成功了。国内安装慢时加 `--registry=https://registry.npmmirror.com`。
35
+
36
+ ## 常见问题
37
+
38
+ | 你看到的 | 怎么办 |
39
+ |---|---|
40
+ | AI Key 无效 / 已过期 / 已吊销 | 回生产ERP网页「AI 助手」页面重新生成,再执行一次 login |
41
+ | 权限不足 | 这是账号本身没有该数据的权限,找管理员开通 |
42
+ | 请求过于频繁 | 稍等一分钟再用 |
43
+ | 想切断工具的全部权限 | 网页「AI 助手」页面对应 Key 点「吊销」,立即生效 |
44
+
45
+ ## 隐私
46
+
47
+ AI Key 只保存在你自己的电脑(`~/.scm-agent/config.json`,仅本用户可读);工具只能查询你本人在网页上有权限看的数据;所有查询会被系统日志记录。
48
+
49
+ 全部命令的详细说明(给你或你的 AI 看):[docs/cli.md](docs/cli.md)。
package/cli/api.js ADDED
@@ -0,0 +1,114 @@
1
+ // HTTP 访问层:AI Key 换令牌、令牌缓存与自动续期、错误翻译。
2
+ // 使用 Node 18 内置 fetch,零第三方依赖。
3
+ const { readConfig, writeConfig } = require('./config');
4
+
5
+ /** 服务端业务错误码到用户可读文案的映射 */
6
+ const ERROR_TEXT = {
7
+ 481: 'AI Key 无效或已过期,请在生产ERP网页「AI 助手」页面重新生成',
8
+ 482: '权限不足:当前用户没有访问该数据的权限',
9
+ 483: 'AI Key 已吊销,请在生产ERP网页「AI 助手」页面重新生成',
10
+ 484: '请求过于频繁,请稍后再试',
11
+ };
12
+
13
+ /** 令牌提前 5 分钟续期,避免请求途中过期 */
14
+ const TOKEN_REFRESH_AHEAD_MS = 5 * 60 * 1000;
15
+
16
+ /**
17
+ * 用 AI Key 换取访问令牌并保存到配置。
18
+ * 失败时抛出带用户可读 message 的 Error,不重试(避免放大爆破限制)。
19
+ */
20
+ async function login(key, server) {
21
+ const response = await request(server, 'POST', '/api/agent/v1/token', { key }, { auth: false });
22
+ const config = { server, key, accessToken: response.accessToken, tokenExpiresAt: Date.now() + response.expiresIn * 1000, userInfo: { userName: response.userName, companyName: response.companyName, scopeText: response.scopeText } };
23
+ writeConfig(config);
24
+ return response;
25
+ }
26
+
27
+ /**
28
+ * 带认证调用业务接口。令牌临近过期时自动重新交换。
29
+ */
30
+ async function call(method, apiPath, queryParams = {}) {
31
+ const config = requireConfig();
32
+ let current = ensureFreshToken(config);
33
+ let result;
34
+ try {
35
+ result = await request(config.server, method, apiPath, buildQuery(queryParams), { token: (await current).accessToken });
36
+ } catch (e) {
37
+ // 令牌失效时强制刷新一次再重试(服务器重启、Redis 清空等场景)
38
+ if (e.code === 481) {
39
+ const refreshed = await login(config.key, config.server);
40
+ result = await request(config.server, method, apiPath, buildQuery(queryParams), { token: refreshed.accessToken });
41
+ } else {
42
+ throw e;
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+
48
+ /**
49
+ * 确保令牌可用:临近或已过期时重新交换。返回带有效令牌的配置。
50
+ */
51
+ async function ensureFreshToken(config) {
52
+ if (!config.accessToken || !config.tokenExpiresAt || config.tokenExpiresAt - Date.now() < TOKEN_REFRESH_AHEAD_MS) {
53
+ const response = await login(config.key, config.server);
54
+ return { ...config, accessToken: response.accessToken };
55
+ }
56
+ return config;
57
+ }
58
+
59
+ function requireConfig() {
60
+ const config = readConfig();
61
+ if (!config || !config.key || !config.server) {
62
+ throw new Error('尚未配置。请先执行:scm-agent login --key qsk-xxxx [--server https://scm.qinsilk.com]');
63
+ }
64
+ return config;
65
+ }
66
+
67
+ function buildQuery(params) {
68
+ const clean = {};
69
+ for (const [k, v] of Object.entries(params)) {
70
+ if (v !== undefined && v !== null && v !== '') {
71
+ clean[k] = v;
72
+ }
73
+ }
74
+ return clean;
75
+ }
76
+
77
+ /**
78
+ * 底层请求。服务端统一返回 { status, message, data }:
79
+ * status === 200 视为成功返回 data;否则抛出带 code 的 Error。
80
+ */
81
+ async function request(server, method, apiPath, body, { token, auth = true } = {}) {
82
+ const url = new URL(apiPath, server);
83
+ const headers = { 'Content-Type': 'application/json' };
84
+ if (token) {
85
+ headers.Authorization = `Bearer ${token}`;
86
+ }
87
+
88
+ let response;
89
+ if (method === 'GET') {
90
+ if (body && Object.keys(body).length) {
91
+ for (const [k, v] of Object.entries(body)) {
92
+ url.searchParams.set(k, String(v));
93
+ }
94
+ }
95
+ response = await fetch(url, { headers });
96
+ } else {
97
+ response = await fetch(url, { method, headers, body: JSON.stringify(body) });
98
+ }
99
+
100
+ let payload;
101
+ try {
102
+ payload = await response.json();
103
+ } catch (e) {
104
+ throw new Error(`服务器响应异常(HTTP ${response.status}):${server}`);
105
+ }
106
+ if (payload.status === 200) {
107
+ return payload.data;
108
+ }
109
+ const error = new Error(ERROR_TEXT[payload.status] || payload.message || `请求失败(${payload.status})`);
110
+ error.code = payload.status;
111
+ throw error;
112
+ }
113
+
114
+ module.exports = { login, call, request, requireConfig, ERROR_TEXT };
@@ -0,0 +1,152 @@
1
+ // 日志查询命令族:db-change 按关键词查数据库变动日志,api 按请求标识查接口日志。
2
+ // 典型用法是两步:先用 db-change 查到「谁在什么时候改了什么」,再用返回的
3
+ // requestId 查 api 得到「通过什么接口操作」。默认输出人类可读表格;--json 输出原始 JSON。
4
+ const { call } = require('../api');
5
+ const { printTable } = require('../table');
6
+
7
+ /** 毫秒时间戳转本地时间文本,取不到时显示 - */
8
+ function formatTime(ms) {
9
+ if (ms === null || ms === undefined) {
10
+ return '-';
11
+ }
12
+ const date = new Date(ms);
13
+ const pad = (n) => String(n).padStart(2, '0');
14
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} `
15
+ + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
16
+ }
17
+
18
+ /** data 是对象时列出变动字段名,帮助不展开 JSON 就看出改了什么 */
19
+ function fieldSummary(data) {
20
+ if (data === null || data === undefined) {
21
+ return '-';
22
+ }
23
+ if (typeof data === 'object' && !Array.isArray(data)) {
24
+ return Object.keys(data).join(',');
25
+ }
26
+ return String(data);
27
+ }
28
+
29
+ const log = {
30
+ command: 'log',
31
+ describe: '查询数据变动日志与接口日志',
32
+ builder(yargs) {
33
+ return yargs
34
+ .command({
35
+ command: 'db-change',
36
+ describe: '数据库变动日志(按关键词/表名/操作人查,至少给一项条件,时间倒序)',
37
+ builder: (y) => y
38
+ .option('keywords', { describe: '关键词,逗号分隔(如物料货号 WL1002),只匹配变动数据,多个词都要命中', type: 'string' })
39
+ .option('table', { describe: '数据库表名(如 scm_material)', type: 'string' })
40
+ .option('uid', { describe: '操作人用户id(可先用 user-list 或上一条日志的 uid)', type: 'number' })
41
+ .option('request-id', { describe: '请求标识:查同一请求的全部变动', type: 'string' })
42
+ .option('from', { describe: '开始日期(yyyy-MM-dd),默认近 30 天', type: 'string' })
43
+ .option('to', { describe: '结束日期(yyyy-MM-dd)', type: 'string' })
44
+ .option('limit', { describe: '本批条数(默认 20,最大 100)', type: 'number' })
45
+ .option('line-num', { describe: '续查行号(来自上一次返回)', type: 'string' })
46
+ .option('scroll-id', { describe: '续查游标(来自上一次返回)', type: 'string' }),
47
+ handler: (argv) => runLog(argv, async () => {
48
+ if (!argv.keywords && !argv.table && !argv.uid && !argv.requestId) {
49
+ throw new Error('请至少给一项条件:--keywords、--table、--uid 或 --request-id');
50
+ }
51
+ const params = {
52
+ keywords: argv.keywords,
53
+ table: argv.table,
54
+ uid: argv.uid,
55
+ requestId: argv.requestId,
56
+ from: argv.from,
57
+ to: argv.to,
58
+ limit: argv.limit,
59
+ lineNum: argv.lineNum,
60
+ scrollId: argv.scrollId,
61
+ };
62
+ Object.keys(params).forEach((key) => params[key] === undefined && delete params[key]);
63
+ const data = await call('GET', '/api/agent/v1/log/db-change', params);
64
+ const rows = data.rows.map((row) => ({
65
+ time: formatTime(row.operationTime),
66
+ operator: row.userName || row.uid || '-',
67
+ operation: row.operationType,
68
+ table: row.table,
69
+ fields: fieldSummary(row.data),
70
+ requestId: row.requestId || '-',
71
+ _row: row,
72
+ }));
73
+ return {
74
+ rows,
75
+ data,
76
+ columns: [
77
+ { field: 'time', label: '时间' },
78
+ { field: 'operator', label: '操作人' },
79
+ { field: 'operation', label: '操作' },
80
+ { field: 'table', label: '表名' },
81
+ { field: 'fields', label: '变动字段' },
82
+ { field: 'requestId', label: '请求标识' },
83
+ ],
84
+ };
85
+ }),
86
+ })
87
+ .command({
88
+ command: 'api',
89
+ describe: '接口日志(按请求标识查同一请求的接口操作,标识来自 db-change 的返回)',
90
+ builder: (y) => y
91
+ .option('request-id', { describe: '请求标识(db-change 返回的 requestId 原值)', demandOption: true, type: 'string' }),
92
+ handler: (argv) => runLog(argv, async () => {
93
+ const data = await call('GET', '/api/agent/v1/log/api', { requestId: argv.requestId });
94
+ const rows = data.map((row) => ({
95
+ time: formatTime(row.time),
96
+ operator: row.operatorName || '-',
97
+ description: row.description || '-',
98
+ methodPath: row.methodPath || '-',
99
+ device: row.device || '-',
100
+ _row: row,
101
+ }));
102
+ return {
103
+ rows,
104
+ data,
105
+ columns: [
106
+ { field: 'time', label: '时间' },
107
+ { field: 'operator', label: '操作人' },
108
+ { field: 'description', label: '操作内容' },
109
+ { field: 'methodPath', label: '接口路径' },
110
+ { field: 'device', label: '设备' },
111
+ ],
112
+ };
113
+ }),
114
+ })
115
+ .demandCommand(1, '请指定子命令:db-change 或 api');
116
+ },
117
+ };
118
+
119
+ /** 输出表格与续查提示;--json 输出原始数据;异常输出可读文案并以非 0 退出 */
120
+ async function runLog(argv, task) {
121
+ try {
122
+ const { rows, data, columns } = await task();
123
+ if (argv.json) {
124
+ console.log(JSON.stringify(data, null, 2));
125
+ return;
126
+ }
127
+ if (rows.length === 0) {
128
+ console.log('(无数据)');
129
+ return;
130
+ }
131
+ if (data.hasMore) {
132
+ console.log(`本批 ${rows.length} 条,还有更多:加 --line-num ${JSON.stringify(data.lineNum)} --scroll-id ${JSON.stringify(data.scrollId)} 续查`);
133
+ } else {
134
+ console.log(`共 ${rows.length} 条`);
135
+ }
136
+ printTable(rows, columns);
137
+ rows.forEach((row) => {
138
+ if (row._row && row._row.methodArgs !== undefined && row._row.methodArgs !== null) {
139
+ const args = String(row._row.methodArgs);
140
+ console.log(`\n${row.time} ${row.description} 参数:${args.slice(0, 200)}${args.length > 200 ? '...(更多用 --json 查看)' : ''}`);
141
+ }
142
+ });
143
+ if (rows.some((row) => row._row && row._row.requestId)) {
144
+ console.log('\n变动数据的完整字段值用 --json 查看;要知道具体通过什么操作改的,用请求标识执行:scm-agent log api --request-id <请求标识>');
145
+ }
146
+ } catch (e) {
147
+ console.error(`错误:${e.message}`);
148
+ process.exitCode = 1;
149
+ }
150
+ }
151
+
152
+ module.exports = { log, formatTime, fieldSummary };
@@ -0,0 +1,229 @@
1
+ // 四组业务命令:商品、物料、生产单、物料采购单。design 命令族查询设计款与 IE 核价。
2
+ // 每个命令默认输出人类可读表格;全局 --json 时输出原始 JSON(供 AI 解析)。
3
+ const { call } = require('../api');
4
+ const { printTable } = require('../table');
5
+
6
+ const goods = {
7
+ command: 'goods',
8
+ describe: '查询商品',
9
+ builder(yargs) {
10
+ return yargs
11
+ .command({
12
+ command: 'search',
13
+ describe: '按款号/名称/条码搜商品',
14
+ builder: (y) => y.option('name', { describe: '搜索关键词(款号、名称或条码)', demandOption: true, type: 'string' }),
15
+ handler: (argv) => run(argv, async () => {
16
+ const data = await call('GET', '/api/agent/v1/goods/search', { searchKey: argv.name, page: argv.page, limit: argv.limit });
17
+ return { list: data.list, total: data.total, columns: [
18
+ { field: 'goodsSn', label: '款号' },
19
+ { field: 'name', label: '名称' },
20
+ { field: 'isOnSale', label: '上架(1是0否)' },
21
+ { field: 'tagPrice', label: '吊牌价' },
22
+ { field: 'storedNumber', label: '库存' },
23
+ { field: 'produceNumber', label: '生产中' },
24
+ { field: 'produceOrderNumber', label: '生产单数' },
25
+ ] };
26
+ }),
27
+ })
28
+ .command({
29
+ command: 'detail',
30
+ describe: '商品详情(颜色尺码与工序)',
31
+ builder: (y) => y.option('id', { describe: '商品id(先用 search 查到)', demandOption: true, type: 'number' }),
32
+ handler: (argv) => run(argv, async () => {
33
+ const data = await call('GET', '/api/agent/v1/goods/detail', { goodsId: argv.id });
34
+ return { data, columns: null };
35
+ }),
36
+ })
37
+ .demandCommand(1, '请指定子命令:search 或 detail');
38
+ },
39
+ };
40
+
41
+ const material = {
42
+ command: 'material',
43
+ describe: '查询物料',
44
+ builder(yargs) {
45
+ return yargs
46
+ .command({
47
+ command: 'search',
48
+ describe: '按名称/货号/条码搜物料',
49
+ builder: (y) => y.option('name', { describe: '搜索关键词(物料名称、货号或条码)', demandOption: true, type: 'string' }),
50
+ handler: (argv) => run(argv, async () => {
51
+ const data = await call('GET', '/api/agent/v1/material/search', { searchKey: argv.name, page: argv.page, limit: argv.limit });
52
+ return { list: data.list, total: data.total, columns: [
53
+ { field: 'sn', label: '货号' },
54
+ { field: 'name', label: '名称' },
55
+ { field: 'type', label: '类型(1面料0辅料)' },
56
+ { field: 'state', label: '状态(1启用)' },
57
+ { field: 'price', label: '价格' },
58
+ { field: 'stockNumber', label: '库存' },
59
+ { field: 'defaultSupplierName', label: '默认供应商' },
60
+ ] };
61
+ }),
62
+ })
63
+ .command({
64
+ command: 'detail',
65
+ describe: '物料详情(单品颜色规格)',
66
+ builder: (y) => y.option('id', { describe: '物料id(先用 search 查到)', demandOption: true, type: 'number' }),
67
+ handler: (argv) => run(argv, async () => {
68
+ const data = await call('GET', '/api/agent/v1/material/detail', { id: argv.id });
69
+ return { data, columns: null };
70
+ }),
71
+ })
72
+ .demandCommand(1, '请指定子命令:search 或 detail');
73
+ },
74
+ };
75
+
76
+ const produce = {
77
+ command: 'produce',
78
+ describe: '查询生产单',
79
+ builder(yargs) {
80
+ return yargs
81
+ .command({
82
+ command: 'list',
83
+ describe: '生产单列表(至少给一项条件)',
84
+ builder: (y) => y
85
+ .option('sn', { describe: '关键词:单号/款号/商品名', type: 'string' })
86
+ .option('state', { describe: '状态:1草稿 2生产中 3生产完成 4待生产 9已撤销', type: 'number' })
87
+ .option('from', { describe: '业务日期起(yyyy-MM-dd)', type: 'string' })
88
+ .option('to', { describe: '业务日期止(yyyy-MM-dd)', type: 'string' }),
89
+ handler: (argv) => run(argv, async () => {
90
+ const data = await call('GET', '/api/agent/v1/produce/list', { searchKey: argv.sn, state: argv.state, from: argv.from, to: argv.to, page: argv.page, limit: argv.limit });
91
+ return { list: data.list, total: data.total, columns: [
92
+ { field: 'ordersSn', label: '生产单号' },
93
+ { field: 'goodsSn', label: '款号' },
94
+ { field: 'goodsName', label: '商品' },
95
+ { field: 'stateName', label: '状态' },
96
+ { field: 'sumNumber', label: '下单数' },
97
+ { field: 'sumInNumber', label: '入库数' },
98
+ { field: 'deliveryTime', label: '交期' },
99
+ { field: 'cutBedState', label: '裁床状态' },
100
+ ] };
101
+ }),
102
+ })
103
+ .command({
104
+ command: 'detail',
105
+ describe: '生产单详情(明细与工序进度,裁床工序 core=2)',
106
+ builder: (y) => y.option('sn', { describe: '生产单号', demandOption: true, type: 'string' }),
107
+ handler: (argv) => run(argv, async () => {
108
+ const data = await call('GET', '/api/agent/v1/produce/detail', { sn: argv.sn });
109
+ return { data, columns: null };
110
+ }),
111
+ })
112
+ .demandCommand(1, '请指定子命令:list 或 detail');
113
+ },
114
+ };
115
+
116
+ const materialPurchase = {
117
+ command: 'material-purchase',
118
+ describe: '查询物料采购单',
119
+ builder(yargs) {
120
+ return yargs
121
+ .command({
122
+ command: 'list',
123
+ describe: '物料采购单列表(至少给一项条件)',
124
+ builder: (y) => y
125
+ .option('sn', { describe: '关键词:单号/供应商/生产单号/物料', type: 'string' })
126
+ .option('state', { describe: '状态:1草稿 2待入库 3部分入库 4全部入库 9撤销', type: 'number' })
127
+ .option('from', { describe: '业务日期起(yyyy-MM-dd)', type: 'string' })
128
+ .option('to', { describe: '业务日期止(yyyy-MM-dd)', type: 'string' }),
129
+ handler: (argv) => run(argv, async () => {
130
+ const data = await call('GET', '/api/agent/v1/material-purchase/list', { searchKey: argv.sn, state: argv.state, from: argv.from, to: argv.to, page: argv.page, limit: argv.limit });
131
+ return { list: data.list, total: data.total, columns: [
132
+ { field: 'ordersSn', label: '采购单号' },
133
+ { field: 'supplierName', label: '供应商' },
134
+ { field: 'state', label: '状态' },
135
+ { field: 'sumPurchaseNumber', label: '采购数' },
136
+ { field: 'amount', label: '金额' },
137
+ { field: 'expectTime', label: '预计到料' },
138
+ ] };
139
+ }),
140
+ })
141
+ .command({
142
+ command: 'detail',
143
+ describe: '物料采购单详情(物料明细)',
144
+ builder: (y) => y.option('sn', { describe: '采购单号', demandOption: true, type: 'string' }),
145
+ handler: (argv) => run(argv, async () => {
146
+ const data = await call('GET', '/api/agent/v1/material-purchase/detail', { sn: argv.sn });
147
+ return { data, columns: null };
148
+ }),
149
+ })
150
+ .demandCommand(1, '请指定子命令:list 或 detail');
151
+ },
152
+ };
153
+
154
+ const design = {
155
+ command: 'design',
156
+ describe: '查询设计款与IE核价',
157
+ builder(yargs) {
158
+ return yargs
159
+ .command({
160
+ command: 'list',
161
+ describe: '设计款列表(至少给一项条件)',
162
+ builder: (y) => y
163
+ .option('sn', { describe: '关键词:款号/名称/条码/货号', type: 'string' })
164
+ .option('state', { describe: '状态:2设计中 3待确定 4修改中 5审核通过 6取消 15开发池 16版型资产库', type: 'number' })
165
+ .option('dateType', { describe: '日期字段:update更新 create创建 dev开发(默认 update)', type: 'string', default: 'update' })
166
+ .option('from', { describe: '日期起(yyyy-MM-dd)', type: 'string' })
167
+ .option('to', { describe: '日期止(yyyy-MM-dd)', type: 'string' }),
168
+ handler: (argv) => run(argv, async () => {
169
+ const data = await call('GET', '/api/agent/v1/design/list', { searchKey: argv.sn, designState: argv.state, dateType: argv.dateType, from: argv.from, to: argv.to, page: argv.page, limit: argv.limit });
170
+ return { list: data.list, total: data.total, columns: [
171
+ { field: 'designSn', label: '款号' },
172
+ { field: 'name', label: '名称' },
173
+ { field: 'designState', label: '状态(2设计中3待确定4修改中5审核通过6取消15开发池16版型资产库)' },
174
+ { field: 'designBrandName', label: '品牌' },
175
+ { field: 'designCategoryName', label: '分类' },
176
+ { field: 'designDesignerName', label: '设计师' },
177
+ { field: 'followerName', label: '跟单员' },
178
+ { field: 'costPrice', label: '成本价' },
179
+ { field: 'designStoredNumber', label: '库存' },
180
+ { field: 'designOrderCount', label: '板单数' },
181
+ ] };
182
+ }),
183
+ })
184
+ .command({
185
+ command: 'ie-list',
186
+ describe: 'IE核价报表(设计款×颜色的物料/工序/费用明细)',
187
+ builder: (y) => y
188
+ .option('sn', { describe: '关键词:名称/货号/款号/条码', type: 'string' })
189
+ .option('dateType', { describe: '日期字段:dev开发 create创建(默认 dev)', type: 'string', default: 'dev' })
190
+ .option('from', { describe: '日期起(yyyy-MM-dd)', type: 'string' })
191
+ .option('to', { describe: '日期止(yyyy-MM-dd)', type: 'string' }),
192
+ handler: (argv) => run(argv, async () => {
193
+ const data = await call('GET', '/api/agent/v1/design/ie-list', { searchKey: argv.sn, dateType: argv.dateType, from: argv.from, to: argv.to, page: argv.page, limit: argv.limit });
194
+ return { list: data.list, total: data.total, columns: [
195
+ { field: 'designSn', label: '款号' },
196
+ { field: 'name', label: '名称' },
197
+ { field: 'colorName', label: '颜色' },
198
+ { field: 'costPrice', label: '成本价' },
199
+ { field: 'designerName', label: '设计师' },
200
+ ] };
201
+ }),
202
+ })
203
+ .demandCommand(1, '请指定子命令:list 或 ie-list');
204
+ },
205
+ };
206
+
207
+ /** 统一执行:异常输出可读文案并以非 0 退出;--json 输出原始数据 */
208
+ async function run(argv, task) {
209
+ try {
210
+ const { list, total, columns, data } = await task();
211
+ if (argv.json) {
212
+ console.log(JSON.stringify({ total, list, data }, null, 2));
213
+ return;
214
+ }
215
+ if (columns) {
216
+ if (total !== undefined) {
217
+ console.log(`共 ${total} 条,当前显示 ${list.length} 条`);
218
+ }
219
+ printTable(list, columns);
220
+ } else {
221
+ console.log(JSON.stringify(data, null, 2));
222
+ }
223
+ } catch (e) {
224
+ console.error(`错误:${e.message}`);
225
+ process.exitCode = 1;
226
+ }
227
+ }
228
+
229
+ module.exports = { goods, material, produce, materialPurchase, design };