@besile/scm-cli 2026.3.29

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.
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli auth logout command
7
+ */
8
+ import { scmClient, createLogger } from '../../internal/index.js';
9
+
10
+ /**
11
+ * Register auth logout subcommand
12
+ */
13
+ export function logoutCommand(program) {
14
+ program
15
+ .command('logout')
16
+ .description('登出并清除 Token')
17
+ .requiredOption('--openId <openId>', '飞书用户 openId')
18
+ .option('-y, --yes', '跳过确认')
19
+ .action(async (opts) => {
20
+ if (!opts.yes) {
21
+ const readline = await import('node:readline/promises');
22
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23
+ const answer = await rl.question(`确认清除 openId=${opts.openId} 的 Token?(y/N) `);
24
+ rl.close();
25
+ if (answer.toLowerCase() !== 'y') {
26
+ console.log('已取消');
27
+ process.exit(0);
28
+ }
29
+ }
30
+
31
+ try {
32
+ await scmClient.clearUserToken(opts.openId);
33
+ console.log(`✅ 已清除 openId=${opts.openId} 的 Token`);
34
+ } catch (err) {
35
+ console.error('❌ 清除失败:', err.message);
36
+ process.exit(1);
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli auth status command
7
+ */
8
+ import { checkToken, getCachedToken, createLogger } from '../../internal/index.js';
9
+
10
+ /**
11
+ * Register auth status subcommand
12
+ */
13
+ export function statusCommand(program) {
14
+ program
15
+ .command('status')
16
+ .description('检查 Token 状态')
17
+ .requiredOption('--openId <openId>', '飞书用户 openId')
18
+ .action(async (opts) => {
19
+ const result = await checkToken(opts.openId);
20
+
21
+ if (result.valid) {
22
+ console.log(`✅ Token 有效 — openId=${opts.openId}`);
23
+ } else {
24
+ console.log(`❌ Token 无效 — openId=${opts.openId}`);
25
+ console.log(` ${result.message}`);
26
+ console.log('\n请先登录: scm-cli auth login --openId <openId> --mobile <手机号> --password <密码>');
27
+ }
28
+ });
29
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli auth token command — list/show/clear tokens
7
+ */
8
+ import { TOKEN_FILE_PATH, loadConfig } from '../../internal/index.js';
9
+ import { readFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+
13
+ async function loadTokens() {
14
+ try {
15
+ const data = await readFile(TOKEN_FILE_PATH, 'utf-8');
16
+ return JSON.parse(data);
17
+ } catch {
18
+ return {};
19
+ }
20
+ }
21
+
22
+ async function listTokens() {
23
+ const tokens = await loadTokens();
24
+ const entries = Object.entries(tokens);
25
+
26
+ if (entries.length === 0) {
27
+ console.log('未找到缓存的 Token');
28
+ return;
29
+ }
30
+
31
+ console.log('\n┌─────────────────────────────────────────────────────────────────────────────┐');
32
+ console.log('│ 缓存的 Token 列表 │');
33
+ console.log('├─────────────────────────────────────────────────────────────────────────────┤');
34
+
35
+ for (const [openId, token] of entries) {
36
+ const now = Date.now() / 1000;
37
+ const isExpired = token.expires_at <= now;
38
+ const status = isExpired ? '❌ 已过期' : '✅ 有效';
39
+
40
+ console.log(`│ Open ID: ${openId.padEnd(60)} │`);
41
+ console.log(`│ 用户名: ${(token.username || '-').padEnd(60)} │`);
42
+ console.log(`│ 状态: ${status.padEnd(60)} │`);
43
+ console.log(`│ 过期时间: ${new Date(token.expires_at * 1000).toLocaleString('zh-CN').padEnd(60)} │`);
44
+ console.log('├─────────────────────────────────────────────────────────────────────────────┤');
45
+ }
46
+ console.log(`\n共 ${entries.length} 个用户`);
47
+ }
48
+
49
+ async function showToken(openId) {
50
+ const tokens = await loadTokens();
51
+ const token = tokens[openId];
52
+
53
+ if (!token) {
54
+ console.log(`未找到 openId=${openId} 的 Token`);
55
+ return;
56
+ }
57
+
58
+ const now = Date.now() / 1000;
59
+ const remaining = Math.max(0, token.expires_at - now);
60
+ const isExpired = token.expires_at <= now;
61
+
62
+ console.log('\n┌─────────────────────────────────────────────────────────────────────────────┐');
63
+ console.log('│ Token 详细信息 │');
64
+ console.log('├─────────────────────────────────────────────────────────────────────────────┤');
65
+ console.log(`│ Open ID: ${openId.padEnd(56)} │`);
66
+ console.log(`│ 用户名: ${(token.username || '-').padEnd(56)} │`);
67
+ console.log(`│ 状态: ${(isExpired ? '已过期' : '有效').padEnd(56)} │`);
68
+ console.log(`│ 剩余时间: ${isExpired ? '已过期' : `${Math.round(remaining / 60)} 分钟`.padEnd(56)} │`);
69
+ console.log(`│ 过期时间: ${new Date(token.expires_at * 1000).toLocaleString('zh-CN').padEnd(56)} │`);
70
+ console.log('└─────────────────────────────────────────────────────────────────────────────┘');
71
+ }
72
+
73
+ async function clearToken(openId) {
74
+ const tokens = await loadTokens();
75
+ if (!tokens[openId]) {
76
+ console.log(`未找到 openId=${openId} 的 Token`);
77
+ return;
78
+ }
79
+ delete tokens[openId];
80
+ const { writeFile } = await import('node:fs/promises');
81
+ await writeFile(TOKEN_FILE_PATH, JSON.stringify(tokens, null, 2), 'utf-8');
82
+ console.log(`✅ 已清除 openId=${openId} 的 Token`);
83
+ }
84
+
85
+ async function clearAllTokens() {
86
+ const { unlink } = await import('node:fs/promises');
87
+ try {
88
+ await unlink(TOKEN_FILE_PATH);
89
+ console.log('✅ 已清除所有 Token');
90
+ } catch {
91
+ console.log('未找到 Token 缓存');
92
+ }
93
+ }
94
+
95
+ export function tokenCommand(program) {
96
+ const tokenCmd = program.command('token').description('Token 管理');
97
+
98
+ tokenCmd
99
+ .command('list')
100
+ .alias('ls')
101
+ .description('列出所有缓存的 Token')
102
+ .action(async () => { await listTokens(); });
103
+
104
+ tokenCmd
105
+ .command('show <openId>')
106
+ .description('显示指定用户的 Token 详情')
107
+ .action(async (openId) => { await showToken(openId); });
108
+
109
+ tokenCmd
110
+ .command('clear <openId>')
111
+ .description('清除指定用户的 Token')
112
+ .action(async (openId) => { await clearToken(openId); });
113
+
114
+ tokenCmd
115
+ .command('clear-all')
116
+ .description('清除所有 Token')
117
+ .action(async () => { await clearAllTokens(); });
118
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * Register all CLI commands and shortcuts to the program
7
+ */
8
+ import { loginCommand } from './auth/login.js';
9
+ import { logoutCommand } from './auth/logout.js';
10
+ import { statusCommand } from './auth/status.js';
11
+ import { tokenCommand } from './auth/token.js';
12
+ import { productionOrdersCommand } from './orders/production-orders.js';
13
+ import { materialOrdersCommand } from './orders/material-orders.js';
14
+ import { qcOrdersCommand } from './orders/qc-orders.js';
15
+ import { getShortcutsMap } from '../shortcuts/index.js';
16
+ import { registerShortcuts } from '../shortcuts/register.js';
17
+
18
+ /**
19
+ * Register all subcommands to the program
20
+ *
21
+ * @param {Object} program - Commander program
22
+ * @param {Object} internalCtx - Internal context { log }
23
+ */
24
+ export function registerCommands(program, internalCtx) {
25
+ // Auth subcommands
26
+ const authCmd = program.command('auth').description('认证管理');
27
+ loginCommand(authCmd);
28
+ logoutCommand(authCmd);
29
+ statusCommand(authCmd);
30
+ tokenCommand(authCmd);
31
+
32
+ // Orders subcommands
33
+ productionOrdersCommand(program);
34
+ materialOrdersCommand(program);
35
+ qcOrdersCommand(program);
36
+
37
+ // Shortcuts (registered under their domain commands)
38
+ const shortcutsMap = getShortcutsMap();
39
+ registerShortcuts(program, shortcutsMap, internalCtx);
40
+ }
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli material-orders command
7
+ */
8
+ import { loadConfig, getCachedToken, checkToken, promptLogin, createLogger } from '../../internal/index.js';
9
+ import { doLogin } from '../auth/login.js';
10
+
11
+ /**
12
+ * Query material orders via SCM API
13
+ *
14
+ * @param {string} openId - 飞书用户 openId
15
+ * @param {Object} opts - 命令行选项
16
+ * @param {Object} config - 配置对象
17
+ */
18
+ async function queryMaterialOrders(openId, opts, config) {
19
+ const token = await getCachedToken(openId);
20
+ const url = `${config.baseUrl}/admin-api/api/ai-scm/execute`;
21
+
22
+ const filter = {
23
+ pageNo: parseInt(opts.pageNo) || 1,
24
+ pageSize: Math.min(parseInt(opts.pageSize) || 20, 100),
25
+ };
26
+
27
+ // 订单信息
28
+ if (opts.orderNo) filter.orderNo = opts.orderNo;
29
+ if (opts.status !== undefined) filter.status = parseInt(opts.status);
30
+ if (opts.cooperationMode !== undefined) filter.cooperationMode = parseInt(opts.cooperationMode);
31
+
32
+ // 商品信息
33
+ if (opts.goodsFullCode) filter.goodsFullCode = opts.goodsFullCode;
34
+ if (opts.goodsBatch) filter.goodsBatch = opts.goodsBatch;
35
+ if (opts.goodsType !== undefined) filter.goodsType = parseInt(opts.goodsType);
36
+
37
+ // 供应商
38
+ if (opts.supplierName) filter.supplierName = opts.supplierName;
39
+
40
+ // 人员
41
+ if (opts.purchaserIdList) filter.purchaserIdList = opts.purchaserIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
42
+ if (opts.pdUserIdList) filter.pdUserIdList = opts.pdUserIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
43
+ if (opts.designerName) filter.designerName = opts.designerName;
44
+
45
+ // 时间
46
+ if (opts.type !== undefined) filter.type = parseInt(opts.type);
47
+ if (opts.startTime) filter.startTime = opts.startTime;
48
+ if (opts.endTime) filter.endTime = opts.endTime;
49
+
50
+ const response = await fetch(url, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
53
+ body: JSON.stringify({ domain: 'goods_material', action: 'query', filter }),
54
+ });
55
+
56
+ const data = await response.json();
57
+ if (!response.ok) {
58
+ if (response.status === 401) throw new Error('Token 无效或已过期,请重新登录');
59
+ throw new Error(`HTTP ${response.status}`);
60
+ }
61
+ if (data.code !== 0 && data.code !== 200 && data.success !== true) {
62
+ throw new Error(data.msg || `API 错误: ${data.code}`);
63
+ }
64
+
65
+ return data.data || data;
66
+ }
67
+
68
+ export function materialOrdersCommand(program) {
69
+ program
70
+ .command('material-orders')
71
+ .description('查询物料订单')
72
+ .requiredOption('--openId <openId>', '飞书用户 openId')
73
+ // 分页
74
+ .option('--pageNo <pageNo>', '页码', '1')
75
+ .option('--pageSize <pageSize>', '每页条数', '20')
76
+ // 订单信息
77
+ .option('--orderNo <orderNo>', '物料订单号')
78
+ .option('--status <status>', '物料订单状态: 0-待采购|1-采购中|2-部分到货|3-已到货|4-已取消')
79
+ .option('--cooperationMode <cooperationMode>', '合作模式: 0-CMT|1-FOB')
80
+ // 商品信息
81
+ .option('--goodsFullCode <goodsFullCode>', '大货款号')
82
+ .option('--goodsBatch <goodsBatch>', '大货批次')
83
+ .option('--goodsType <goodsType>', '大货类型: 0-首单|1-加单|2-翻单')
84
+ // 供应商
85
+ .option('--supplierName <supplierName>', '供应商名称')
86
+ // 人员
87
+ .option('--purchaserIdList <purchaserIdList>', '采购 ID 列表 (逗号分隔)')
88
+ .option('--pdUserIdList <pdUserIdList>', 'PD ID 列表 (逗号分隔)')
89
+ .option('--designerName <designerName>', '设计师名称')
90
+ // 时间
91
+ .option('--type <type>', '时间查询类型: 0-上市|1-下单|2-交期|3-调整|4-关单|5-生产下单|6-工厂接单|7-计划到货|8-实际到货|9-需求到货')
92
+ .option('--startTime <startTime>', '开始时间 (ISO 8601)')
93
+ .option('--endTime <endTime>', '结束时间 (ISO 8601)')
94
+ // 输出
95
+ .option('--format <format>', '输出格式: table|json|pretty', 'table')
96
+ .action(async (opts) => {
97
+ const config = loadConfig();
98
+
99
+ const tokenCheck = await checkToken(opts.openId);
100
+ if (!tokenCheck.valid) {
101
+ const loginResult = await promptLogin(opts.openId);
102
+ if (!loginResult.success) process.exit(1);
103
+ try {
104
+ await doLogin(opts.openId, loginResult.mobile, loginResult.password);
105
+ console.log('✅ 登录成功!\n');
106
+ } catch (err) {
107
+ console.error('\n❌ 登录失败:', err.message);
108
+ process.exit(1);
109
+ }
110
+ }
111
+
112
+ try {
113
+ console.log('正在查询物料订单...');
114
+ const result = await queryMaterialOrders(opts.openId, opts, config);
115
+ console.log(opts.format === 'json'
116
+ ? JSON.stringify(result, null, 2)
117
+ : JSON.stringify(result, null, 2));
118
+ } catch (err) {
119
+ console.error('\n❌ 查询失败:', err.message);
120
+ process.exit(1);
121
+ }
122
+ });
123
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli production-orders command
7
+ */
8
+ import { scmClient, loadConfig, createLogger, getCachedToken, checkToken, promptLogin } from '../../internal/index.js';
9
+ import { doLogin } from '../auth/login.js';
10
+
11
+ /**
12
+ * Query production orders via SCM API
13
+ *
14
+ * @param {string} openId - 飞书用户 openId
15
+ * @param {Object} opts - 命令行选项
16
+ * @param {Object} config - 配置对象
17
+ */
18
+ async function queryProductionOrders(openId, opts, config) {
19
+ const token = await getCachedToken(openId);
20
+ const url = `${config.baseUrl}/admin-api/api/ai-scm/execute`;
21
+
22
+ const filter = {
23
+ pageNo: parseInt(opts.pageNo) || 1,
24
+ pageSize: Math.min(parseInt(opts.pageSize) || 20, 100),
25
+ };
26
+
27
+ // 商品信息
28
+ if (opts.goodsFullCode) filter.goodsFullCode = opts.goodsFullCode;
29
+ if (opts.goodsBatch) filter.goodsBatch = opts.goodsBatch;
30
+ if (opts.goodsType !== undefined) filter.goodsType = parseInt(opts.goodsType);
31
+
32
+ // 订单状态
33
+ if (opts.prodStatus !== undefined) filter.prodStatus = parseInt(opts.prodStatus);
34
+ if (opts.confirmSupplier !== undefined) filter.confirmSupplier = parseInt(opts.confirmSupplier);
35
+ if (opts.supplierOrderStatus !== undefined) filter.supplierOrderStatus = parseInt(opts.supplierOrderStatus);
36
+ if (opts.quotationStatus !== undefined) filter.quotationStatus = parseInt(opts.quotationStatus);
37
+ if (opts.washStatus !== undefined) filter.washStatus = parseInt(opts.washStatus);
38
+ if (opts.qcInspection !== undefined) filter.qcInspection = parseInt(opts.qcInspection);
39
+
40
+ // 供应商
41
+ if (opts.supplierName) filter.supplierName = opts.supplierName;
42
+ if (opts.cooperationMode !== undefined) filter.cooperationMode = parseInt(opts.cooperationMode);
43
+
44
+ // 人员
45
+ if (opts.pdUserIdList) filter.pdUserIdList = opts.pdUserIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
46
+ if (opts.purchaserIdList) filter.purchaserIdList = opts.purchaserIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
47
+ if (opts.designerName) filter.designerName = opts.designerName;
48
+
49
+ // 时间
50
+ if (opts.type !== undefined) filter.type = parseInt(opts.type);
51
+ if (opts.startTime) filter.startTime = opts.startTime;
52
+ if (opts.endTime) filter.endTime = opts.endTime;
53
+
54
+ // 分类
55
+ if (opts.saleArea) filter.saleArea = opts.saleArea.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
56
+
57
+ const response = await fetch(url, {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
60
+ body: JSON.stringify({ domain: 'production_order', action: 'query', filter }),
61
+ });
62
+
63
+ const data = await response.json();
64
+
65
+ if (!response.ok) {
66
+ if (response.status === 401) throw new Error('Token 无效或已过期,请重新登录');
67
+ throw new Error(`HTTP ${response.status}`);
68
+ }
69
+ if (data.code !== 0 && data.code !== 200 && data.success !== true) {
70
+ throw new Error(data.msg || `API 错误: ${data.code}`);
71
+ }
72
+
73
+ return data.data || data;
74
+ }
75
+
76
+ /**
77
+ * Register production-orders subcommand
78
+ */
79
+ export function productionOrdersCommand(program) {
80
+ program
81
+ .command('production-orders')
82
+ .alias('orders')
83
+ .description('查询大货生产订单')
84
+ .requiredOption('--openId <openId>', '飞书用户 openId')
85
+ // 分页
86
+ .option('--pageNo <pageNo>', '页码', '1')
87
+ .option('--pageSize <pageSize>', '每页条数', '20')
88
+ // 商品信息
89
+ .option('--goodsFullCode <goodsFullCode>', '大货款号')
90
+ .option('--goodsBatch <goodsBatch>', '大货批次')
91
+ .option('--goodsType <goodsType>', '大货类型: 0-首单|1-加单|2-翻单')
92
+ // 订单状态
93
+ .option('--prodStatus <prodStatus>', '大货生产订单状态: -1-全部|0-待确定|1-待同步|2-在途中|3-已完成|100-已取消')
94
+ .option('--confirmSupplier <confirmSupplier>', '确认供应商: 0-未确认|1-已确认')
95
+ .option('--supplierOrderStatus <supplierOrderStatus>', '供应商生产订单状态: 0-待接单|1-生产中|2-已退单|3-已完成|4-已取消')
96
+ .option('--quotationStatus <quotationStatus>', '报价单状态: 0-确认中|1-已驳回|2-已确认|3-未提报')
97
+ .option('--washStatus <washStatus>', '洗唛订单状态: -1-无|0-未同步|1-已同步|2-未确认|3-生产中|4-已发货|5-已取消')
98
+ .option('--qcInspection <qcInspection>', 'QC 质检结果')
99
+ // 供应商
100
+ .option('--supplierName <supplierName>', '供应商名称')
101
+ .option('--cooperationMode <cooperationMode>', '合作模式: 0-CMT|1-FOB')
102
+ // 人员
103
+ .option('--pdUserIdList <pdUserIdList>', 'PD ID 列表 (逗号分隔)')
104
+ .option('--purchaserIdList <purchaserIdList>', '采购 ID 列表 (逗号分隔)')
105
+ .option('--designerName <designerName>', '设计师名称')
106
+ // 时间
107
+ .option('--type <type>', '时间查询类型: 0-上市|1-下单|2-交期|3-调整|4-关单|5-生产下单|6-工厂接单|7-计划到货|8-实际到货|9-需求到货')
108
+ .option('--startTime <startTime>', '开始时间 (ISO 8601)')
109
+ .option('--endTime <endTime>', '结束时间 (ISO 8601)')
110
+ // 分类
111
+ .option('--saleArea <saleArea>', '销售地区 ID 列表 (逗号分隔)')
112
+ // 输出
113
+ .option('--format <format>', '输出格式: table|json|pretty', 'table')
114
+ .action(async (opts) => {
115
+ const config = loadConfig();
116
+ const log = createLogger(opts.debug || config.debug);
117
+
118
+ const tokenCheck = await checkToken(opts.openId);
119
+ if (!tokenCheck.valid) {
120
+ const loginResult = await promptLogin(opts.openId);
121
+ if (!loginResult.success) process.exit(1);
122
+ try {
123
+ await doLogin(opts.openId, loginResult.mobile, loginResult.password);
124
+ console.log('✅ 登录成功!\n');
125
+ } catch (err) {
126
+ console.error('\n❌ 登录失败:', err.message);
127
+ process.exit(1);
128
+ }
129
+ }
130
+
131
+ try {
132
+ console.log('正在查询生产订单...');
133
+ const result = await queryProductionOrders(opts.openId, opts, config);
134
+ const data = opts.format === 'json'
135
+ ? JSON.stringify(result, null, 2)
136
+ : JSON.stringify(result, null, 2);
137
+ console.log(data);
138
+ } catch (err) {
139
+ console.error('\n❌ 查询失败:', err.message);
140
+ process.exit(1);
141
+ }
142
+ });
143
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli qc-orders command
7
+ */
8
+ import { loadConfig, getCachedToken, checkToken, promptLogin } from '../../internal/index.js';
9
+ import { doLogin } from '../auth/login.js';
10
+
11
+ async function queryQcOrders(openId, opts, config) {
12
+ const token = await getCachedToken(openId);
13
+ const url = `${config.baseUrl}/admin-api/api/ai-scm/execute`;
14
+
15
+ const filter = {
16
+ pageNo: parseInt(opts.pageNo) || 1,
17
+ pageSize: Math.min(parseInt(opts.pageSize) || 20, 100),
18
+ };
19
+ if (opts.qcOrderStatus !== undefined) filter.qcOrderStatus = parseInt(opts.qcOrderStatus);
20
+ if (opts.goodsFullCode) filter.goodsFullCode = opts.goodsFullCode;
21
+ if (opts.goodsType !== undefined) filter.goodsType = parseInt(opts.goodsType);
22
+ if (opts.supplierName) filter.supplierName = opts.supplierName;
23
+ if (opts.goodsGradeList) filter.goodsGradeList = opts.goodsGradeList.split(',').map(s => s.trim());
24
+ if (opts.midManager) filter.midManager = opts.midManager;
25
+ if (opts.checkGoods) filter.checkGoods = opts.checkGoods;
26
+ if (opts.tailManager) filter.tailManager = opts.tailManager;
27
+ if (opts.qcUserIdList) filter.qcUserIdList = opts.qcUserIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
28
+ if (opts.prodManagerIdList) filter.prodManagerIdList = opts.prodManagerIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
29
+ if (opts.purchaserIdList) filter.purchaserIdList = opts.purchaserIdList.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
30
+ if (opts.timeType !== undefined) filter.timeType = parseInt(opts.timeType);
31
+ if (opts.startTime) filter.startTime = opts.startTime;
32
+ if (opts.endTime) filter.endTime = opts.endTime;
33
+
34
+ const response = await fetch(url, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
37
+ body: JSON.stringify({ domain: 'qc_order', action: 'query', filter }),
38
+ });
39
+
40
+ const data = await response.json();
41
+ if (!response.ok) {
42
+ if (response.status === 401) throw new Error('Token 无效或已过期,请重新登录');
43
+ throw new Error(`HTTP ${response.status}`);
44
+ }
45
+ if (data.code !== 0 && data.code !== 200 && data.success !== true) {
46
+ throw new Error(data.msg || `API 错误: ${data.code}`);
47
+ }
48
+
49
+ return data.data || data;
50
+ }
51
+
52
+ export function qcOrdersCommand(program) {
53
+ program
54
+ .command('qc-orders')
55
+ .alias('qc')
56
+ .description('查询 QC 质检订单')
57
+ .requiredOption('--openId <openId>', '飞书用户 openId')
58
+ // 分页
59
+ .option('--pageNo <pageNo>', '页码', '1')
60
+ .option('--pageSize <pageSize>', '每页条数', '20')
61
+ // 订单状态
62
+ .option('--qcOrderStatus <qcOrderStatus>', 'QC 订单状态: -1-全部|0-待分配|1-已分配|2-已完成|3-已取消')
63
+ // 商品信息
64
+ .option('--goodsFullCode <goodsFullCode>', '大货款号')
65
+ .option('--goodsType <goodsType>', '大货类型: 1-大货|2-翻单|3-补料')
66
+ .option('--goodsGradeList <goodsGradeList>', '商品分级列表 (逗号分隔)')
67
+ // 供应商
68
+ .option('--supplierName <supplierName>', '供应商名称')
69
+ // 人员
70
+ .option('--midManager <midManager>', '跟单员名称')
71
+ .option('--checkGoods <checkGoods>', 'QC 跟单员名称')
72
+ .option('--tailManager <tailManager>', '尾查员名称')
73
+ .option('--qcUserIdList <qcUserIdList>', 'QC 质检员 ID 列表 (逗号分隔)')
74
+ .option('--prodManagerIdList <prodManagerIdList>', '生产负责人 ID 列表 (逗号分隔)')
75
+ .option('--purchaserIdList <purchaserIdList>', '采购员 ID 列表 (逗号分隔)')
76
+ // 时间
77
+ .option('--timeType <timeType>', '时间类型: 0-预约验货|1-下单|2-接单|3-分配|4-完成|5-交期')
78
+ .option('--startTime <startTime>', '开始时间 (ISO 8601)')
79
+ .option('--endTime <endTime>', '结束时间 (ISO 8601)')
80
+ // 输出
81
+ .option('--format <format>', '输出格式: table|json|pretty', 'table')
82
+ .action(async (opts) => {
83
+ const config = loadConfig();
84
+
85
+ const tokenCheck = await checkToken(opts.openId);
86
+ if (!tokenCheck.valid) {
87
+ const loginResult = await promptLogin(opts.openId);
88
+ if (!loginResult.success) process.exit(1);
89
+ try {
90
+ await doLogin(opts.openId, loginResult.mobile, loginResult.password);
91
+ console.log('✅ 登录成功!\n');
92
+ } catch (err) {
93
+ console.error('\n❌ 登录失败:', err.message);
94
+ process.exit(1);
95
+ }
96
+ }
97
+
98
+ try {
99
+ console.log('正在查询 QC 订单...');
100
+ const result = await queryQcOrders(opts.openId, opts, config);
101
+ console.log(opts.format === 'json'
102
+ ? JSON.stringify(result, null, 2)
103
+ : JSON.stringify(result, null, 2));
104
+ } catch (err) {
105
+ console.error('\n❌ 查询失败:', err.message);
106
+ process.exit(1);
107
+ }
108
+ });
109
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * scm-cli root command — creates the commander program with global flags
7
+ */
8
+ import { Command } from 'commander';
9
+ import { createLogger } from '../internal/index.js';
10
+
11
+ /**
12
+ * Create the root commander program
13
+ *
14
+ * @param {Object} opts - Options { version, name }
15
+ * @returns {Command} Configured commander program
16
+ */
17
+ export function createProgram(opts = {}) {
18
+ const { version = '1.0.0', name = 'scm-cli' } = opts;
19
+
20
+ const program = new Command();
21
+
22
+ program
23
+ .name(name)
24
+ .version(version)
25
+ .description('SCM CLI — 供应链管理命令行工具')
26
+ .option('--format <format>', '输出格式: table|json|pretty', 'table')
27
+ .option('--debug', '开启调试模式');
28
+
29
+ return program;
30
+ }
31
+
32
+ /**
33
+ * Build internal context for command execution
34
+ *
35
+ * @param {Object} opts - Global options from program
36
+ * @returns {Object} Internal context
37
+ */
38
+ export function buildInternalContext(opts = {}) {
39
+ const log = createLogger(opts.debug || false);
40
+ return { log };
41
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 WFS
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * @deprecated Import from src/internal/client/scm-client.js instead
7
+ */
8
+ export { scmClient, ScmClient } from '../internal/client/scm-client.js';