质量管理模块:指标按产品/类别/工序维护与绑定、不合格处理方式、检验提交入库判定

- 新增 QualityMetricController:按产品+检验类别+工序维护检测指标,支持标准绑定
- 指标查询按 inspectType/process 过滤,原材料/过程/出厂指标隔离
- 不合格管理新增处理方式(返工/报废/让步接收)字段及字典脚本
- 检验提交:仅合格入库,缺少采购单/产品信息时跳过入库不报错
- 补充演示数据与单元测试
已添加11个文件
已修改11个文件
1830 ■■■■■ 文件已修改
doc/20261029_disable_process_maintenance_menu.sql 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20261029_quality_demo_data.sql 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20261029_quality_unqualified_deal_type.sql 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20261030_quality_demo_data_extra.sql 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityMetricController.java 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMetricBindingDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMetricDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityTestStandardBindingMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityTestStandardParamMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-tide.yml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityTestStandardBindingMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityTestStandardParamMapper.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/ruoyi/quality/controller/QualityInspectControllerTest.java 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/ruoyi/quality/controller/QualityMetricControllerTest.java 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/ruoyi/quality/controller/QualityUnqualifiedControllerTest.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/ruoyi/quality/service/QualityInspectServiceImplTest.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20261029_disable_process_maintenance_menu.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
-- éšè—å¹¶åœç”¨â€œå·¥åºç»´æŠ¤â€èœå•(MySQL,可重复执行)。
-- å·¥åºæ•°æ®åŠ /productProcess/list æŽ¥å£ç»§ç»­ä¿ç•™ï¼Œä¾›è¿‡ç¨‹æ£€éªŒå’ŒæŒ‡æ ‡ç»‘定选择工序。
UPDATE sys_menu
SET visible = '1',
    status = '1',
    update_by = 'admin',
    update_time = NOW(),
    remark = CONCAT(IFNULL(remark, ''), IF(IFNULL(remark, '') = '', '', ';'), '已取消独立工序维护菜单')
WHERE menu_name = '工序维护'
  AND (visible <> '1' OR status <> '1');
doc/20261029_quality_demo_data.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,331 @@
-- è´¨é‡ç®¡ç†æ¼”示数据(MySQL 5.7+/8.0,可重复执行)
-- è¦†ç›–:指标维护、原材料检验、过程检验、出厂检验、不合格管理。
-- æ‰§è¡Œå‰è¯·å…ˆæ‰§è¡Œ 20261029_quality_unqualified_deal_type.sql。
-- é»˜è®¤ä½¿ç”¨ admin ç”¨æˆ·æ‰€å±žç§Ÿæˆ·ï¼›å¦‚不存在 admin,则使用最小租户 ID,最后回退到 0。
SET @demo_tenant_id = COALESCE(
    (SELECT tenant_id FROM sys_user WHERE user_name = 'admin' LIMIT 1),
    (SELECT MIN(tenant_id) FROM sys_user WHERE tenant_id IS NOT NULL),
    0
);
-- ä¸€ã€åŸºç¡€æ¼”示产品和工序
INSERT INTO product (parent_id, product_name, tenant_id)
SELECT NULL, '工业铝板(演示)', @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM product WHERE product_name = '工业铝板(演示)' AND tenant_id = @demo_tenant_id
);
INSERT INTO product (parent_id, product_name, tenant_id)
SELECT NULL, '精密轴承(演示)', @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM product WHERE product_name = '精密轴承(演示)' AND tenant_id = @demo_tenant_id
);
INSERT INTO product (parent_id, product_name, tenant_id)
SELECT NULL, '控制器总成(演示)', @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM product WHERE product_name = '控制器总成(演示)' AND tenant_id = @demo_tenant_id
);
INSERT INTO product_process (name, no, remark, create_time, update_time, tenant_id)
SELECT '精加工(演示)', 'DEMO-P-001', '质量管理演示工序', NOW(), NOW(), @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM product_process WHERE no = 'DEMO-P-001' AND tenant_id = @demo_tenant_id
);
SET @raw_product_id = (
    SELECT id FROM product WHERE product_name = '工业铝板(演示)' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @process_product_id = (
    SELECT id FROM product WHERE product_name = '精密轴承(演示)' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @final_product_id = (
    SELECT id FROM product WHERE product_name = '控制器总成(演示)' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @demo_process_id = (
    SELECT id FROM product_process WHERE no = 'DEMO-P-001' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
-- äºŒã€ä¸‰ç±»æ£€éªŒæ ‡å‡†
INSERT INTO quality_test_standard
    (standard_no, standard_name, remark, create_time, create_user, update_time, update_user,
     tenant_id, state, inspect_type, process_id)
SELECT 'QM-DEMO-RAW-001', '工业铝板来料检验标准', '演示数据', NOW(), 1, NOW(), 1,
       @demo_tenant_id, '0', 0, NULL
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard
    WHERE standard_no = 'QM-DEMO-RAW-001' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_test_standard
    (standard_no, standard_name, remark, create_time, create_user, update_time, update_user,
     tenant_id, state, inspect_type, process_id)
SELECT 'QM-DEMO-PROCESS-001', '精密轴承精加工检验标准', '演示数据', NOW(), 1, NOW(), 1,
       @demo_tenant_id, '0', 1, @demo_process_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard
    WHERE standard_no = 'QM-DEMO-PROCESS-001' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_test_standard
    (standard_no, standard_name, remark, create_time, create_user, update_time, update_user,
     tenant_id, state, inspect_type, process_id)
SELECT 'QM-DEMO-FINAL-001', '控制器总成出厂检验标准', '演示数据', NOW(), 1, NOW(), 1,
       @demo_tenant_id, '0', 2, NULL
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard
    WHERE standard_no = 'QM-DEMO-FINAL-001' AND tenant_id = @demo_tenant_id
);
SET @raw_standard_id = (
    SELECT id FROM quality_test_standard
    WHERE standard_no = 'QM-DEMO-RAW-001' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @process_standard_id = (
    SELECT id FROM quality_test_standard
    WHERE standard_no = 'QM-DEMO-PROCESS-001' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @final_standard_id = (
    SELECT id FROM quality_test_standard
    WHERE standard_no = 'QM-DEMO-FINAL-001' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
-- ä¸‰ã€æ ‡å‡†æŒ‡æ ‡å‚æ•°
INSERT INTO quality_test_standard_param
    (parameter_item, unit, standard_value, control_value, default_value,
     create_time, create_user, update_time, update_user, tenant_id, test_standard_id)
SELECT '厚度', 'mm', '2.00±0.10', '1.95~2.05', '2.00', NOW(), 1, NOW(), 1, @demo_tenant_id, @raw_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_param
    WHERE test_standard_id = @raw_standard_id AND parameter_item = '厚度'
);
INSERT INTO quality_test_standard_param
    (parameter_item, unit, standard_value, control_value, default_value,
     create_time, create_user, update_time, update_user, tenant_id, test_standard_id)
SELECT '表面质量', '/', '无划伤、无氧化斑', '无可见缺陷', '正常', NOW(), 1, NOW(), 1, @demo_tenant_id, @raw_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_param
    WHERE test_standard_id = @raw_standard_id AND parameter_item = '表面质量'
);
INSERT INTO quality_test_standard_param
    (parameter_item, unit, standard_value, control_value, default_value,
     create_time, create_user, update_time, update_user, tenant_id, test_standard_id)
SELECT '外径', 'mm', '20.00±0.05', '19.98~20.02', '20.00', NOW(), 1, NOW(), 1, @demo_tenant_id, @process_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_param
    WHERE test_standard_id = @process_standard_id AND parameter_item = '外径'
);
INSERT INTO quality_test_standard_param
    (parameter_item, unit, standard_value, control_value, default_value,
     create_time, create_user, update_time, update_user, tenant_id, test_standard_id)
SELECT '圆度', 'mm', '≤0.02', '≤0.015', '0.01', NOW(), 1, NOW(), 1, @demo_tenant_id, @process_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_param
    WHERE test_standard_id = @process_standard_id AND parameter_item = '圆度'
);
INSERT INTO quality_test_standard_param
    (parameter_item, unit, standard_value, control_value, default_value,
     create_time, create_user, update_time, update_user, tenant_id, test_standard_id)
SELECT '绝缘电阻', 'MΩ', '≥100', '≥120', '150', NOW(), 1, NOW(), 1, @demo_tenant_id, @final_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_param
    WHERE test_standard_id = @final_standard_id AND parameter_item = '绝缘电阻'
);
INSERT INTO quality_test_standard_param
    (parameter_item, unit, standard_value, control_value, default_value,
     create_time, create_user, update_time, update_user, tenant_id, test_standard_id)
SELECT '功能测试', '/', '全部功能通过', '连续运行30分钟无异常', '通过', NOW(), 1, NOW(), 1, @demo_tenant_id, @final_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_param
    WHERE test_standard_id = @final_standard_id AND parameter_item = '功能测试'
);
-- å››ã€äº§å“ä¸Žæ ‡å‡†ç»‘定
INSERT INTO quality_test_standard_binding
    (product_id, test_standard_id, create_time, create_user, update_time, update_user, tenant_id)
SELECT @raw_product_id, @raw_standard_id, NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_binding
    WHERE product_id = @raw_product_id AND test_standard_id = @raw_standard_id
);
INSERT INTO quality_test_standard_binding
    (product_id, test_standard_id, create_time, create_user, update_time, update_user, tenant_id)
SELECT @process_product_id, @process_standard_id, NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_binding
    WHERE product_id = @process_product_id AND test_standard_id = @process_standard_id
);
INSERT INTO quality_test_standard_binding
    (product_id, test_standard_id, create_time, create_user, update_time, update_user, tenant_id)
SELECT @final_product_id, @final_standard_id, NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_test_standard_binding
    WHERE product_id = @final_product_id AND test_standard_id = @final_standard_id
);
-- äº”、原材料、过程、出厂检验记录(每类包含合格与不合格)
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 0, DATE_SUB(CURDATE(), INTERVAL 5 DAY), '华东金属材料有限公司', NULL, NULL, '张质检',
       @raw_product_id, '工业铝板(演示)', 'AL-2.0', 'å¼ ', 120, '质量管理演示数据', '合格',
       NOW(), 1, NOW(), 1, @demo_tenant_id, 1, NULL, @raw_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE inspect_type = 0 AND product_id = @raw_product_id AND check_result = '合格'
      AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 0, DATE_SUB(CURDATE(), INTERVAL 4 DAY), '北方轻合金有限公司', NULL, NULL, '李质检',
       @raw_product_id, '工业铝板(演示)', 'AL-2.0', 'å¼ ', 80, '质量管理演示数据', '不合格',
       NOW(), 1, NOW(), 1, @demo_tenant_id, 1, '厚度偏薄,边缘存在划伤', @raw_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE inspect_type = 0 AND product_id = @raw_product_id AND check_result = '不合格'
      AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 1, DATE_SUB(CURDATE(), INTERVAL 3 DAY), NULL, '内部生产', '精加工(演示)', '王质检',
       @process_product_id, '精密轴承(演示)', 'BR-6204', '套', 300, '质量管理演示数据', '合格',
       NOW(), 1, NOW(), 1, @demo_tenant_id, 1, NULL, @process_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE inspect_type = 1 AND product_id = @process_product_id AND check_result = '合格'
      AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 1, DATE_SUB(CURDATE(), INTERVAL 2 DAY), NULL, '内部生产', '精加工(演示)', '赵质检',
       @process_product_id, '精密轴承(演示)', 'BR-6204', '套', 35, '质量管理演示数据', '不合格',
       NOW(), 1, NOW(), 1, @demo_tenant_id, 1, '外径超差,圆度不满足要求', @process_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE inspect_type = 1 AND product_id = @process_product_id AND check_result = '不合格'
      AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 2, DATE_SUB(CURDATE(), INTERVAL 1 DAY), NULL, '华南自动化科技有限公司', NULL, '陈质检',
       @final_product_id, '控制器总成(演示)', 'CTRL-A8', '台', 60, '质量管理演示数据', '合格',
       NOW(), 1, NOW(), 1, @demo_tenant_id, 1, NULL, @final_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE inspect_type = 2 AND product_id = @final_product_id AND check_result = '合格'
      AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 2, CURDATE(), NULL, '华东智能装备有限公司', NULL, '周质检',
       @final_product_id, '控制器总成(演示)', 'CTRL-A8', '台', 12, '质量管理演示数据', '不合格',
       NOW(), 1, NOW(), 1, @demo_tenant_id, 1, '绝缘电阻偏低,功能测试间歇性失效', @final_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE inspect_type = 2 AND product_id = @final_product_id AND check_result = '不合格'
      AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id
);
SET @raw_ok_id = (SELECT id FROM quality_inspect WHERE inspect_type = 0 AND product_id = @raw_product_id AND check_result = '合格' AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1);
SET @raw_ng_id = (SELECT id FROM quality_inspect WHERE inspect_type = 0 AND product_id = @raw_product_id AND check_result = '不合格' AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1);
SET @process_ok_id = (SELECT id FROM quality_inspect WHERE inspect_type = 1 AND product_id = @process_product_id AND check_result = '合格' AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1);
SET @process_ng_id = (SELECT id FROM quality_inspect WHERE inspect_type = 1 AND product_id = @process_product_id AND check_result = '不合格' AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1);
SET @final_ok_id = (SELECT id FROM quality_inspect WHERE inspect_type = 2 AND product_id = @final_product_id AND check_result = '合格' AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1);
SET @final_ng_id = (SELECT id FROM quality_inspect WHERE inspect_type = 2 AND product_id = @final_product_id AND check_result = '不合格' AND check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1);
-- å…­ã€æ£€éªŒæ˜Žç»†ã€‚使用 UNION ALL åŽç»Ÿä¸€æŽ’除已存在项。
INSERT INTO quality_inspect_param
    (parameter_item, unit, standard_value, control_value, test_value, inspect_id,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT d.parameter_item, d.unit, d.standard_value, d.control_value, d.test_value, d.inspect_id,
       NOW(), 1, NOW(), 1, @demo_tenant_id
FROM (
    SELECT '厚度' parameter_item, 'mm' unit, '2.00±0.10' standard_value, '1.95~2.05' control_value, '2.01' test_value, @raw_ok_id inspect_id
    UNION ALL SELECT '表面质量', '/', '无划伤、无氧化斑', '无可见缺陷', '正常', @raw_ok_id
    UNION ALL SELECT '厚度', 'mm', '2.00±0.10', '1.95~2.05', '1.86', @raw_ng_id
    UNION ALL SELECT '表面质量', '/', '无划伤、无氧化斑', '无可见缺陷', '边缘划伤', @raw_ng_id
    UNION ALL SELECT '外径', 'mm', '20.00±0.05', '19.98~20.02', '20.01', @process_ok_id
    UNION ALL SELECT '圆度', 'mm', '≤0.02', '≤0.015', '0.01', @process_ok_id
    UNION ALL SELECT '外径', 'mm', '20.00±0.05', '19.98~20.02', '20.18', @process_ng_id
    UNION ALL SELECT '圆度', 'mm', '≤0.02', '≤0.015', '0.05', @process_ng_id
    UNION ALL SELECT '绝缘电阻', 'MΩ', '≥100', '≥120', '156', @final_ok_id
    UNION ALL SELECT '功能测试', '/', '全部功能通过', '连续运行30分钟无异常', '通过', @final_ok_id
    UNION ALL SELECT '绝缘电阻', 'MΩ', '≥100', '≥120', '62', @final_ng_id
    UNION ALL SELECT '功能测试', '/', '全部功能通过', '连续运行30分钟无异常', '间歇性失效', @final_ng_id
) d
WHERE d.inspect_id IS NOT NULL
  AND NOT EXISTS (
      SELECT 1 FROM quality_inspect_param p
      WHERE p.inspect_id = d.inspect_id AND p.parameter_item = d.parameter_item
  );
-- ä¸ƒã€ä¸åˆæ ¼å“è®°å½•:包含待处理、返工完成和报废完成三种展示状态。
INSERT INTO quality_unqualified
    (inspect_type, inspect_state, check_time, check_name, product_id, product_name, model, unit,
     quantity, defective_phenomena, deal_type, deal_result, deal_name, deal_time,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT 0, 0, DATE_SUB(CURDATE(), INTERVAL 4 DAY), '李质检', @raw_product_id, '工业铝板(演示)',
       'AL-2.0', 'å¼ ', 80, '厚度偏薄,边缘存在划伤', NULL, NULL, NULL, NULL,
       NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_unqualified
    WHERE inspect_type = 0 AND product_id = @raw_product_id
      AND defective_phenomena = '厚度偏薄,边缘存在划伤' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_unqualified
    (inspect_type, inspect_state, check_time, check_name, product_id, product_name, model, unit,
     quantity, defective_phenomena, deal_type, deal_result, deal_name, deal_time,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT 1, 1, DATE_SUB(CURDATE(), INTERVAL 2 DAY), '赵质检', @process_product_id, '精密轴承(演示)',
       'BR-6204', '套', 35, '外径超差,圆度不满足要求', 'rework', '已返工并复检合格', '钱主管', DATE_SUB(CURDATE(), INTERVAL 1 DAY),
       NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_unqualified
    WHERE inspect_type = 1 AND product_id = @process_product_id
      AND defective_phenomena = '外径超差,圆度不满足要求' AND tenant_id = @demo_tenant_id
);
INSERT INTO quality_unqualified
    (inspect_type, inspect_state, check_time, check_name, product_id, product_name, model, unit,
     quantity, defective_phenomena, deal_type, deal_result, deal_name, deal_time,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT 2, 1, CURDATE(), '周质检', @final_product_id, '控制器总成(演示)',
       'CTRL-A8', '台', 12, '绝缘电阻偏低,功能测试间歇性失效', 'scrap', '确认无法返修,已隔离报废', '孙主管', CURDATE(),
       NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_unqualified
    WHERE inspect_type = 2 AND product_id = @final_product_id
      AND defective_phenomena = '绝缘电阻偏低,功能测试间歇性失效' AND tenant_id = @demo_tenant_id
);
-- æ‰§è¡Œç»“果摘要
SELECT @demo_tenant_id AS tenant_id,
       (SELECT COUNT(*) FROM quality_test_standard WHERE standard_no LIKE 'QM-DEMO-%' AND tenant_id = @demo_tenant_id) AS demo_standards,
       (SELECT COUNT(*) FROM quality_inspect WHERE check_company = '质量管理演示数据' AND tenant_id = @demo_tenant_id) AS demo_inspections,
       (SELECT COUNT(*) FROM quality_unqualified WHERE product_name LIKE '%(演示)' AND tenant_id = @demo_tenant_id) AS demo_unqualified;
doc/20261029_quality_unqualified_deal_type.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
-- ä¸åˆæ ¼å“å¤„理方式字段及字典数据(MySQL)
-- å¯é‡å¤æ‰§è¡Œï¼šä»…在字段/字典不存在时创建。
SET @deal_type_column_exists = (
    SELECT COUNT(*)
    FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'quality_unqualified'
      AND COLUMN_NAME = 'deal_type'
);
SET @deal_type_column_sql = IF(
    @deal_type_column_exists = 0,
    'ALTER TABLE quality_unqualified ADD COLUMN deal_type VARCHAR(32) NULL COMMENT ''处理方式:rework返工、scrap报废、concession让步接收'' AFTER defective_phenomena',
    'SELECT 1'
);
PREPARE deal_type_column_stmt FROM @deal_type_column_sql;
EXECUTE deal_type_column_stmt;
DEALLOCATE PREPARE deal_type_column_stmt;
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '质量处理方式', 'quality_deal_type', '0', 'admin', NOW(), '不合格品处理方式'
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_type WHERE dict_type = 'quality_deal_type'
);
INSERT INTO sys_dict_data
    (dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
     is_default, status, create_by, create_time, remark)
SELECT 1, '返工', 'rework', 'quality_deal_type', '', 'warning', 'N', '0', 'admin', NOW(), '返工处理'
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_data
    WHERE dict_type = 'quality_deal_type' AND dict_value = 'rework'
);
INSERT INTO sys_dict_data
    (dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
     is_default, status, create_by, create_time, remark)
SELECT 2, '报废', 'scrap', 'quality_deal_type', '', 'danger', 'N', '0', 'admin', NOW(), '报废处理'
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_data
    WHERE dict_type = 'quality_deal_type' AND dict_value = 'scrap'
);
INSERT INTO sys_dict_data
    (dict_sort, dict_label, dict_value, dict_type, css_class, list_class,
     is_default, status, create_by, create_time, remark)
SELECT 3, '让步接收', 'concession', 'quality_deal_type', '', 'info', 'N', '0', 'admin', NOW(), '让步接收'
WHERE NOT EXISTS (
    SELECT 1 FROM sys_dict_data
    WHERE dict_type = 'quality_deal_type' AND dict_value = 'concession'
);
doc/20261030_quality_demo_data_extra.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
-- è´¨é‡ç®¡ç†æ¼”示数据补充(MySQL 5.7+/8.0,可重复执行)
-- åœ¨ 20261029_quality_demo_data.sql åŸºç¡€ä¸Šè¡¥å……:
--   1) è®©æ­¥æŽ¥æ”¶å¤„理方式的不合格记录
--   2) å¾…处理状态的不合格记录
--   3) æœªæäº¤ï¼ˆè‰ç¨¿ï¼‰çš„æ£€éªŒå•,便于验证提交校验与列表状态
SET @demo_tenant_id = COALESCE(
    (SELECT tenant_id FROM sys_user WHERE user_name = 'admin' LIMIT 1),
    (SELECT MIN(tenant_id) FROM sys_user WHERE tenant_id IS NOT NULL),
    0
);
SET @raw_product_id = (
    SELECT id FROM product WHERE product_name = '工业铝板(演示)' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @process_product_id = (
    SELECT id FROM product WHERE product_name = '精密轴承(演示)' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @raw_standard_id = (
    SELECT id FROM quality_test_standard WHERE standard_no = 'QM-DEMO-RAW-001' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
SET @process_standard_id = (
    SELECT id FROM quality_test_standard WHERE standard_no = 'QM-DEMO-PROCESS-001' AND tenant_id = @demo_tenant_id ORDER BY id LIMIT 1
);
-- ä¸€ã€ä¸åˆæ ¼è®°å½•:让步接收(已处理)
INSERT INTO quality_unqualified
    (inspect_type, inspect_state, check_time, check_name, product_id, product_name, model, unit,
     quantity, defective_phenomena, deal_type, deal_result, deal_name, deal_time,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT 0, 1, DATE_SUB(CURDATE(), INTERVAL 3 DAY), '李质检', @raw_product_id, '工业铝板(演示)',
       'AL-2.0', 'å¼ ', 15, '表面轻微划伤,不影响装配使用', 'concession', '经技术评审让步接收,限定用途', '钱主管', DATE_SUB(CURDATE(), INTERVAL 2 DAY),
       NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_unqualified
    WHERE tenant_id = @demo_tenant_id AND defective_phenomena = '表面轻微划伤,不影响装配使用'
);
-- äºŒã€ä¸åˆæ ¼è®°å½•:过程检验待处理
INSERT INTO quality_unqualified
    (inspect_type, inspect_state, check_time, check_name, product_id, product_name, model, unit,
     quantity, defective_phenomena, deal_type, deal_result, deal_name, deal_time,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT 1, 0, CURDATE(), '赵质检', @process_product_id, '精密轴承(演示)',
       'BR-6204', '套', 20, '圆度超差,需评审处理', NULL, NULL, NULL, NULL,
       NOW(), 1, NOW(), 1, @demo_tenant_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_unqualified
    WHERE tenant_id = @demo_tenant_id AND defective_phenomena = '圆度超差,需评审处理'
);
-- ä¸‰ã€è‰ç¨¿æ£€éªŒå•(未提交,待判定)
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 0, CURDATE(), '华东金属材料有限公司', NULL, NULL, '张质检',
       @raw_product_id, '工业铝板(演示)', 'AL-2.0', 'å¼ ', 50, '质量管理演示数据-草稿', NULL,
       NOW(), 1, NOW(), 1, @demo_tenant_id, 0, NULL, @raw_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE tenant_id = @demo_tenant_id AND check_company = '质量管理演示数据-草稿' AND inspect_type = 0
);
INSERT INTO quality_inspect
    (inspect_type, check_time, supplier, customer, process, check_name, product_id, product_name,
     model, unit, quantity, check_company, check_result, create_time, create_user, update_time,
     update_user, tenant_id, inspect_state, defective_phenomena, test_standard_id)
SELECT 1, CURDATE(), NULL, '内部生产', '精加工(演示)', '王质检',
       @process_product_id, '精密轴承(演示)', 'BR-6204', '套', 100, '质量管理演示数据-草稿', NULL,
       NOW(), 1, NOW(), 1, @demo_tenant_id, 0, NULL, @process_standard_id
WHERE NOT EXISTS (
    SELECT 1 FROM quality_inspect
    WHERE tenant_id = @demo_tenant_id AND check_company = '质量管理演示数据-草稿' AND inspect_type = 1
);
SET @raw_draft_id = (
    SELECT id FROM quality_inspect
    WHERE tenant_id = @demo_tenant_id AND check_company = '质量管理演示数据-草稿' AND inspect_type = 0 ORDER BY id LIMIT 1
);
SET @process_draft_id = (
    SELECT id FROM quality_inspect
    WHERE tenant_id = @demo_tenant_id AND check_company = '质量管理演示数据-草稿' AND inspect_type = 1 ORDER BY id LIMIT 1
);
-- å››ã€è‰ç¨¿æ£€éªŒå•的检验项目(检验值留空,用于验证提交前校验)
INSERT INTO quality_inspect_param
    (parameter_item, unit, standard_value, control_value, test_value, inspect_id,
     create_time, create_user, update_time, update_user, tenant_id)
SELECT d.parameter_item, d.unit, d.standard_value, d.control_value, NULL, d.inspect_id,
       NOW(), 1, NOW(), 1, @demo_tenant_id
FROM (
    SELECT '厚度' parameter_item, 'mm' unit, '2.00±0.10' standard_value, '1.95~2.05' control_value, @raw_draft_id inspect_id
    UNION ALL SELECT '表面质量', '/', '无划伤、无氧化斑', '无可见缺陷', @raw_draft_id
    UNION ALL SELECT '外径', 'mm', '20.00±0.05', '19.98~20.02', @process_draft_id
    UNION ALL SELECT '圆度', 'mm', '≤0.02', '≤0.015', @process_draft_id
) d
WHERE d.inspect_id IS NOT NULL
  AND NOT EXISTS (
      SELECT 1 FROM quality_inspect_param p
      WHERE p.inspect_id = d.inspect_id AND p.parameter_item = d.parameter_item
  );
-- ç»“果摘要
SELECT @demo_tenant_id AS tenant_id,
       (SELECT COUNT(*) FROM quality_inspect WHERE tenant_id = @demo_tenant_id AND check_company LIKE '质量管理演示数据%') AS inspections,
       (SELECT COUNT(*) FROM quality_unqualified WHERE tenant_id = @demo_tenant_id) AS unqualified,
       (SELECT COUNT(*) FROM quality_unqualified WHERE tenant_id = @demo_tenant_id AND deal_type = 'concession') AS concession_count;
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -209,20 +209,15 @@
        qualityInspect.setProductMainId(productionProductMain.getId());
        qualityInspect.setProductModelId(productModel.getId());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandardBinding> qualityTestStandardBindings = qualityTestStandardBindingMapper.selectList(
                new LambdaQueryWrapper<QualityTestStandardBinding>()
                        .eq(QualityTestStandardBinding::getProductId, product.getId()));
        if (qualityTestStandardBindings.size()>0){
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandardBindings.get(0).getTestStandardId()))
                    .forEach(qualityTestStandardParam -> {
        qualityTestStandardParamMapper.selectListByProductId(
                        product.getId(), inspectType, inspectType == 1 ? productProcess.getName() : null)
                .forEach(qualityTestStandardParam -> {
                QualityInspectParam param = new QualityInspectParam();
                BeanUtils.copyProperties(qualityTestStandardParam, param);
                param.setId(null);
                param.setInspectId(qualityInspect.getId());
                qualityInspectParamMapper.insert(param);
            });
        }
        /*更新工单和生产订单*/
        ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(dto.getWorkOrderId());
        productWorkOrder.setCompleteQuantity(productWorkOrder.getCompleteQuantity().add(dto.getQuantity()));
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -229,20 +229,14 @@
        qualityInspect.setUnit(saleProduct.getUnit());
        qualityInspect.setQuantity(saleProduct.getQuantity());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandardBinding> qualityTestStandardBindings = qualityTestStandardBindingMapper.selectList(
                new LambdaQueryWrapper<QualityTestStandardBinding>()
                        .eq(QualityTestStandardBinding::getProductId, saleProduct.getProductId()));
        if (qualityTestStandardBindings.size()>0){
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandardBindings.get(0).getTestStandardId()))
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
                        param.setId(null);
                        param.setInspectId(qualityInspect.getId());
                        qualityInspectParamMapper.insert(param);
                    });
        }
        qualityTestStandardParamMapper.selectListByProductId(saleProduct.getProductId(), 0, null)
                .forEach(qualityTestStandardParam -> {
                    QualityInspectParam param = new QualityInspectParam();
                    com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
                    param.setId(null);
                    param.setInspectId(qualityInspect.getId());
                    qualityInspectParamMapper.insert(param);
                });
    }
    private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, Integer type) {
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java
@@ -15,6 +15,7 @@
import com.ruoyi.quality.service.IQualityInspectParamService;
import com.ruoyi.quality.service.IQualityInspectService;
import com.ruoyi.quality.service.IQualityUnqualifiedService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -49,6 +50,10 @@
     */
    @PostMapping("/add")
    public AjaxResult add(@RequestBody QualityInspectDto qualityInspectDto) {
        String validationMessage = validateInspect(qualityInspectDto, false);
        if (validationMessage != null) {
            return AjaxResult.error(validationMessage);
        }
        return AjaxResult.success(qualityInspectService.add(qualityInspectDto));
    }
@@ -91,6 +96,17 @@
     */
    @PostMapping("/update")
    public AjaxResult update(@RequestBody QualityInspectDto qualityInspectDto) {
        String validationMessage = validateInspect(qualityInspectDto, true);
        if (validationMessage != null) {
            return AjaxResult.error(validationMessage);
        }
        QualityInspect stored = qualityInspectService.getById(qualityInspectDto.getId());
        if (stored == null) {
            return AjaxResult.error("检验记录不存在");
        }
        if (Integer.valueOf(1).equals(stored.getInspectState())) {
            return AjaxResult.error("已提交的检验记录不能修改");
        }
        return AjaxResult.success(qualityInspectService.updateQualityInspect(qualityInspectDto));
    }
@@ -134,4 +150,43 @@
    public void down(HttpServletResponse response,@RequestBody QualityInspect qualityInspect) {
        qualityInspectService.down(response, qualityInspect);
    }
    private String validateInspect(QualityInspectDto inspect, boolean requireId) {
        if (inspect == null) {
            return "检验数据不能为空";
        }
        if (requireId && inspect.getId() == null) {
            return "检验记录ID不能为空";
        }
        if (inspect.getInspectType() == null || inspect.getInspectType() < 0 || inspect.getInspectType() > 2) {
            return "检验类别不正确";
        }
        if (inspect.getProductId() == null) {
            return "请选择产品";
        }
        if (inspect.getCheckTime() == null) {
            return "请选择检测日期";
        }
        if (inspect.getQuantity() == null || inspect.getQuantity().signum() <= 0) {
            return "检验数量必须大于0";
        }
        if (!"合格".equals(inspect.getCheckResult()) && !"不合格".equals(inspect.getCheckResult())) {
            return "请选择有效的检测结果";
        }
        if (inspect.getInspectType() == 1 && StringUtils.isBlank(inspect.getProcess())) {
            return "过程检验必须选择工序";
        }
        if (CollectionUtils.isEmpty(inspect.getQualityInspectParams())) {
            return "当前产品未配置检验指标,请先维护指标";
        }
        for (QualityInspectParam parameter : inspect.getQualityInspectParams()) {
            if (StringUtils.isBlank(parameter.getParameterItem())) {
                return "检验项目不能为空";
            }
            if (StringUtils.isBlank(parameter.getTestValue())) {
                return "请填写“" + parameter.getParameterItem() + "”的检验值";
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/quality/controller/QualityMetricController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,294 @@
package com.ruoyi.quality.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.quality.dto.QualityMetricBindingDto;
import com.ruoyi.quality.dto.QualityMetricDto;
import com.ruoyi.quality.mapper.QualityTestStandardBindingMapper;
import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
import com.ruoyi.quality.pojo.QualityTestStandard;
import com.ruoyi.quality.pojo.QualityTestStandardBinding;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.quality.service.IQualityTestStandardService;
import com.ruoyi.quality.service.QualityTestStandardBindingService;
import com.ruoyi.quality.service.QualityTestStandardParamService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
/**
 * æŒ‰äº§å“ç»´æŠ¤æ£€æµ‹æŒ‡æ ‡ã€‚
 *
 * å‰ç«¯çš„“指标维护”页面以产品为入口,本接口将产品、检测标准与标准参数的
 * ä¸‰å±‚数据结构封装为页面所需的单层指标操作,并按检验类别/工序隔离指标池。
 */
@RestController
@RequestMapping("/quality/qualityMetric")
@RequiredArgsConstructor
public class QualityMetricController {
    private static final int INSPECT_TYPE_PROCESS = 1;
    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
    private final QualityTestStandardParamService qualityTestStandardParamService;
    private final QualityTestStandardBindingService qualityTestStandardBindingService;
    private final IQualityTestStandardService qualityTestStandardService;
    private final QualityTestStandardBindingMapper qualityTestStandardBindingMapper;
    @GetMapping("/listPage")
    public AjaxResult listPage(Page<QualityTestStandardParam> page,
                               @RequestParam("productId") Long productId,
                               @RequestParam(value = "inspectType", required = false) Integer inspectType,
                               @RequestParam(value = "processId", required = false) Long processId) {
        IPage<QualityTestStandardParam> result = qualityTestStandardParamMapper
                .selectPageByProductId(page, productId, inspectType, processId);
        return AjaxResult.success(result);
    }
    @GetMapping("/product/{productId}")
    public AjaxResult listByProduct(@PathVariable("productId") Long productId,
                                    @RequestParam(value = "inspectType", required = false) Integer inspectType,
                                    @RequestParam(value = "process", required = false) String process) {
        return AjaxResult.success(qualityTestStandardParamMapper
                .selectListByProductId(productId, inspectType, process));
    }
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(@RequestBody QualityMetricDto metric) {
        normalizeInspectType(metric);
        String validationMessage = validateMetric(metric, true);
        if (validationMessage != null) {
            return AjaxResult.error(validationMessage);
        }
        Long testStandardId = getOrCreateTestStandard(metric.getProductId(),
                metric.getInspectType(), metric.getProcessId());
        QualityTestStandardParam parameter = copyEditableFields(metric, new QualityTestStandardParam());
        parameter.setTestStandardId(testStandardId);
        if (!qualityTestStandardParamService.save(parameter)) {
            throw new ServiceException("保存检测指标失败");
        }
        return AjaxResult.success(true);
    }
    @PostMapping("/update")
    public AjaxResult update(@RequestBody QualityMetricDto metric) {
        String validationMessage = validateMetric(metric, false);
        if (validationMessage != null) {
            return AjaxResult.error(validationMessage);
        }
        if (metric.getId() == null) {
            return AjaxResult.error("指标ID不能为空");
        }
        QualityTestStandardParam parameter = qualityTestStandardParamService.getById(metric.getId());
        if (parameter == null) {
            return AjaxResult.error("检测指标不存在");
        }
        copyEditableFields(metric, parameter);
        if (!qualityTestStandardParamService.updateById(parameter)) {
            throw new ServiceException("修改检测指标失败");
        }
        return AjaxResult.success(true);
    }
    @DeleteMapping("/del")
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return AjaxResult.error("请选择至少一条数据");
        }
        if (!qualityTestStandardParamService.removeBatchByIds(ids)) {
            throw new ServiceException("删除检测指标失败");
        }
        return AjaxResult.success(true);
    }
    @PostMapping("/export")
    public void export(HttpServletResponse response,
                       @RequestParam("productId") Long productId,
                       @RequestParam(value = "inspectType", required = false) Integer inspectType,
                       @RequestParam(value = "process", required = false) String process) {
        List<QualityTestStandardParam> list = qualityTestStandardParamMapper
                .selectListByProductId(productId, inspectType, process);
        ExcelUtil<QualityTestStandardParam> util = new ExcelUtil<>(QualityTestStandardParam.class);
        util.exportExcel(response, list, "检测指标");
    }
    /**
     * æŸ¥è¯¢å…¨éƒ¨æ£€æµ‹æ ‡å‡†ï¼Œä¾›â€œæŒ‡æ ‡ç»‘定”页面选择。
     */
    @GetMapping("/standardList")
    public AjaxResult standardList() {
        return AjaxResult.success(qualityTestStandardService.list(
                Wrappers.<QualityTestStandard>lambdaQuery()
                        .orderByDesc(QualityTestStandard::getId)));
    }
    /**
     * æŸ¥è¯¢äº§å“å·²ç»‘定的检测标准。
     */
    @GetMapping("/bindingList")
    public AjaxResult bindingList(@RequestParam("productId") Long productId) {
        return AjaxResult.success(qualityTestStandardBindingMapper.selectBindingByProductId(productId));
    }
    /**
     * ç»‘定产品与检测标准。
     */
    @PostMapping("/binding")
    public AjaxResult bind(@RequestBody QualityMetricBindingDto binding) {
        if (binding.getProductId() == null) {
            return AjaxResult.error("请先选择产品");
        }
        if (binding.getTestStandardId() == null) {
            return AjaxResult.error("请选择检测标准");
        }
        long exists = qualityTestStandardBindingService.count(
                Wrappers.<QualityTestStandardBinding>lambdaQuery()
                        .eq(QualityTestStandardBinding::getProductId, binding.getProductId())
                        .eq(QualityTestStandardBinding::getTestStandardId, binding.getTestStandardId()));
        if (exists > 0) {
            return AjaxResult.error("该标准已绑定到此产品");
        }
        QualityTestStandardBinding entity = new QualityTestStandardBinding();
        entity.setProductId(binding.getProductId());
        entity.setTestStandardId(binding.getTestStandardId());
        if (!qualityTestStandardBindingService.save(entity)) {
            throw new ServiceException("绑定检测标准失败");
        }
        return AjaxResult.success(true);
    }
    /**
     * è§£é™¤äº§å“ä¸Žæ£€æµ‹æ ‡å‡†çš„绑定。
     */
    @DeleteMapping("/binding")
    public AjaxResult unbind(@RequestBody List<Integer> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return AjaxResult.error("请选择至少一条数据");
        }
        if (!qualityTestStandardBindingService.removeBatchByIds(ids)) {
            throw new ServiceException("解除绑定失败");
        }
        return AjaxResult.success(true);
    }
    private void normalizeInspectType(QualityMetricDto metric) {
        if (metric != null && metric.getInspectType() == null) {
            metric.setInspectType(0);
        }
    }
    /**
     * æŸ¥æ‰¾æˆ–创建与“产品 + æ£€éªŒç±»åˆ« + å·¥åºâ€åŒ¹é…çš„æ£€æµ‹æ ‡å‡†ï¼Œå¹¶å»ºç«‹äº§å“ç»‘定。
     */
    private Long getOrCreateTestStandard(Long productId, Integer inspectType, Long processId) {
        int type = inspectType == null ? 0 : inspectType;
        List<QualityTestStandardBinding> bindings = qualityTestStandardBindingService.list(
                Wrappers.<QualityTestStandardBinding>lambdaQuery()
                        .eq(QualityTestStandardBinding::getProductId, productId)
                        .orderByAsc(QualityTestStandardBinding::getId));
        if (!bindings.isEmpty()) {
            List<Integer> standardIds = bindings.stream()
                    .map(QualityTestStandardBinding::getTestStandardId)
                    .collect(Collectors.toList());
            com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityTestStandard> wrapper =
                    Wrappers.<QualityTestStandard>lambdaQuery()
                            .in(QualityTestStandard::getId, standardIds)
                            .eq(QualityTestStandard::getInspectType, type);
            // ä»…过程检验按工序区分标准,原材料/出厂检验忽略工序
            if (type == INSPECT_TYPE_PROCESS && processId != null) {
                wrapper.eq(QualityTestStandard::getProcessId, processId.intValue());
            }
            QualityTestStandard matched = qualityTestStandardService.getOne(
                    wrapper.orderByAsc(QualityTestStandard::getId).last("limit 1"));
            if (matched != null) {
                return matched.getId();
            }
        }
        QualityTestStandard standard = new QualityTestStandard();
        String suffix = inspectTypeSuffix(type) + (processId == null ? "" : "-" + processId);
        standard.setStandardNo("AUTO-" + productId + "-" + suffix);
        standard.setStandardName("产品检测指标-" + productId + "-" + suffix);
        standard.setState("1");
        standard.setInspectType(type);
        if (processId != null) {
            standard.setProcessId(processId.intValue());
        }
        if (!qualityTestStandardService.save(standard) || standard.getId() == null) {
            throw new ServiceException("创建产品检测标准失败");
        }
        QualityTestStandardBinding binding = new QualityTestStandardBinding();
        binding.setProductId(productId);
        binding.setTestStandardId(Math.toIntExact(standard.getId()));
        if (!qualityTestStandardBindingService.save(binding)) {
            throw new ServiceException("建立产品与检测标准关联失败");
        }
        return standard.getId();
    }
    private String inspectTypeSuffix(int inspectType) {
        switch (inspectType) {
            case 1:
                return "PROCESS";
            case 2:
                return "FINAL";
            default:
                return "RAW";
        }
    }
    private String validateMetric(QualityMetricDto metric, boolean requireProduct) {
        if (metric == null) {
            return "指标数据不能为空";
        }
        if (requireProduct && metric.getProductId() == null) {
            return "请先选择产品";
        }
        if (metric.getInspectType() != null
                && metric.getInspectType() == INSPECT_TYPE_PROCESS
                && metric.getProcessId() == null) {
            return "过程检验请先选择工序";
        }
        if (StringUtils.isBlank(metric.getParameterItem())) {
            return "指标名称不能为空";
        }
        if (StringUtils.isBlank(metric.getUnit())) {
            return "单位不能为空";
        }
        if (StringUtils.isBlank(metric.getStandardValue())) {
            return "标准值不能为空";
        }
        return null;
    }
    private QualityTestStandardParam copyEditableFields(QualityMetricDto source,
                                                         QualityTestStandardParam target) {
        target.setParameterItem(source.getParameterItem());
        target.setUnit(source.getUnit());
        target.setStandardValue(source.getStandardValue());
        target.setControlValue(source.getControlValue());
        target.setDefaultValue(source.getDefaultValue());
        return target;
    }
}
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java
@@ -2,9 +2,11 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.quality.service.IQualityUnqualifiedService;
import org.springframework.util.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -29,7 +31,17 @@
     */
    @PostMapping("/add")
    public AjaxResult add(@RequestBody QualityUnqualified qualityUnqualified) {
        return AjaxResult.success(qualityUnqualifiedService.save(qualityUnqualified));
        if (qualityUnqualified != null && qualityUnqualified.getInspectState() == null) {
            qualityUnqualified.setInspectState(0);
        }
        String validationMessage = validateUnqualified(qualityUnqualified, false);
        if (validationMessage != null) {
            return AjaxResult.error(validationMessage);
        }
        if (!qualityUnqualifiedService.save(qualityUnqualified)) {
            throw new ServiceException("新增不合格记录失败");
        }
        return AjaxResult.success(true);
    }
    /**
@@ -62,7 +74,17 @@
     */
    @PostMapping("/update")
    public AjaxResult update(@RequestBody QualityUnqualified qualityUnqualified) {
        return AjaxResult.success(qualityUnqualifiedService.updateById(qualityUnqualified));
        String validationMessage = validateUnqualified(qualityUnqualified, true);
        if (validationMessage != null) {
            return AjaxResult.error(validationMessage);
        }
        if (qualityUnqualifiedService.getById(qualityUnqualified.getId()) == null) {
            return AjaxResult.error("不合格记录不存在");
        }
        if (!qualityUnqualifiedService.updateById(qualityUnqualified)) {
            throw new ServiceException("修改不合格记录失败");
        }
        return AjaxResult.success(true);
    }
    /**
@@ -86,5 +108,45 @@
        qualityUnqualifiedService.qualityUnqualifiedExport(response, qualityUnqualified);
    }
    private String validateUnqualified(QualityUnqualified record, boolean requireId) {
        if (record == null) {
            return "不合格数据不能为空";
        }
        if (requireId && record.getId() == null) {
            return "不合格记录ID不能为空";
        }
        if (record.getInspectType() == null || record.getInspectType() < 0 || record.getInspectType() > 2) {
            return "检验类别不正确";
        }
        if (record.getInspectState() == null
                || (record.getInspectState() != 0 && record.getInspectState() != 1)) {
            return "处理状态不正确";
        }
        if (record.getProductId() == null) {
            return "请选择产品";
        }
        if (record.getQuantity() == null || record.getQuantity().signum() <= 0) {
            return "不合格数量必须大于0";
        }
        if (StringUtils.isBlank(record.getDefectivePhenomena())) {
            return "请填写不合格原因或现象";
        }
        if (Integer.valueOf(1).equals(record.getInspectState())) {
            if (StringUtils.isBlank(record.getDealType())) {
                return "请选择处理方式";
            }
            if (StringUtils.isBlank(record.getDealResult())) {
                return "请填写处理结果";
            }
            if (StringUtils.isBlank(record.getDealName())) {
                return "请填写处理人";
            }
            if (record.getDealTime() == null) {
                return "请选择处理日期";
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/quality/dto/QualityMetricBindingDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.quality.dto;
import lombok.Data;
import java.io.Serializable;
/**
 * æŒ‡æ ‡ç»‘定列表展示对象:产品与检测标准的关联信息。
 */
@Data
public class QualityMetricBindingDto implements Serializable {
    private static final long serialVersionUID = 1L;
    /** ç»‘定关系ID */
    private Integer id;
    /** äº§å“ID */
    private Long productId;
    /** æ£€æµ‹æ ‡å‡†ID */
    private Integer testStandardId;
    /** æ ‡å‡†ç¼–号 */
    private String standardNo;
    /** æ ‡å‡†åç§° */
    private String standardName;
    /** æ£€éªŒç±»åˆ«(0:原材料检验;1:过程检验;2:出厂检验) */
    private Integer inspectType;
    /** å·¥åºID */
    private Integer processId;
    /** å·¥åºåç§° */
    private String processName;
    /** çŠ¶æ€ */
    private String state;
}
src/main/java/com/ruoyi/quality/dto/QualityMetricDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.quality.dto;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import lombok.Data;
import java.io.Serializable;
/**
 * æŒ‰äº§å“ç»´æŠ¤æ£€æµ‹æŒ‡æ ‡çš„前端交互对象。
 */
@Data
public class QualityMetricDto extends QualityTestStandardParam implements Serializable {
    private static final long serialVersionUID = 1L;
    /** æŒ‡æ ‡æ‰€å±žäº§å“ID */
    private Long productId;
    /** æ£€éªŒç±»åˆ«(0:原材料检验;1:过程检验;2:出厂检验) */
    private Integer inspectType;
    /** å·¥åºID(过程检验时使用) */
    private Long processId;
}
src/main/java/com/ruoyi/quality/mapper/QualityTestStandardBindingMapper.java
@@ -1,5 +1,6 @@
package com.ruoyi.quality.mapper;
import com.ruoyi.quality.dto.QualityMetricBindingDto;
import com.ruoyi.quality.dto.QualityTestStandardBindingDto;
import com.ruoyi.quality.pojo.QualityTestStandardBinding;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -20,4 +21,6 @@
public interface QualityTestStandardBindingMapper extends BaseMapper<QualityTestStandardBinding> {
    List<QualityTestStandardBindingDto> listBinding(@Param("testStandardId") Long testStandardId);
    List<QualityMetricBindingDto> selectBindingByProductId(@Param("productId") Long productId);
}
src/main/java/com/ruoyi/quality/mapper/QualityTestStandardParamMapper.java
@@ -1,5 +1,7 @@
package com.ruoyi.quality.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@@ -19,5 +21,12 @@
@Mapper
public interface QualityTestStandardParamMapper extends BaseMapper<QualityTestStandardParam> {
    List<QualityTestStandardParam> selectListByProductId(@Param("productId") Long productId);
    List<QualityTestStandardParam> selectListByProductId(@Param("productId") Long productId,
                                                         @Param("inspectType") Integer inspectType,
                                                         @Param("process") String process);
    IPage<QualityTestStandardParam> selectPageByProductId(Page<QualityTestStandardParam> page,
                                                          @Param("productId") Long productId,
                                                          @Param("inspectType") Integer inspectType,
                                                          @Param("processId") Long processId);
}
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
@@ -93,6 +93,12 @@
    private String defectivePhenomena;
    /**
     * å¤„理方式(字典 quality_deal_type:返工/报废/让步接收等)
     */
    @Excel(name = "处理方式", readConverterExp = "rework=返工,scrap=报废,concession=让步接收")
    private String dealType;
    /**
     * å¤„理结果
     */
    @Excel(name = "处理结果")
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -9,6 +9,7 @@
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.HackLoopTableRenderPolicy;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -29,6 +30,7 @@
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -66,17 +68,25 @@
        QualityInspect qualityInspect = new QualityInspect();
        BeanUtils.copyProperties(qualityInspectDto,qualityInspect);
        qualityInspect.setInspectState(0);//默认未提交
        qualityInspectMapper.insert(qualityInspect);
        int inserted = qualityInspectMapper.insert(qualityInspect);
        if (inserted <= 0 || qualityInspect.getId() == null) {
            throw new ServiceException("新增检验记录失败");
        }
        for (QualityInspectParam qualityInspectParam : qualityInspectDto.getQualityInspectParams()) {
            qualityInspectParam.setInspectId(qualityInspect.getId());
        }
        qualityInspectParamService.saveBatch(qualityInspectDto.getQualityInspectParams());
        return 0;
        if (!qualityInspectParamService.saveBatch(qualityInspectDto.getQualityInspectParams())) {
            throw new ServiceException("保存检验项目失败");
        }
        return inserted;
    }
    @Override
    public QualityInspectDto getDetailById(Integer id) {
        QualityInspect qualityInspect = qualityInspectMapper.selectById(id);
        if (qualityInspect == null) {
            throw new ServiceException("检验记录不存在");
        }
        List<QualityInspectParam> qualityInspectParams = qualityInspectParamService.list(Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, id));
        QualityInspectDto qualityInspectDto = new QualityInspectDto();
        BeanUtils.copyProperties(qualityInspect,qualityInspectDto);
@@ -87,13 +97,36 @@
    //提交
    @Override
    public int submit(QualityInspect inspect) {
        if (inspect == null || inspect.getId() == null) {
            throw new ServiceException("检验记录ID不能为空");
        }
        QualityInspect qualityInspect = qualityInspectMapper.selectById(inspect.getId());
        if (qualityInspect == null) {
            throw new ServiceException("检验记录不存在");
        }
        if (Integer.valueOf(1).equals(qualityInspect.getInspectState())) {
            throw new ServiceException("检验记录已提交,请勿重复操作");
        }
        if (!"合格".equals(qualityInspect.getCheckResult())
                && !"不合格".equals(qualityInspect.getCheckResult())) {
            throw new ServiceException("请先完成检验结果判定");
        }
        List<QualityInspectParam> inspectParams = qualityInspectParamService.list(
                Wrappers.<QualityInspectParam>lambdaQuery()
                        .eq(QualityInspectParam::getInspectId, inspect.getId()));
        if (inspectParams.isEmpty()) {
            throw new ServiceException("检验项目为空,不能提交");
        }
        for (QualityInspectParam parameter : inspectParams) {
            if (StringUtils.isBlank(parameter.getTestValue())) {
                throw new ServiceException("检验项目“" + parameter.getParameterItem() + "”尚未填写检验值");
            }
        }
        /*判断不合格*/
        if (ObjectUtils.isNotNull(qualityInspect.getCheckResult()) && qualityInspect.getCheckResult().equals("不合格")){
            QualityUnqualified qualityUnqualified = new QualityUnqualified();
            BeanUtils.copyProperties(qualityInspect,qualityUnqualified);
            qualityUnqualified.setInspectState(0);//待处理
            List<QualityInspectParam> inspectParams = qualityInspectParamService.list(Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, inspect.getId()));
            String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(","));
            qualityUnqualified.setDefectivePhenomena(text+"这些指标中存在不合格");//不合格现象
            qualityUnqualifiedMapper.insert(qualityUnqualified);
@@ -101,79 +134,83 @@
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (qualityInspect.getInspectType() == 0) {
            if ("合格".equals(qualityInspect.getCheckResult())) {
                ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
                procurementRecordOutAdd.setType(1);
                procurementRecordOutAdd.setTypeName("采购原材料检验合格入库");
                procurementRecordOutAdd.setNickName(loginUser.getNickName());
                procurementRecordOutAdd.setPurchaseLedgerId(Math.toIntExact(qualityInspect.getPurchaseLedgerId()));
                if (qualityInspect.getPurchaseLedgerId() == null) {
                    throw new BaseException("请选择采购单");
                }
            // åˆæ ¼ä¸”关联了采购单/产品信息时才自动入库,信息不全时跳过入库但正常提交
            if ("合格".equals(qualityInspect.getCheckResult())
                    && qualityInspect.getPurchaseLedgerId() != null) {
                SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(qualityInspect.getProductId());
                if (salesLedgerProduct != null) {
                    ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
                    procurementRecordOutAdd.setType(1);
                    procurementRecordOutAdd.setTypeName("采购原材料检验合格入库");
                    procurementRecordOutAdd.setNickName(loginUser.getNickName());
                    procurementRecordOutAdd.setPurchaseLedgerId(Math.toIntExact(qualityInspect.getPurchaseLedgerId()));
                ArrayList<Details> detailss = new ArrayList<>();
                Details details = new Details();
                details.setId(Math.toIntExact(salesLedgerProduct.getId()));
                details.setInboundQuantity(qualityInspect.getQuantity());
                details.setWarnNum(salesLedgerProduct.getWarnNum());
                details.setUnitPrice(salesLedgerProduct.getTaxInclusiveUnitPrice());
                details.setTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice());
                details.setProductModelId(qualityInspect.getProductModelId());
                detailss.add( details);
                procurementRecordOutAdd.setDetails(detailss);
                procurementRecordOutAdd.setQualityInspectId(qualityInspect.getId());
                procurementRecordService.add(procurementRecordOutAdd);
                    ArrayList<Details> detailss = new ArrayList<>();
                    Details details = new Details();
                    details.setId(Math.toIntExact(salesLedgerProduct.getId()));
                    details.setInboundQuantity(qualityInspect.getQuantity());
                    details.setWarnNum(salesLedgerProduct.getWarnNum());
                    details.setUnitPrice(salesLedgerProduct.getTaxInclusiveUnitPrice());
                    details.setTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice());
                    details.setProductModelId(qualityInspect.getProductModelId());
                    detailss.add( details);
                    procurementRecordOutAdd.setDetails(detailss);
                    procurementRecordOutAdd.setQualityInspectId(qualityInspect.getId());
                    procurementRecordService.add(procurementRecordOutAdd);
                }
            }
        }else if (qualityInspect.getInspectType() == 1) {
            //查询UnitPrice/TotalPrice
            ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
            procurementRecordOutAdd.setType(2);
            procurementRecordOutAdd.setTypeName("生产过程检验合格入库");
            procurementRecordOutAdd.setNickName(loginUser.getNickName());
            List<Details> details = new ArrayList<>();
            Details details1 = new Details();
            details1.setInboundQuantity(qualityInspect.getQuantity());
            details1.setProductModelId(qualityInspect.getProductModelId());
            procurementRecordOutAdd.setDetails(details);
            // è¿‡ç¨‹æ£€éªŒä»…合格时入库,不合格品由上面的不合格管理流程处理
            if ("合格".equals(qualityInspect.getCheckResult())) {
                ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
                procurementRecordOutAdd.setType(2);
                procurementRecordOutAdd.setTypeName("生产过程检验合格入库");
                procurementRecordOutAdd.setNickName(loginUser.getNickName());
                List<Details> details = new ArrayList<>();
                Details details1 = new Details();
                details1.setInboundQuantity(qualityInspect.getQuantity());
                details1.setProductModelId(qualityInspect.getProductModelId());
                procurementRecordOutAdd.setDetails(details);
            ProcurementRecordStorage.ProcurementRecordStorageBuilder procurementRecordBuilder = ProcurementRecordStorage.builder()
                    .salesLedgerProductId(0)
                    .inboundBatches( "生产半成品入库")
                    .inboundNum(details1.getInboundQuantity())
                    .type(2)
                    .warnNum(new BigDecimal(0))
                    .unitPrice(new BigDecimal(0))
                    .totalPrice(new BigDecimal(0))
                    .createTime(LocalDateTime.now())
                    .createUser(loginUser.getUserId())
                    .updateTime(LocalDateTime.now())
                    .updateUser(loginUser.getUserId())
                    .createBy(procurementRecordOutAdd.getNickName())
                    .productModelId(details1.getProductModelId())
                    .qualityInspectId(qualityInspect.getId());
            procurementRecordService.save(procurementRecordBuilder.build());
                ProcurementRecordStorage.ProcurementRecordStorageBuilder procurementRecordBuilder = ProcurementRecordStorage.builder()
                        .salesLedgerProductId(0)
                        .inboundBatches( "生产半成品入库")
                        .inboundNum(details1.getInboundQuantity())
                        .type(2)
                        .warnNum(new BigDecimal(0))
                        .unitPrice(new BigDecimal(0))
                        .totalPrice(new BigDecimal(0))
                        .createTime(LocalDateTime.now())
                        .createUser(loginUser.getUserId())
                        .updateTime(LocalDateTime.now())
                        .updateUser(loginUser.getUserId())
                        .createBy(procurementRecordOutAdd.getNickName())
                        .productModelId(details1.getProductModelId())
                        .qualityInspectId(qualityInspect.getId());
                procurementRecordService.save(procurementRecordBuilder.build());
            }
        }else if (qualityInspect.getInspectType() == 2) {
            //查询UnitPrice/TotalPrice
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectSalesLedgerProductByMainId(qualityInspect.getProductMainId());
            ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
            procurementRecordOutAdd.setType(2);
            procurementRecordOutAdd.setTypeName("生产出厂检验合格入库");
            procurementRecordOutAdd.setNickName(loginUser.getNickName());
            List<Details> details = new ArrayList<>();
            Details details1 = new Details();
            details1.setInboundQuantity(qualityInspect.getQuantity());
            details1.setId(Math.toIntExact(salesLedgerProduct.getId()));
            details1.setUnitPrice(salesLedgerProduct.getTaxInclusiveUnitPrice());
            details1.setTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice());
            details1.setProductModelId(salesLedgerProduct.getProductModelId());
            details.add(details1);
            procurementRecordOutAdd.setDetails(details);
            procurementRecordOutAdd.setQualityInspectId(qualityInspect.getId());
            procurementRecordService.add(procurementRecordOutAdd);
            // å‡ºåŽ‚æ£€éªŒä»…åˆæ ¼æ—¶å…¥åº“ï¼Œç¼ºå°‘äº§å“ä¿¡æ¯æ—¶è·³è¿‡å…¥åº“ä½†æ­£å¸¸æäº¤
            if ("合格".equals(qualityInspect.getCheckResult())) {
                SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectSalesLedgerProductByMainId(qualityInspect.getProductMainId());
                if (salesLedgerProduct != null) {
                    ProcurementAddDto procurementRecordOutAdd = new ProcurementAddDto();
                    procurementRecordOutAdd.setType(2);
                    procurementRecordOutAdd.setTypeName("生产出厂检验合格入库");
                    procurementRecordOutAdd.setNickName(loginUser.getNickName());
                    List<Details> details = new ArrayList<>();
                    Details details1 = new Details();
                    details1.setInboundQuantity(qualityInspect.getQuantity());
                    details1.setId(Math.toIntExact(salesLedgerProduct.getId()));
                    details1.setUnitPrice(salesLedgerProduct.getTaxInclusiveUnitPrice());
                    details1.setTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice());
                    details1.setProductModelId(salesLedgerProduct.getProductModelId());
                    details.add(details1);
                    procurementRecordOutAdd.setDetails(details);
                    procurementRecordOutAdd.setQualityInspectId(qualityInspect.getId());
                    procurementRecordService.add(procurementRecordOutAdd);
                }
            }
        }
        qualityInspect.setInspectState(1);//已提交
        return qualityInspectMapper.updateById(qualityInspect);
src/main/resources/application-tide.yml
@@ -216,7 +216,3 @@
  tablePrefix: sys_
  # æ˜¯å¦å…è®¸ç”Ÿæˆæ–‡ä»¶è¦†ç›–到本地(自定义路径),默认不允许
  allowOverwrite: false
file:
  temp-dir: /center-lims/mis/file/temp/uploads
  upload-dir: /center-lims/mis/file/prod/uploads
src/main/resources/mapper/quality/QualityTestStandardBindingMapper.xml
@@ -13,6 +13,23 @@
        <result column="update_user" property="updateUser"/>
        <result column="tenant_id" property="tenantId"/>
    </resultMap>
    <select id="selectBindingByProductId" resultType="com.ruoyi.quality.dto.QualityMetricBindingDto">
        select t1.id,
               t1.product_id,
               t1.test_standard_id,
               t2.standard_no,
               t2.standard_name,
               t2.inspect_type,
               t2.process_id,
               pp.name as process_name,
               t2.state
        from quality_test_standard_binding t1
                 left join quality_test_standard t2 on t1.test_standard_id = t2.id
                 left join product_process pp on t2.process_id = pp.id
        where t1.product_id = #{productId}
        order by t1.id desc
    </select>
    <select id="listBinding" resultType="com.ruoyi.quality.dto.QualityTestStandardBindingDto">
        select t2.product_name,
               t1.id qualityTestStandardBindingId
src/main/resources/mapper/quality/QualityTestStandardParamMapper.xml
@@ -18,10 +18,32 @@
        <result column="default_value" property="defaultValue" />
    </resultMap>
    <select id="selectListByProductId" resultType="com.ruoyi.quality.pojo.QualityTestStandardParam">
        select t1.* from quality_test_standard_param t1
        left join quality_test_standard t2 on t1.test_standard_id = t2.id
        left join quality_test_standard_binding t3 on t2.id = t3.test_standard_id
        select distinct t1.* from quality_test_standard_param t1
        inner join quality_test_standard t2 on t1.test_standard_id = t2.id
        inner join quality_test_standard_binding t3 on t2.id = t3.test_standard_id
        left join product_process pp on t2.process_id = pp.id
        where t3.product_id = #{productId}
        <if test="inspectType != null">
            and t2.inspect_type = #{inspectType}
        </if>
        <if test="process != null and process != ''">
            and pp.name = #{process}
        </if>
        order by t1.id desc
    </select>
    <select id="selectPageByProductId" resultType="com.ruoyi.quality.pojo.QualityTestStandardParam">
        select distinct t1.* from quality_test_standard_param t1
        inner join quality_test_standard t2 on t1.test_standard_id = t2.id
        inner join quality_test_standard_binding t3 on t1.test_standard_id = t3.test_standard_id
        where t3.product_id = #{productId}
        <if test="inspectType != null">
            and t2.inspect_type = #{inspectType}
        </if>
        <if test="processId != null">
            and t2.process_id = #{processId}
        </if>
        order by t1.id desc
    </select>
</mapper>
src/test/java/com/ruoyi/quality/controller/QualityInspectControllerTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,140 @@
package com.ruoyi.quality.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
import com.ruoyi.quality.dto.QualityInspectDto;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.service.IQualityInspectFileService;
import com.ruoyi.quality.service.IQualityInspectParamService;
import com.ruoyi.quality.service.IQualityInspectService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class QualityInspectControllerTest {
    @Mock private IQualityInspectService inspectService;
    @Mock private IQualityInspectParamService paramService;
    @Mock private IQualityInspectFileService fileService;
    @Mock private ProcurementRecordService procurementRecordService;
    @InjectMocks private QualityInspectController controller;
    @Test
    void addAcceptsCompleteInspection() {
        QualityInspectDto dto = validInspection(0);
        when(inspectService.add(dto)).thenReturn(1);
        AjaxResult result = controller.add(dto);
        assertTrue(result.isSuccess());
        assertEquals(1, result.get(AjaxResult.DATA_TAG));
    }
    @Test
    void addRejectsInspectionWithoutMetrics() {
        QualityInspectDto dto = validInspection(0);
        dto.setQualityInspectParams(Collections.emptyList());
        AjaxResult result = controller.add(dto);
        assertTrue(result.isError());
        assertEquals("当前产品未配置检验指标,请先维护指标", result.get(AjaxResult.MSG_TAG));
        verify(inspectService, never()).add(any());
    }
    @Test
    void addRejectsMetricWithoutTestValue() {
        QualityInspectDto dto = validInspection(0);
        dto.getQualityInspectParams().get(0).setTestValue(" ");
        AjaxResult result = controller.add(dto);
        assertTrue(result.isError());
        assertEquals("请填写“硬度”的检验值", result.get(AjaxResult.MSG_TAG));
    }
    @Test
    void processInspectionRequiresProcess() {
        QualityInspectDto dto = validInspection(1);
        dto.setProcess(null);
        AjaxResult result = controller.add(dto);
        assertTrue(result.isError());
        assertEquals("过程检验必须选择工序", result.get(AjaxResult.MSG_TAG));
    }
    @Test
    void updateRejectsSubmittedInspection() {
        QualityInspectDto dto = validInspection(2);
        dto.setId(9L);
        QualityInspect stored = new QualityInspect();
        stored.setInspectState(1);
        when(inspectService.getById(9L)).thenReturn(stored);
        AjaxResult result = controller.update(dto);
        assertTrue(result.isError());
        assertEquals("已提交的检验记录不能修改", result.get(AjaxResult.MSG_TAG));
        verify(inspectService, never()).updateQualityInspect(any());
    }
    @Test
    void updateAcceptsUnsubmittedInspection() {
        QualityInspectDto dto = validInspection(2);
        dto.setId(9L);
        QualityInspect stored = new QualityInspect();
        stored.setInspectState(0);
        when(inspectService.getById(9L)).thenReturn(stored);
        when(inspectService.updateQualityInspect(dto)).thenReturn(1);
        AjaxResult result = controller.update(dto);
        assertTrue(result.isSuccess());
    }
    @Test
    void listPageDelegatesByInspectionType() {
        Page<QualityInspect> page = new Page<>(1, 10);
        QualityInspect query = new QualityInspect();
        query.setInspectType(2);
        when(inspectService.qualityInspectListPage(page, query)).thenReturn(page);
        AjaxResult result = controller.qualityInspectListPage(page, query);
        assertTrue(result.isSuccess());
        assertEquals(page, result.get(AjaxResult.DATA_TAG));
    }
    private QualityInspectDto validInspection(int inspectType) {
        QualityInspectDto dto = new QualityInspectDto();
        dto.setInspectType(inspectType);
        dto.setProductId(10L);
        dto.setCheckTime(new Date());
        dto.setQuantity(BigDecimal.ONE);
        dto.setCheckResult("合格");
        dto.setProcess(inspectType == 1 ? "装配" : null);
        QualityInspectParam parameter = new QualityInspectParam();
        parameter.setParameterItem("硬度");
        parameter.setStandardValue("50-55");
        parameter.setTestValue("52");
        dto.setQualityInspectParams(Collections.singletonList(parameter));
        return dto;
    }
}
src/test/java/com/ruoyi/quality/controller/QualityMetricControllerTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,198 @@
package com.ruoyi.quality.controller;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.quality.dto.QualityMetricDto;
import com.ruoyi.quality.mapper.QualityTestStandardBindingMapper;
import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
import com.ruoyi.quality.pojo.QualityTestStandard;
import com.ruoyi.quality.pojo.QualityTestStandardBinding;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.quality.service.IQualityTestStandardService;
import com.ruoyi.quality.service.QualityTestStandardBindingService;
import com.ruoyi.quality.service.QualityTestStandardParamService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class QualityMetricControllerTest {
    @Mock
    private QualityTestStandardParamMapper paramMapper;
    @Mock
    private QualityTestStandardParamService paramService;
    @Mock
    private QualityTestStandardBindingService bindingService;
    @Mock
    private IQualityTestStandardService standardService;
    @Mock
    private QualityTestStandardBindingMapper bindingMapper;
    private QualityMetricController controller;
    @BeforeEach
    void setUp() {
        controller = new QualityMetricController(paramMapper, paramService,
                bindingService, standardService, bindingMapper);
    }
    @Test
    void addCreatesStandardAndBindingForFirstMetric() {
        QualityMetricDto metric = metric(10L, null, "硬度", "HRC", "50-55", "51-54");
        when(bindingService.list(any(Wrapper.class))).thenReturn(Collections.emptyList());
        when(standardService.save(any(QualityTestStandard.class))).thenAnswer(invocation -> {
            QualityTestStandard standard = invocation.getArgument(0);
            standard.setId(100L);
            return true;
        });
        when(bindingService.save(any(QualityTestStandardBinding.class))).thenReturn(true);
        when(paramService.save(any(QualityTestStandardParam.class))).thenReturn(true);
        AjaxResult result = controller.add(metric);
        assertTrue(result.isSuccess());
        ArgumentCaptor<QualityTestStandardBinding> bindingCaptor =
                ArgumentCaptor.forClass(QualityTestStandardBinding.class);
        verify(bindingService).save(bindingCaptor.capture());
        assertEquals(10L, bindingCaptor.getValue().getProductId());
        assertEquals(100, bindingCaptor.getValue().getTestStandardId());
        ArgumentCaptor<QualityTestStandardParam> parameterCaptor =
                ArgumentCaptor.forClass(QualityTestStandardParam.class);
        verify(paramService).save(parameterCaptor.capture());
        assertEquals(100L, parameterCaptor.getValue().getTestStandardId());
        assertEquals("硬度", parameterCaptor.getValue().getParameterItem());
        assertEquals("50-55", parameterCaptor.getValue().getStandardValue());
    }
    @Test
    void addReusesExistingProductBinding() {
        QualityTestStandardBinding existing = new QualityTestStandardBinding();
        existing.setProductId(10L);
        existing.setTestStandardId(88);
        when(bindingService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(existing));
        QualityTestStandard matched = new QualityTestStandard();
        matched.setId(88L);
        matched.setInspectType(0);
        when(standardService.getOne(any(Wrapper.class))).thenReturn(matched);
        when(paramService.save(any(QualityTestStandardParam.class))).thenReturn(true);
        AjaxResult result = controller.add(metric(10L, null, "长度", "mm", "100", "99-101"));
        assertTrue(result.isSuccess());
        verify(standardService, never()).save(any(QualityTestStandard.class));
        verify(bindingService, never()).save(any(QualityTestStandardBinding.class));
        ArgumentCaptor<QualityTestStandardParam> captor =
                ArgumentCaptor.forClass(QualityTestStandardParam.class);
        verify(paramService).save(captor.capture());
        assertEquals(88L, captor.getValue().getTestStandardId());
    }
    @Test
    void addRejectsMissingProductWithoutWritingData() {
        AjaxResult result = controller.add(metric(null, null, "长度", "mm", "100", null));
        assertTrue(result.isError());
        assertEquals("请先选择产品", result.get(AjaxResult.MSG_TAG));
        verify(standardService, never()).save(any(QualityTestStandard.class));
        verify(paramService, never()).save(any(QualityTestStandardParam.class));
    }
    @Test
    void updateChangesOnlyEditableMetricFields() {
        QualityTestStandardParam stored = new QualityTestStandardParam();
        stored.setId(5L);
        stored.setTestStandardId(88L);
        stored.setParameterItem("旧指标");
        when(paramService.getById(5L)).thenReturn(stored);
        when(paramService.updateById(any(QualityTestStandardParam.class))).thenReturn(true);
        AjaxResult result = controller.update(metric(null, 5L, "新指标", "mm", "20", "19-21"));
        assertTrue(result.isSuccess());
        ArgumentCaptor<QualityTestStandardParam> captor =
                ArgumentCaptor.forClass(QualityTestStandardParam.class);
        verify(paramService).updateById(captor.capture());
        assertSame(stored, captor.getValue());
        assertEquals(88L, stored.getTestStandardId());
        assertEquals("新指标", stored.getParameterItem());
        assertEquals("20", stored.getStandardValue());
    }
    @Test
    void updateReportsMissingMetric() {
        when(paramService.getById(99L)).thenReturn(null);
        AjaxResult result = controller.update(metric(null, 99L, "指标", "mm", "1", null));
        assertTrue(result.isError());
        assertEquals("检测指标不存在", result.get(AjaxResult.MSG_TAG));
        verify(paramService, never()).updateById(any(QualityTestStandardParam.class));
    }
    @Test
    void deleteRejectsEmptySelectionAndDeletesSelectedMetrics() {
        AjaxResult emptyResult = controller.delete(Collections.emptyList());
        assertTrue(emptyResult.isError());
        verify(paramService, never()).removeBatchByIds(any());
        when(paramService.removeBatchByIds(Arrays.asList(1L, 2L))).thenReturn(true);
        AjaxResult deleteResult = controller.delete(Arrays.asList(1L, 2L));
        assertTrue(deleteResult.isSuccess());
    }
    @Test
    void listByProductReturnsMapperData() {
        QualityTestStandardParam parameter = new QualityTestStandardParam();
        parameter.setId(1L);
        when(paramMapper.selectListByProductId(10L, 0, null)).thenReturn(Collections.singletonList(parameter));
        AjaxResult result = controller.listByProduct(10L, 0, null);
        assertTrue(result.isSuccess());
        assertEquals(Collections.singletonList(parameter), result.get(AjaxResult.DATA_TAG));
    }
    @Test
    void listPageReturnsPagedMapperData() {
        Page<QualityTestStandardParam> expected = new Page<>(1, 10);
        expected.setRecords(Collections.singletonList(new QualityTestStandardParam()));
        when(paramMapper.selectPageByProductId(any(Page.class), any(Long.class), any(), any()))
                .thenReturn(expected);
        AjaxResult result = controller.listPage(new Page<>(1, 10), 10L, 0, null);
        assertTrue(result.isSuccess());
        assertSame(expected, result.get(AjaxResult.DATA_TAG));
        assertFalse(expected.getRecords().isEmpty());
    }
    private QualityMetricDto metric(Long productId, Long id, String name,
                                    String unit, String standardValue, String controlValue) {
        QualityMetricDto metric = new QualityMetricDto();
        metric.setProductId(productId);
        metric.setId(id);
        metric.setParameterItem(name);
        metric.setUnit(unit);
        metric.setStandardValue(standardValue);
        metric.setControlValue(controlValue);
        return metric;
    }
}
src/test/java/com/ruoyi/quality/controller/QualityUnqualifiedControllerTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
package com.ruoyi.quality.controller;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.quality.service.IQualityUnqualifiedService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class QualityUnqualifiedControllerTest {
    @Mock private IQualityUnqualifiedService service;
    @InjectMocks private QualityUnqualifiedController controller;
    @Test
    void addCreatesPendingRecordByDefault() {
        QualityUnqualified record = pendingRecord();
        record.setInspectState(null);
        when(service.save(record)).thenReturn(true);
        AjaxResult result = controller.add(record);
        assertTrue(result.isSuccess());
        assertEquals(0, record.getInspectState());
    }
    @Test
    void addRejectsMissingReason() {
        QualityUnqualified record = pendingRecord();
        record.setDefectivePhenomena(" ");
        AjaxResult result = controller.add(record);
        assertTrue(result.isError());
        assertEquals("请填写不合格原因或现象", result.get(AjaxResult.MSG_TAG));
        verify(service, never()).save(any());
    }
    @Test
    void processedRecordRequiresTreatmentMethod() {
        QualityUnqualified record = processedRecord();
        record.setDealType(null);
        AjaxResult result = controller.add(record);
        assertTrue(result.isError());
        assertEquals("请选择处理方式", result.get(AjaxResult.MSG_TAG));
    }
    @Test
    void processedRecordRequiresTreatmentResult() {
        QualityUnqualified record = processedRecord();
        record.setDealResult(null);
        AjaxResult result = controller.add(record);
        assertTrue(result.isError());
        assertEquals("请填写处理结果", result.get(AjaxResult.MSG_TAG));
    }
    @Test
    void updateReportsMissingRecord() {
        QualityUnqualified record = processedRecord();
        record.setId(99L);
        when(service.getById(99L)).thenReturn(null);
        AjaxResult result = controller.update(record);
        assertTrue(result.isError());
        assertEquals("不合格记录不存在", result.get(AjaxResult.MSG_TAG));
    }
    @Test
    void updateClosesCompleteRecord() {
        QualityUnqualified record = processedRecord();
        record.setId(8L);
        when(service.getById(8L)).thenReturn(pendingRecord());
        when(service.updateById(record)).thenReturn(true);
        AjaxResult result = controller.update(record);
        assertTrue(result.isSuccess());
    }
    private QualityUnqualified pendingRecord() {
        QualityUnqualified record = new QualityUnqualified();
        record.setInspectType(0);
        record.setInspectState(0);
        record.setProductId(10L);
        record.setQuantity(BigDecimal.ONE);
        record.setDefectivePhenomena("表面划伤");
        return record;
    }
    private QualityUnqualified processedRecord() {
        QualityUnqualified record = pendingRecord();
        record.setInspectState(1);
        record.setDealType("rework");
        record.setDealResult("返工后复检合格");
        record.setDealName("张三");
        record.setDealTime(new Date());
        return record;
    }
}
src/test/java/com/ruoyi/quality/service/QualityInspectServiceImplTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
package com.ruoyi.quality.service;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.procurementrecord.service.ProcurementRecordService;
import com.ruoyi.quality.dto.QualityInspectDto;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.mapper.QualityTestStandardMapper;
import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.service.impl.QualityInspectServiceImpl;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class QualityInspectServiceImplTest {
    @Mock private QualityInspectMapper inspectMapper;
    @Mock private IQualityInspectParamService paramService;
    @Mock private QualityTestStandardMapper standardMapper;
    @Mock private QualityUnqualifiedMapper unqualifiedMapper;
    @Mock private SalesLedgerProductMapper productMapper;
    @Mock private ProcurementRecordService procurementRecordService;
    @InjectMocks private QualityInspectServiceImpl service;
    @Test
    void addPersistsInspectionAndMetrics() {
        QualityInspectDto dto = new QualityInspectDto();
        QualityInspectParam parameter = new QualityInspectParam();
        dto.setQualityInspectParams(Collections.singletonList(parameter));
        when(inspectMapper.insert(any(QualityInspect.class))).thenAnswer(invocation -> {
            QualityInspect inspect = invocation.getArgument(0);
            inspect.setId(12L);
            return 1;
        });
        when(paramService.saveBatch(dto.getQualityInspectParams())).thenReturn(true);
        int result = service.add(dto);
        assertEquals(1, result);
        ArgumentCaptor<QualityInspect> inspectCaptor = ArgumentCaptor.forClass(QualityInspect.class);
        verify(inspectMapper).insert(inspectCaptor.capture());
        assertEquals(0, inspectCaptor.getValue().getInspectState());
        assertEquals(12L, parameter.getInspectId());
    }
    @Test
    void submitRejectsRepeatedSubmissionBeforeCreatingSideEffects() {
        QualityInspect submitted = new QualityInspect();
        submitted.setId(5L);
        submitted.setInspectState(1);
        when(inspectMapper.selectById(5L)).thenReturn(submitted);
        QualityInspect request = new QualityInspect();
        request.setId(5L);
        ServiceException error = assertThrows(ServiceException.class, () -> service.submit(request));
        assertEquals("检验记录已提交,请勿重复操作", error.getMessage());
        verify(unqualifiedMapper, never()).insert(any());
        verify(procurementRecordService, never()).add(any());
    }
    @Test
    void submitRejectsInspectionWithoutMetrics() {
        QualityInspect stored = pendingInspection();
        when(inspectMapper.selectById(5L)).thenReturn(stored);
        when(paramService.list(any())).thenReturn(Collections.emptyList());
        QualityInspect request = new QualityInspect();
        request.setId(5L);
        ServiceException error = assertThrows(ServiceException.class, () -> service.submit(request));
        assertEquals("检验项目为空,不能提交", error.getMessage());
    }
    @Test
    void submitRejectsMetricWithoutTestValue() {
        QualityInspect stored = pendingInspection();
        QualityInspectParam parameter = new QualityInspectParam();
        parameter.setParameterItem("硬度");
        when(inspectMapper.selectById(5L)).thenReturn(stored);
        when(paramService.list(any())).thenReturn(Collections.singletonList(parameter));
        QualityInspect request = new QualityInspect();
        request.setId(5L);
        ServiceException error = assertThrows(ServiceException.class, () -> service.submit(request));
        assertEquals("检验项目“硬度”尚未填写检验值", error.getMessage());
    }
    private QualityInspect pendingInspection() {
        QualityInspect stored = new QualityInspect();
        stored.setId(5L);
        stored.setInspectState(0);
        stored.setCheckResult("合格");
        return stored;
    }
}