@chaobinchen/mes-cli 1.0.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.
@@ -0,0 +1,490 @@
1
+ /**
2
+ * 称量相关命令(场景 9–17)
3
+ *
4
+ * 9. weighing-overview (wov) — 称量工单执行概览
5
+ * 10. weighing-tasks (wt) — 称量任务清单
6
+ * 11. pending-weighing (pw) — 未完成称量项
7
+ * 12. weighing-detail (wdt) — 称量执行明细
8
+ * 13. operator-records (orec) — 操作员称量记录
9
+ * 14. review-status (rvs) — 称量复核状态
10
+ * 15. accounting-status (acs) — 称量账务状态
11
+ * 16. weighing-exceptions (wex) — 称量异常或作废记录
12
+ * 17. weighing-duration (wdur) — 称量耗时分析
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const { queryWithParams, close, sql } = require('../db');
18
+ const { resolveConfig } = require('../config');
19
+ const { printTable, printDetail } = require('../display');
20
+ const chalk = require('chalk');
21
+
22
+ // ─── 场景 9:称量工单执行概览 ──────────────────────────────────────────────────
23
+ const WEIGHING_OVERVIEW_SQL = `
24
+ ;WITH weight_work_order AS (
25
+ SELECT TOP (1) id, order_num
26
+ FROM psm_work_order
27
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
28
+ ORDER BY id DESC
29
+ )
30
+ SELECT
31
+ ww.order_num,
32
+ we.execute_start_time,
33
+ we.execute_end_time,
34
+ we.execute_progress,
35
+ CASE
36
+ WHEN we.execute_state = 1 THEN N'未称量'
37
+ WHEN we.execute_state = 2 THEN N'称量中'
38
+ WHEN we.execute_state = 8 THEN N'称量完成'
39
+ ELSE N'未知状态'
40
+ END AS weighing_status,
41
+ CASE
42
+ WHEN we.stock_preparation_state = 1 THEN N'未发起领料'
43
+ WHEN we.stock_preparation_state = 2 THEN N'已发起领料'
44
+ ELSE N'未知状态'
45
+ END AS stock_preparation_status,
46
+ we.backhaul_failure_reason
47
+ FROM weight_work_order ww
48
+ OUTER APPLY (
49
+ SELECT TOP (1) *
50
+ FROM psm_weighing_work_order_execute
51
+ WHERE parent_id = ww.id AND ISNULL(is_deleted, 0) = 0
52
+ ORDER BY update_time DESC, id DESC
53
+ ) we;
54
+ `;
55
+
56
+ async function weighingOverviewCommand(orderNum, options, command) {
57
+ let dbConfig;
58
+ try { dbConfig = resolveConfig(command.parent.opts()); }
59
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
60
+
61
+ try {
62
+ console.log(chalk.dim(`⏳ 正在查询称量工单执行概览: ${orderNum} …`));
63
+ const rows = await queryWithParams(WEIGHING_OVERVIEW_SQL, [
64
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
65
+ ], dbConfig);
66
+ if (!rows || rows.length === 0) {
67
+ console.log(chalk.yellow(`⚠ 未找到工单号 ${orderNum} 的称量工单`));
68
+ return;
69
+ }
70
+ printDetail(rows[0], `称量工单执行概览 — ${orderNum}`);
71
+ } catch (err) {
72
+ console.error(chalk.red('✖ 查询失败:'), err.message);
73
+ process.exitCode = 1;
74
+ } finally { await close(); }
75
+ }
76
+
77
+ // ─── 场景 10:称量任务清单 ─────────────────────────────────────────────────────
78
+ const WEIGHING_TASKS_SQL = `
79
+ ;WITH weight_work_order AS (
80
+ SELECT TOP (1) id
81
+ FROM psm_work_order
82
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
83
+ ORDER BY id DESC
84
+ )
85
+ SELECT
86
+ w.number,
87
+ w.feeding_phase,
88
+ w.material_code,
89
+ w.material_name,
90
+ w.quantity,
91
+ w.recommended_batch,
92
+ CASE
93
+ WHEN w.state = 0 THEN N'待称量'
94
+ WHEN w.state = 1 THEN N'称量中'
95
+ WHEN w.state = 2 THEN N'已称量'
96
+ ELSE N'未知状态'
97
+ END AS task_status,
98
+ CASE
99
+ WHEN w.review_status = 1 THEN N'未称量复核'
100
+ WHEN w.review_status = 2 THEN N'称量复核中'
101
+ WHEN w.review_status = 4 THEN N'已称量复核'
102
+ ELSE N'未知状态'
103
+ END AS review_status,
104
+ CASE
105
+ WHEN w.accounting_state = 1 THEN N'无需过账'
106
+ WHEN w.accounting_state = 2 THEN N'未称量下架'
107
+ WHEN w.accounting_state = 4 THEN N'部分下架过账'
108
+ WHEN w.accounting_state = 8 THEN N'全部下架过账'
109
+ WHEN w.accounting_state = 16 THEN N'部分上架过账'
110
+ WHEN w.accounting_state = 32 THEN N'全部上架过账'
111
+ WHEN w.accounting_state = 64 THEN N'预投料复核全部移库过账'
112
+ WHEN w.accounting_state = 128 THEN N'预投料复核部分移库过账'
113
+ ELSE N'未知状态'
114
+ END AS accounting_status,
115
+ w.current_weighing_user,
116
+ w.note
117
+ FROM psm_weighing w
118
+ WHERE w.parent_id = (SELECT id FROM weight_work_order)
119
+ AND ISNULL(w.is_deleted, 0) = 0
120
+ ORDER BY w.number, w.material_code;
121
+ `;
122
+
123
+ async function weighingTasksCommand(orderNum, options, command) {
124
+ let dbConfig;
125
+ try { dbConfig = resolveConfig(command.parent.opts()); }
126
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
127
+
128
+ try {
129
+ console.log(chalk.dim(`⏳ 正在查询称量任务清单: ${orderNum} …`));
130
+ const rows = await queryWithParams(WEIGHING_TASKS_SQL, [
131
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
132
+ ], dbConfig);
133
+ printTable(rows, `称量任务清单 — ${orderNum}`);
134
+ } catch (err) {
135
+ console.error(chalk.red('✖ 查询失败:'), err.message);
136
+ process.exitCode = 1;
137
+ } finally { await close(); }
138
+ }
139
+
140
+ // ─── 场景 11:未完成称量项 ─────────────────────────────────────────────────────
141
+ const PENDING_WEIGHING_SQL = `
142
+ ;WITH weight_work_order AS (
143
+ SELECT TOP (1) id
144
+ FROM psm_work_order
145
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
146
+ ORDER BY id DESC
147
+ )
148
+ SELECT
149
+ w.number,
150
+ w.material_code,
151
+ w.material_name,
152
+ w.quantity,
153
+ w.recommended_batch,
154
+ CASE
155
+ WHEN w.state = 0 THEN N'待称量'
156
+ WHEN w.state = 1 THEN N'称量中'
157
+ WHEN w.state = 2 THEN N'已称量'
158
+ ELSE N'未知状态'
159
+ END AS task_status,
160
+ w.current_weighing_user
161
+ FROM psm_weighing w
162
+ WHERE w.parent_id = (SELECT id FROM weight_work_order)
163
+ AND ISNULL(w.is_deleted, 0) = 0
164
+ AND w.state IN (0, 1)
165
+ ORDER BY w.number, w.material_code;
166
+ `;
167
+
168
+ async function pendingWeighingCommand(orderNum, options, command) {
169
+ let dbConfig;
170
+ try { dbConfig = resolveConfig(command.parent.opts()); }
171
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
172
+
173
+ try {
174
+ console.log(chalk.dim(`⏳ 正在查询未完成称量项: ${orderNum} …`));
175
+ const rows = await queryWithParams(PENDING_WEIGHING_SQL, [
176
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
177
+ ], dbConfig);
178
+ printTable(rows, `未完成称量项 — ${orderNum}`);
179
+ } catch (err) {
180
+ console.error(chalk.red('✖ 查询失败:'), err.message);
181
+ process.exitCode = 1;
182
+ } finally { await close(); }
183
+ }
184
+
185
+ // ─── 场景 12:称量执行明细 ─────────────────────────────────────────────────────
186
+ const WEIGHING_DETAIL_SQL = `
187
+ ;WITH weight_work_order AS (
188
+ SELECT TOP (1) id
189
+ FROM psm_work_order
190
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
191
+ ORDER BY id DESC
192
+ )
193
+ SELECT
194
+ w.number,
195
+ w.material_code,
196
+ w.material_name,
197
+ w.quantity,
198
+ e.actual_quantity,
199
+ e.material_batch,
200
+ e.operator,
201
+ e.operation_start_time,
202
+ e.operation_end_time,
203
+ e.picking_store_name,
204
+ e.putaway_store_name,
205
+ CASE
206
+ WHEN e.id IS NULL THEN N'未执行'
207
+ WHEN e.is_dosing = 1 THEN N'已投加'
208
+ ELSE N'未投加'
209
+ END AS dosing_status,
210
+ CASE
211
+ WHEN e.id IS NULL THEN N'未执行'
212
+ WHEN e.is_canceled = 1 THEN N'已作废'
213
+ ELSE N'未作废'
214
+ END AS canceled_status
215
+ FROM psm_weighing w
216
+ LEFT JOIN psm_weighing_execute e ON e.parent_id = w.id
217
+ WHERE w.parent_id = (SELECT id FROM weight_work_order)
218
+ AND ISNULL(w.is_deleted, 0) = 0
219
+ AND (@material_code IS NULL OR w.material_code = @material_code)
220
+ ORDER BY w.number, e.operation_start_time, e.id;
221
+ `;
222
+
223
+ async function weighingDetailCommand(orderNum, options, command) {
224
+ let dbConfig;
225
+ try { dbConfig = resolveConfig(command.parent.opts()); }
226
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
227
+
228
+ try {
229
+ console.log(chalk.dim(`⏳ 正在查询称量执行明细: ${orderNum} …`));
230
+ const rows = await queryWithParams(WEIGHING_DETAIL_SQL, [
231
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
232
+ { name: 'material_code', type: sql.NVarChar(100), value: options.material || null },
233
+ ], dbConfig);
234
+ printTable(rows, `称量执行明细 — ${orderNum}`);
235
+ } catch (err) {
236
+ console.error(chalk.red('✖ 查询失败:'), err.message);
237
+ process.exitCode = 1;
238
+ } finally { await close(); }
239
+ }
240
+
241
+ // ─── 场景 13:操作员称量记录 ───────────────────────────────────────────────────
242
+ const OPERATOR_RECORDS_SQL = `
243
+ SELECT
244
+ e.operator,
245
+ wo.order_num,
246
+ wo.batch_num,
247
+ w.material_code,
248
+ w.material_name,
249
+ e.material_batch,
250
+ e.actual_quantity,
251
+ e.operation_start_time,
252
+ e.operation_end_time,
253
+ e.picking_store_name,
254
+ e.putaway_store_name
255
+ FROM psm_weighing_execute e
256
+ INNER JOIN psm_weighing w ON e.parent_id = w.id
257
+ INNER JOIN psm_work_order wo
258
+ ON e.work_order_id = wo.id AND wo.tr_type = 1 AND ISNULL(wo.is_deleted, 0) = 0
259
+ WHERE ISNULL(w.is_deleted, 0) = 0
260
+ AND e.operator = @operator
261
+ AND e.operation_start_time >= @start_date
262
+ AND e.operation_start_time < DATEADD(DAY, 1, @end_date)
263
+ ORDER BY e.operation_start_time DESC, wo.order_num;
264
+ `;
265
+
266
+ async function operatorRecordsCommand(operator, options, command) {
267
+ let dbConfig;
268
+ try { dbConfig = resolveConfig(command.parent.opts()); }
269
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
270
+
271
+ if (!options.start || !options.end) {
272
+ console.error(chalk.red('✖ 请通过 --start <YYYY-MM-DD> --end <YYYY-MM-DD> 指定时间范围'));
273
+ process.exitCode = 1;
274
+ return;
275
+ }
276
+
277
+ try {
278
+ console.log(chalk.dim(`⏳ 正在查询操作员 ${operator} 的称量记录 …`));
279
+ const rows = await queryWithParams(OPERATOR_RECORDS_SQL, [
280
+ { name: 'operator', type: sql.NVarChar(100), value: operator },
281
+ { name: 'start_date', type: sql.Date, value: new Date(options.start) },
282
+ { name: 'end_date', type: sql.Date, value: new Date(options.end) },
283
+ ], dbConfig);
284
+ printTable(rows, `操作员称量记录 — ${operator} (${options.start} ~ ${options.end})`);
285
+ } catch (err) {
286
+ console.error(chalk.red('✖ 查询失败:'), err.message);
287
+ process.exitCode = 1;
288
+ } finally { await close(); }
289
+ }
290
+
291
+ // ─── 场景 14:称量复核状态 ─────────────────────────────────────────────────────
292
+ const REVIEW_STATUS_SQL = `
293
+ ;WITH weight_work_order AS (
294
+ SELECT TOP (1) id
295
+ FROM psm_work_order
296
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
297
+ ORDER BY id DESC
298
+ )
299
+ SELECT
300
+ w.number,
301
+ w.material_code,
302
+ w.material_name,
303
+ w.quantity,
304
+ CASE
305
+ WHEN w.review_status = 1 THEN N'未称量复核'
306
+ WHEN w.review_status = 2 THEN N'称量复核中'
307
+ WHEN w.review_status = 4 THEN N'已称量复核'
308
+ ELSE N'未知状态'
309
+ END AS review_status,
310
+ w.current_weighing_user,
311
+ w.weighing_progress
312
+ FROM psm_weighing w
313
+ WHERE w.parent_id = (SELECT id FROM weight_work_order)
314
+ AND ISNULL(w.is_deleted, 0) = 0
315
+ ORDER BY w.number, w.material_code;
316
+ `;
317
+
318
+ async function reviewStatusCommand(orderNum, options, command) {
319
+ let dbConfig;
320
+ try { dbConfig = resolveConfig(command.parent.opts()); }
321
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
322
+
323
+ try {
324
+ console.log(chalk.dim(`⏳ 正在查询称量复核状态: ${orderNum} …`));
325
+ const rows = await queryWithParams(REVIEW_STATUS_SQL, [
326
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
327
+ ], dbConfig);
328
+ printTable(rows, `称量复核状态 — ${orderNum}`);
329
+ } catch (err) {
330
+ console.error(chalk.red('✖ 查询失败:'), err.message);
331
+ process.exitCode = 1;
332
+ } finally { await close(); }
333
+ }
334
+
335
+ // ─── 场景 15:称量账务状态 ─────────────────────────────────────────────────────
336
+ const ACCOUNTING_STATUS_SQL = `
337
+ ;WITH weight_work_order AS (
338
+ SELECT TOP (1) id
339
+ FROM psm_work_order
340
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
341
+ ORDER BY id DESC
342
+ )
343
+ SELECT
344
+ w.number,
345
+ w.material_code,
346
+ w.material_name,
347
+ CASE
348
+ WHEN w.accounting_state = 1 THEN N'无需过账'
349
+ WHEN w.accounting_state = 2 THEN N'未称量下架'
350
+ WHEN w.accounting_state = 4 THEN N'部分下架过账'
351
+ WHEN w.accounting_state = 8 THEN N'全部下架过账'
352
+ WHEN w.accounting_state = 16 THEN N'部分上架过账'
353
+ WHEN w.accounting_state = 32 THEN N'全部上架过账'
354
+ WHEN w.accounting_state = 64 THEN N'预投料复核全部移库过账'
355
+ WHEN w.accounting_state = 128 THEN N'预投料复核部分移库过账'
356
+ ELSE N'未知状态'
357
+ END AS accounting_status,
358
+ CASE WHEN w.is_shifting_storage = 1 THEN N'是' ELSE N'否' END AS shifting_storage_flag,
359
+ CASE WHEN w.is_bucket_management = 1 THEN N'是' ELSE N'否' END AS bucket_management_flag,
360
+ CASE WHEN w.is_canceled = 1 THEN N'是' ELSE N'否' END AS canceled_flag
361
+ FROM psm_weighing w
362
+ WHERE w.parent_id = (SELECT id FROM weight_work_order)
363
+ AND ISNULL(w.is_deleted, 0) = 0
364
+ ORDER BY w.number, w.material_code;
365
+ `;
366
+
367
+ async function accountingStatusCommand(orderNum, options, command) {
368
+ let dbConfig;
369
+ try { dbConfig = resolveConfig(command.parent.opts()); }
370
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
371
+
372
+ try {
373
+ console.log(chalk.dim(`⏳ 正在查询称量账务状态: ${orderNum} …`));
374
+ const rows = await queryWithParams(ACCOUNTING_STATUS_SQL, [
375
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
376
+ ], dbConfig);
377
+ printTable(rows, `称量账务状态 — ${orderNum}`);
378
+ } catch (err) {
379
+ console.error(chalk.red('✖ 查询失败:'), err.message);
380
+ process.exitCode = 1;
381
+ } finally { await close(); }
382
+ }
383
+
384
+ // ─── 场景 16:称量异常或作废记录 ──────────────────────────────────────────────
385
+ const WEIGHING_EXCEPTIONS_SQL = `
386
+ ;WITH weight_work_order AS (
387
+ SELECT TOP (1) id
388
+ FROM psm_work_order
389
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
390
+ ORDER BY id DESC
391
+ )
392
+ SELECT
393
+ w.material_code,
394
+ w.material_name,
395
+ e.material_batch,
396
+ e.operator,
397
+ CASE WHEN e.is_canceled = 1 THEN N'已作废' ELSE N'未作废' END AS canceled_status,
398
+ e.canceled_time,
399
+ e.failure_reason,
400
+ e.mismatch_reason,
401
+ e.cancel_putaway_store_name
402
+ FROM psm_weighing_execute e
403
+ INNER JOIN psm_weighing w ON e.parent_id = w.id
404
+ WHERE e.work_order_id = (SELECT id FROM weight_work_order)
405
+ AND (
406
+ e.is_canceled = 1
407
+ OR ISNULL(e.failure_reason, N'') <> N''
408
+ OR ISNULL(e.mismatch_reason, N'') <> N''
409
+ )
410
+ ORDER BY e.operation_start_time DESC, e.id DESC;
411
+ `;
412
+
413
+ async function weighingExceptionsCommand(orderNum, options, command) {
414
+ let dbConfig;
415
+ try { dbConfig = resolveConfig(command.parent.opts()); }
416
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
417
+
418
+ try {
419
+ console.log(chalk.dim(`⏳ 正在查询称量异常/作废记录: ${orderNum} …`));
420
+ const rows = await queryWithParams(WEIGHING_EXCEPTIONS_SQL, [
421
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
422
+ ], dbConfig);
423
+ printTable(rows, `称量异常/作废记录 — ${orderNum}`);
424
+ } catch (err) {
425
+ console.error(chalk.red('✖ 查询失败:'), err.message);
426
+ process.exitCode = 1;
427
+ } finally { await close(); }
428
+ }
429
+
430
+ // ─── 场景 17:称量耗时分析 ─────────────────────────────────────────────────────
431
+ const WEIGHING_DURATION_SQL = `
432
+ ;WITH weight_work_order AS (
433
+ SELECT TOP (1) id, order_num
434
+ FROM psm_work_order
435
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
436
+ ORDER BY id DESC
437
+ )
438
+ SELECT
439
+ wwo.order_num,
440
+ w.number,
441
+ w.feeding_phase,
442
+ w.material_code,
443
+ w.material_name,
444
+ w.quantity,
445
+ e.actual_quantity,
446
+ e.operator,
447
+ e.operation_start_time,
448
+ e.operation_end_time,
449
+ CAST(DATEDIFF(SECOND, e.operation_start_time, e.operation_end_time) / 60.0 AS DECIMAL(18, 2)) AS weighing_duration_minutes,
450
+ CASE
451
+ WHEN w.state = 0 THEN N'待称量'
452
+ WHEN w.state = 1 THEN N'称量中'
453
+ WHEN w.state = 2 THEN N'已称量'
454
+ ELSE N'未知状态'
455
+ END AS task_status
456
+ FROM weight_work_order wwo
457
+ INNER JOIN psm_weighing w ON w.parent_id = wwo.id AND ISNULL(w.is_deleted, 0) = 0
458
+ LEFT JOIN psm_weighing_execute e ON e.parent_id = w.id
459
+ WHERE e.id IS NOT NULL
460
+ ORDER BY weighing_duration_minutes DESC, w.number, e.operation_end_time DESC;
461
+ `;
462
+
463
+ async function weighingDurationCommand(orderNum, options, command) {
464
+ let dbConfig;
465
+ try { dbConfig = resolveConfig(command.parent.opts()); }
466
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
467
+
468
+ try {
469
+ console.log(chalk.dim(`⏳ 正在分析称量耗时: ${orderNum} …`));
470
+ const rows = await queryWithParams(WEIGHING_DURATION_SQL, [
471
+ { name: 'order_num', type: sql.NVarChar(100), value: orderNum },
472
+ ], dbConfig);
473
+ printTable(rows, `称量耗时分析 — ${orderNum}`);
474
+ } catch (err) {
475
+ console.error(chalk.red('✖ 查询失败:'), err.message);
476
+ process.exitCode = 1;
477
+ } finally { await close(); }
478
+ }
479
+
480
+ module.exports = {
481
+ weighingOverviewCommand,
482
+ weighingTasksCommand,
483
+ pendingWeighingCommand,
484
+ weighingDetailCommand,
485
+ operatorRecordsCommand,
486
+ reviewStatusCommand,
487
+ accountingStatusCommand,
488
+ weighingExceptionsCommand,
489
+ weighingDurationCommand,
490
+ };