@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,334 @@
1
+ /**
2
+ * 报告相关命令(场景 24–25)
3
+ *
4
+ * 24. weighing-report (wr) — 称量报告(表头 + 明细)
5
+ * 25. bulk-report (br) — 半成品报告(表头 + 明细)
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { queryWithParams, close, sql } = require('../db');
11
+ const { resolveConfig } = require('../config');
12
+ const { printTable, printDetail } = require('../display');
13
+ const chalk = require('chalk');
14
+
15
+ // ─── 场景 24:称量报告 ─────────────────────────────────────────────────────────
16
+ const WEIGHING_REPORT_HEADER_SQL = `
17
+ ;WITH bulk_work_order AS (
18
+ SELECT TOP (1)
19
+ order_num, product_code, product_name, product_file_version,
20
+ quantity, product_units, device_name, workshop_name, production_note
21
+ FROM psm_work_order
22
+ WHERE order_num = @order_num AND tr_type = 2 AND ISNULL(is_deleted, 0) = 0
23
+ ORDER BY id DESC
24
+ ),
25
+ weight_work_order AS (
26
+ SELECT TOP (1) id, order_num
27
+ FROM psm_work_order
28
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
29
+ ORDER BY id DESC
30
+ )
31
+ SELECT
32
+ ww.order_num AS task_num,
33
+ ww.order_num,
34
+ bw.product_code,
35
+ bw.product_name,
36
+ bw.product_code + N'->' + ISNULL(bw.product_file_version, N'') AS recipe_code,
37
+ CAST(bw.quantity AS NVARCHAR(50)) + N' ' + ISNULL(bw.product_units, N'') AS planned_quantity,
38
+ bw.device_name AS device_group,
39
+ bw.workshop_name,
40
+ COALESCE(
41
+ CONVERT(NVARCHAR(10), we.execute_end_time, 23),
42
+ CONVERT(NVARCHAR(10), mx.max_weighing_time, 23)
43
+ ) AS weighing_date,
44
+ bw.production_note,
45
+ ww.order_num AS qr_content
46
+ FROM bulk_work_order bw
47
+ INNER JOIN weight_work_order ww ON 1 = 1
48
+ OUTER APPLY (
49
+ SELECT TOP (1) execute_end_time
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
+ OUTER APPLY (
55
+ SELECT MAX(e.operation_end_time) AS max_weighing_time
56
+ FROM psm_weighing w
57
+ INNER JOIN psm_weighing_execute e ON e.parent_id = w.id
58
+ WHERE w.parent_id = ww.id AND ISNULL(w.is_deleted, 0) = 0
59
+ ) mx;
60
+ `;
61
+
62
+ const WEIGHING_REPORT_DETAIL_SQL = `
63
+ ;WITH weight_work_order AS (
64
+ SELECT TOP (1) id, order_num
65
+ FROM psm_work_order
66
+ WHERE order_num = @order_num AND tr_type = 1 AND ISNULL(is_deleted, 0) = 0
67
+ ORDER BY id DESC
68
+ ),
69
+ weighing_task AS (
70
+ SELECT w.id, w.number, w.material_code, w.feeding_phase, w.quantity AS required_quantity
71
+ FROM weight_work_order ww
72
+ INNER JOIN psm_weighing w ON w.parent_id = ww.id AND ISNULL(w.is_deleted, 0) = 0 AND ISNULL(w.is_unweight, 0) = 0
73
+ ),
74
+ execute_base AS (
75
+ SELECT e.parent_id, e.id, e.actual_quantity, e.material_batch,
76
+ e.operation_end_time, e.device_name, e.operator
77
+ FROM psm_weighing_execute e
78
+ INNER JOIN weighing_task wt ON wt.id = e.parent_id
79
+ ),
80
+ execute_agg AS (
81
+ SELECT
82
+ parent_id,
83
+ SUM(ISNULL(actual_quantity, 0)) AS actual_quantity,
84
+ STRING_AGG(ISNULL(material_batch, N''), CHAR(13) + CHAR(10))
85
+ WITHIN GROUP (ORDER BY operation_end_time, id) AS material_batch,
86
+ STRING_AGG(CONVERT(NVARCHAR(16), operation_end_time, 120), CHAR(13) + CHAR(10))
87
+ WITHIN GROUP (ORDER BY operation_end_time, id) AS weighing_time,
88
+ STRING_AGG(ISNULL(device_name, N''), CHAR(13) + CHAR(10))
89
+ WITHIN GROUP (ORDER BY operation_end_time, id) AS weighing_station,
90
+ STRING_AGG(ISNULL(operator, N''), CHAR(13) + CHAR(10))
91
+ WITHIN GROUP (ORDER BY operation_end_time, id) AS weighing_user
92
+ FROM execute_base
93
+ GROUP BY parent_id
94
+ )
95
+ SELECT
96
+ ROW_NUMBER() OVER (ORDER BY wt.number, wt.id) AS seq_no,
97
+ wt.material_code,
98
+ wt.feeding_phase,
99
+ wt.required_quantity,
100
+ ea.actual_quantity,
101
+ ea.material_batch,
102
+ ea.weighing_time,
103
+ ea.weighing_station,
104
+ ea.weighing_user
105
+ FROM weighing_task wt
106
+ LEFT JOIN execute_agg ea ON ea.parent_id = wt.id
107
+ ORDER BY seq_no;
108
+ `;
109
+
110
+ async function weighingReportCommand(orderNum, options, command) {
111
+ let dbConfig;
112
+ try { dbConfig = resolveConfig(command.parent.opts()); }
113
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
114
+
115
+ const params = [{ name: 'order_num', type: sql.NVarChar(100), value: orderNum }];
116
+
117
+ try {
118
+ console.log(chalk.dim(`⏳ 正在生成称量报告: ${orderNum} …`));
119
+ const [headerRows, detailRows] = await Promise.all([
120
+ queryWithParams(WEIGHING_REPORT_HEADER_SQL, params, dbConfig),
121
+ queryWithParams(WEIGHING_REPORT_DETAIL_SQL, params, dbConfig),
122
+ ]);
123
+
124
+ if (!headerRows || headerRows.length === 0) {
125
+ console.log(chalk.yellow(`⚠ 未找到工单号 ${orderNum}`));
126
+ return;
127
+ }
128
+ printDetail(headerRows[0], `称量报告表头 — ${orderNum}`);
129
+ printTable(detailRows, `称量报告明细 — ${orderNum}`);
130
+ } catch (err) {
131
+ console.error(chalk.red('✖ 查询失败:'), err.message);
132
+ process.exitCode = 1;
133
+ } finally { await close(); }
134
+ }
135
+
136
+ // ─── 场景 25:半成品报告 ───────────────────────────────────────────────────────
137
+ const BULK_REPORT_HEADER_SQL = `
138
+ ;WITH bulk_work_order AS (
139
+ SELECT TOP (1)
140
+ id, order_num, batch_num, product_code, product_name, product_file_version,
141
+ quantity, product_units, workshop_name, line_name, device_name, plan_start_time
142
+ FROM psm_work_order
143
+ WHERE order_num = @order_num AND tr_type = 2 AND ISNULL(is_deleted, 0) = 0
144
+ ORDER BY id DESC
145
+ )
146
+ SELECT
147
+ bwo.order_num,
148
+ bwo.batch_num,
149
+ bwo.product_code,
150
+ bwo.product_name,
151
+ CONVERT(NVARCHAR(50), CAST(bwo.quantity AS FLOAT)) + N' ' + ISNULL(bwo.product_units, N'') AS planned_quantity,
152
+ bwo.workshop_name,
153
+ bwo.line_name,
154
+ bwo.device_name,
155
+ CONVERT(NVARCHAR(10), bwo.plan_start_time, 120) AS plan_start_time,
156
+ CONVERT(NVARCHAR(19), be.execute_start_time, 120) AS execute_start_time,
157
+ CONVERT(NVARCHAR(19), be.execute_end_time, 120) AS execute_end_time,
158
+ CASE
159
+ WHEN be.execute_state IS NULL THEN N'未生成生产执行记录'
160
+ WHEN be.execute_state = 0 THEN N'待生产' WHEN be.execute_state = 1 THEN N'生产中'
161
+ WHEN be.execute_state = 2 THEN N'已暂停' WHEN be.execute_state = 4 THEN N'投料完成'
162
+ WHEN be.execute_state = 8 THEN N'出锅中' WHEN be.execute_state = 16 THEN N'已出锅'
163
+ ELSE N'未知状态'
164
+ END AS production_status,
165
+ CASE
166
+ WHEN be.quality_state IS NULL THEN N'未生成生产执行记录'
167
+ WHEN be.quality_state = 0 THEN N'待质检' WHEN be.quality_state = 1 THEN N'已发起质检'
168
+ WHEN be.quality_state = 2 THEN N'部分合格' WHEN be.quality_state = 4 THEN N'不合格'
169
+ WHEN be.quality_state = 8 THEN N'合格' WHEN be.quality_state = 16 THEN N'在线返工'
170
+ WHEN be.quality_state = 32 THEN N'出锅返工' WHEN be.quality_state = 64 THEN N'再次请检'
171
+ WHEN be.quality_state = 128 THEN N'无法识别'
172
+ ELSE N'未知状态'
173
+ END AS quality_status
174
+ FROM bulk_work_order bwo
175
+ LEFT JOIN psm_bulk_execute_work_order be
176
+ ON be.work_order_id = bwo.id AND ISNULL(be.is_deleted, 0) = 0;
177
+ `;
178
+
179
+ const BULK_REPORT_DETAIL_SQL = `
180
+ ;WITH bulk_work_order AS (
181
+ SELECT TOP (1) id
182
+ FROM psm_work_order
183
+ WHERE order_num = @order_num AND tr_type = 2 AND ISNULL(is_deleted, 0) = 0
184
+ ORDER BY id DESC
185
+ ),
186
+ premix_barcode_base AS (
187
+ SELECT NULL AS pot_sort_num, CAST(p.unit_number AS NVARCHAR(100)) AS pot_sort_text,
188
+ p.name AS pot_name, s.step_number, s.step_desc, b.id AS barcode_id, b.number AS item_sort,
189
+ N'手投料' AS function_name,
190
+ N'项号:' + ISNULL(b.feeding_phase, N'') + CHAR(13)+CHAR(10)
191
+ + N'编码:' + ISNULL(b.code, N'') + CHAR(13)+CHAR(10)
192
+ + N'设定数量:' + q2.qty_text AS setting_value
193
+ FROM psm_bulk_trcp_logic_premix_barcode b
194
+ CROSS APPLY (SELECT LTRIM(STR(CONVERT(DECIMAL(38,10), ISNULL(b.quantity,0)),38,10)) AS qty_raw) q0
195
+ CROSS APPLY (SELECT LEFT(q0.qty_raw, LEN(q0.qty_raw)-PATINDEX('%[^0]%',REVERSE(q0.qty_raw))+1) AS qty_trim) q1
196
+ CROSS APPLY (SELECT CASE WHEN RIGHT(q1.qty_trim,1)='.' THEN LEFT(q1.qty_trim,LEN(q1.qty_trim)-1) ELSE q1.qty_trim END AS qty_text) q2
197
+ INNER JOIN psm_bulk_trcp_logic_premix_stepgen s ON b.parent_id = s.id AND ISNULL(s.is_delete,0)=0
198
+ INNER JOIN psm_bulk_trcp_logic_premix p ON s.parent_id = p.id AND ISNULL(p.is_delete,0)=0
199
+ WHERE b.work_order_id = (SELECT id FROM bulk_work_order) AND ISNULL(b.is_delete,0)=0
200
+ ),
201
+ premix_barcode_exec_numbered AS (
202
+ SELECT e.put_id, ROW_NUMBER() OVER (PARTITION BY e.put_id ORDER BY e.create_time, e.id) AS pkg_no,
203
+ q2.qty_text, e.batch_number, e.create_time
204
+ FROM psm_bulk_trcp_logic_premix_barcode_execute e
205
+ CROSS APPLY (SELECT LTRIM(STR(CONVERT(DECIMAL(38,10),ISNULL(e.quantity,0)),38,10)) AS qty_raw) q0
206
+ CROSS APPLY (SELECT LEFT(q0.qty_raw,LEN(q0.qty_raw)-PATINDEX('%[^0]%',REVERSE(q0.qty_raw))+1) AS qty_trim) q1
207
+ CROSS APPLY (SELECT CASE WHEN RIGHT(q1.qty_trim,1)='.' THEN LEFT(q1.qty_trim,LEN(q1.qty_trim)-1) ELSE q1.qty_trim END AS qty_text) q2
208
+ WHERE e.work_order_id = (SELECT id FROM bulk_work_order)
209
+ ),
210
+ premix_barcode_exec_agg AS (
211
+ SELECT put_id,
212
+ STRING_AGG(N'【第'+CAST(pkg_no AS NVARCHAR(20))+N'包】数量:'+qty_text+N' 批次:'+ISNULL(batch_number,N''), CHAR(13)+CHAR(10)) WITHIN GROUP (ORDER BY pkg_no) AS report_value,
213
+ STRING_AGG(N'【第'+CAST(pkg_no AS NVARCHAR(20))+N'包】'+CONVERT(NVARCHAR(19),create_time,120), CHAR(13)+CHAR(10)) WITHIN GROUP (ORDER BY pkg_no) AS action_time
214
+ FROM premix_barcode_exec_numbered GROUP BY put_id
215
+ ),
216
+ premix_feedback_base AS (
217
+ SELECT NULL AS pot_sort_num, CAST(p.unit_number AS NVARCHAR(100)) AS pot_sort_text,
218
+ p.name AS pot_name, s.step_number, s.step_desc, f.id AS feedback_id, f.iNumber AS item_sort,
219
+ N'反馈' AS function_name, ISNULL(f.cName, N'') AS setting_value
220
+ FROM psm_bulk_trcp_logic_premix_feedback f
221
+ INNER JOIN psm_bulk_trcp_logic_premix_stepgen s ON f.FID = s.id AND ISNULL(s.is_delete,0)=0
222
+ INNER JOIN psm_bulk_trcp_logic_premix p ON s.parent_id = p.id AND ISNULL(p.is_delete,0)=0
223
+ WHERE f.iOrdID = (SELECT id FROM bulk_work_order)
224
+ ),
225
+ step_phase_base AS (
226
+ SELECT sg.unit_number AS pot_sort_num, CAST(sg.unit_number AS NVARCHAR(100)) AS pot_sort_text,
227
+ sg.unit_name AS pot_name, sg.step_number, sg.step_description AS step_desc,
228
+ sp.id AS phase_id, sp.phase_number AS item_sort,
229
+ ISNULL(sp.function_name,N'') AS function_name, ISNULL(sp.aboutvis,N'') AS setting_value,
230
+ ISNULL(sp.execute_text,N'') AS report_value
231
+ FROM psm_bulk_trcp_logic_step_phase sp
232
+ INNER JOIN psm_bulk_trcp_logic_stepgen sg
233
+ ON sg.work_order_id=sp.work_order_id AND sg.techrcp_id=sp.techrcp_id
234
+ AND sg.unit_number=sp.unit_number AND sg.step_number=sp.step_number AND ISNULL(sg.is_delete,0)=0
235
+ WHERE sp.work_order_id = (SELECT id FROM bulk_work_order)
236
+ AND ISNULL(sp.is_delete,0)=0 AND NOT (sp.ep_type=999901 AND sp.rpar00=5)
237
+ ),
238
+ step_phase_first_exec AS (
239
+ SELECT x.parent_id, x.start_time FROM (
240
+ SELECT e.parent_id, e.start_time,
241
+ ROW_NUMBER() OVER (PARTITION BY e.parent_id ORDER BY e.start_time, e.id) AS rn
242
+ FROM psm_bulk_trcp_logic_step_phase_execute e
243
+ WHERE e.work_order_id = (SELECT id FROM bulk_work_order)
244
+ ) x WHERE x.rn = 1
245
+ ),
246
+ step_barcode_base AS (
247
+ SELECT sg.unit_number AS pot_sort_num, CAST(sg.unit_number AS NVARCHAR(100)) AS pot_sort_text,
248
+ sg.unit_name AS pot_name, sg.step_number, sg.step_description AS step_desc,
249
+ b.id AS barcode_id, b.number AS item_sort, N'手投料' AS function_name,
250
+ N'项号:' + ISNULL(b.feeding_phase,N'') + CHAR(13)+CHAR(10)
251
+ + N'编码:' + ISNULL(b.code,N'') + CHAR(13)+CHAR(10)
252
+ + N'设定数量:' + q2.qty_text AS setting_value
253
+ FROM psm_bulk_trcp_logic_step_barcode b
254
+ CROSS APPLY (SELECT LTRIM(STR(CONVERT(DECIMAL(38,10),ISNULL(b.quantity,0)),38,10)) AS qty_raw) q0
255
+ CROSS APPLY (SELECT LEFT(q0.qty_raw,LEN(q0.qty_raw)-PATINDEX('%[^0]%',REVERSE(q0.qty_raw))+1) AS qty_trim) q1
256
+ CROSS APPLY (SELECT CASE WHEN RIGHT(q1.qty_trim,1)='.' THEN LEFT(q1.qty_trim,LEN(q1.qty_trim)-1) ELSE q1.qty_trim END AS qty_text) q2
257
+ INNER JOIN psm_bulk_trcp_logic_stepgen sg
258
+ ON sg.work_order_id=b.work_order_id AND sg.techrcp_id=b.techrcp_id
259
+ AND sg.unit_number=b.unit_number AND sg.step_number=b.step_number AND ISNULL(sg.is_delete,0)=0
260
+ WHERE b.work_order_id = (SELECT id FROM bulk_work_order) AND ISNULL(b.is_delete,0)=0
261
+ ),
262
+ step_barcode_exec_numbered AS (
263
+ SELECT r.tr_set_barcode_id,
264
+ ROW_NUMBER() OVER (PARTITION BY r.tr_set_barcode_id ORDER BY r.create_time, r.id) AS pkg_no,
265
+ q2.qty_text, r.barcode_material_batch, r.create_time
266
+ FROM psm_bulk_trcplc_report_put_material_item r
267
+ CROSS APPLY (SELECT LTRIM(STR(CONVERT(DECIMAL(38,10),ISNULL(r.barcode_material_quantity,0)),38,10)) AS qty_raw) q0
268
+ CROSS APPLY (SELECT LEFT(q0.qty_raw,LEN(q0.qty_raw)-PATINDEX('%[^0]%',REVERSE(q0.qty_raw))+1) AS qty_trim) q1
269
+ CROSS APPLY (SELECT CASE WHEN RIGHT(q1.qty_trim,1)='.' THEN LEFT(q1.qty_trim,LEN(q1.qty_trim)-1) ELSE q1.qty_trim END AS qty_text) q2
270
+ WHERE r.work_order_id = (SELECT id FROM bulk_work_order)
271
+ ),
272
+ step_barcode_exec_agg AS (
273
+ SELECT tr_set_barcode_id,
274
+ STRING_AGG(N'【第'+CAST(pkg_no AS NVARCHAR(20))+N'包】数量:'+qty_text+N' 批次:'+ISNULL(barcode_material_batch,N''), CHAR(13)+CHAR(10)) WITHIN GROUP (ORDER BY pkg_no) AS report_value,
275
+ STRING_AGG(N'【第'+CAST(pkg_no AS NVARCHAR(20))+N'包】'+CONVERT(NVARCHAR(19),create_time,120), CHAR(13)+CHAR(10)) WITHIN GROUP (ORDER BY pkg_no) AS action_time
276
+ FROM step_barcode_exec_numbered GROUP BY tr_set_barcode_id
277
+ ),
278
+ final_report AS (
279
+ SELECT 1 AS section_sort, pb.pot_sort_num, pb.pot_sort_text, pb.step_number, 1 AS type_sort, pb.item_sort,
280
+ pb.pot_name, N'第'+CAST(pb.step_number AS NVARCHAR(20))+N'步' AS step_name, pb.step_desc,
281
+ pb.function_name, pb.setting_value, ISNULL(pa.report_value,N'') AS report_value, ISNULL(pa.action_time,N'') AS action_time
282
+ FROM premix_barcode_base pb LEFT JOIN premix_barcode_exec_agg pa ON pa.put_id = pb.barcode_id
283
+ UNION ALL
284
+ SELECT 1, pf.pot_sort_num, pf.pot_sort_text, pf.step_number, 2, pf.item_sort,
285
+ pf.pot_name, N'第'+CAST(pf.step_number AS NVARCHAR(20))+N'步', pf.step_desc,
286
+ pf.function_name, pf.setting_value, ISNULL(fe.value,N''), CONVERT(NVARCHAR(19), fe.create_time, 120)
287
+ FROM premix_feedback_base pf
288
+ LEFT JOIN psm_bulk_trcp_logic_premix_feedback_execute fe
289
+ ON fe.parent_id = pf.feedback_id AND fe.work_order_id = (SELECT id FROM bulk_work_order)
290
+ UNION ALL
291
+ SELECT 2, sp.pot_sort_num, sp.pot_sort_text, sp.step_number, 1, sp.item_sort,
292
+ sp.pot_name, N'第'+CAST(sp.step_number AS NVARCHAR(20))+N'步', sp.step_desc,
293
+ sp.function_name, sp.setting_value, sp.report_value, CONVERT(NVARCHAR(19), se.start_time, 120)
294
+ FROM step_phase_base sp LEFT JOIN step_phase_first_exec se ON se.parent_id = sp.phase_id
295
+ UNION ALL
296
+ SELECT 2, sb.pot_sort_num, sb.pot_sort_text, sb.step_number, 2, sb.item_sort,
297
+ sb.pot_name, N'第'+CAST(sb.step_number AS NVARCHAR(20))+N'步', sb.step_desc,
298
+ sb.function_name, sb.setting_value, ISNULL(sa.report_value,N''), ISNULL(sa.action_time,N'')
299
+ FROM step_barcode_base sb LEFT JOIN step_barcode_exec_agg sa ON sa.tr_set_barcode_id = sb.barcode_id
300
+ )
301
+ SELECT pot_name, step_name, step_desc, function_name, setting_value, report_value, action_time
302
+ FROM final_report
303
+ ORDER BY section_sort,
304
+ CASE WHEN pot_sort_num IS NULL THEN 1 ELSE 2 END,
305
+ pot_sort_num, pot_sort_text, step_number, type_sort, item_sort;
306
+ `;
307
+
308
+ async function bulkReportCommand(orderNum, options, command) {
309
+ let dbConfig;
310
+ try { dbConfig = resolveConfig(command.parent.opts()); }
311
+ catch (err) { console.error(chalk.red('✖ 连接配置错误:'), err.message); process.exitCode = 1; return; }
312
+
313
+ const params = [{ name: 'order_num', type: sql.NVarChar(100), value: orderNum }];
314
+
315
+ try {
316
+ console.log(chalk.dim(`⏳ 正在生成半成品报告: ${orderNum} …`));
317
+ const [headerRows, detailRows] = await Promise.all([
318
+ queryWithParams(BULK_REPORT_HEADER_SQL, params, dbConfig),
319
+ queryWithParams(BULK_REPORT_DETAIL_SQL, params, dbConfig),
320
+ ]);
321
+
322
+ if (!headerRows || headerRows.length === 0) {
323
+ console.log(chalk.yellow(`⚠ 未找到工单号 ${orderNum}`));
324
+ return;
325
+ }
326
+ printDetail(headerRows[0], `半成品报告表头 — ${orderNum}`);
327
+ printTable(detailRows, `半成品报告明细 — ${orderNum}`);
328
+ } catch (err) {
329
+ console.error(chalk.red('✖ 查询失败:'), err.message);
330
+ process.exitCode = 1;
331
+ } finally { await close(); }
332
+ }
333
+
334
+ module.exports = { weighingReportCommand, bulkReportCommand };