From a5f66f55b2c176e8504b8a077e00acfe2dfb4b00 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 18 九月 2026 18:09:56 +0800
Subject: [PATCH] feat(生产报工): 报工完成按四类区分计数零件并分流质检入库

---
 src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java                          |    8 +
 src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java                  |    8 +
 src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java |  224 +++++++++++++++++++++----------
 doc/20260918_release_report_distinguish_major_category.sql                            |   69 +++++++++
 src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml                        |   11 +
 src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java             |    6 
 src/main/resources/mapper/quality/QualityInspectMapper.xml                            |    6 
 src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java       |   12 +
 doc/20260918_add_major_category_to_production_product_main.sql                        |   36 +++++
 src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java                    |    5 
 src/main/resources/mapper/production/ProductionProductMainMapper.xml                  |    5 
 doc/20260918_add_rework_qty_for_report_distinguish.sql                                |   11 +
 src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java                            |    3 
 13 files changed, 329 insertions(+), 75 deletions(-)

diff --git a/doc/20260918_add_major_category_to_production_product_main.sql b/doc/20260918_add_major_category_to_production_product_main.sql
new file mode 100644
index 0000000..8541de4
--- /dev/null
+++ b/doc/20260918_add_major_category_to_production_product_main.sql
@@ -0,0 +1,36 @@
+-- 闇�姹傦細鐢熶骇鎶ュ伐涓嶅尯鍒嗛浂浠讹紝鍙寜浜у搧澶х被锛堝皬鎻愮惔/澶ф彁鐞�/璐濇柉锛夌粺璁�
+-- 1) 鎶ュ伐涓昏〃澧炲姞澶х被瀛楁锛堝箓绛夛細鍒楀凡瀛樺湪鏃惰烦杩囷紝鍙噸澶嶆墽琛岋級
+SET @add_major_category_ddl := (
+    SELECT IF(COUNT(*) = 0,
+              'ALTER TABLE production_product_main ADD COLUMN major_category VARCHAR(32) NULL COMMENT ''鎶ュ伐浜у搧澶х被锛氬皬鎻愮惔/澶ф彁鐞�/璐濇柉'' AFTER product_no',
+              'SELECT ''major_category 宸插瓨鍦紝璺宠繃''')
+    FROM information_schema.COLUMNS
+    WHERE TABLE_SCHEMA = DATABASE()
+      AND TABLE_NAME = 'production_product_main'
+      AND COLUMN_NAME = 'major_category'
+);
+PREPARE add_major_category_stmt FROM @add_major_category_ddl;
+EXECUTE add_major_category_stmt;
+DEALLOCATE PREPARE add_major_category_stmt;
+
+-- 2) 鍘嗗彶鏁版嵁鍥炲~锛氫紭鍏堢敤浜у嚭浜у搧鑷韩鍚嶇О鍋氬叧閿瓧褰掔被锛堢幇鍦烘暟鎹�滆礉鏂�濆啓浣溾�滆礉鍙糕�濓級锛�
+--    鏈懡涓啀鍙栦骇鍝佹爲鐖惰妭鐐瑰悕绉板厹搴曪紱浜у搧鏍戦《灞備负鈥滄垚鍝�/鍘熸枡鈥濈瓑搴撳瓨鍒嗙被锛屼笉鑳界洿鎺ヤ綔涓哄ぇ绫汇��
+--    浠嶆棤娉曞綊绫荤殑淇濇寔 NULL锛堝鐞寸洅/鐞村/涓彁鐞达級銆�
+UPDATE production_product_main ppm
+    JOIN production_product_output ppo ON ppo.production_product_main_id = ppm.id
+    JOIN product_model pm ON pm.id = ppo.product_model_id
+    JOIN product p ON p.id = pm.product_id
+    LEFT JOIN product pp ON pp.id = p.parent_id
+SET ppm.major_category = CASE
+        -- 鐞寸洅/鐞村绛夐厤浠跺甫鈥滆礉鍙糕�濈瓑瀛楁牱锛堝 SA璐濆徃濂楋級锛屽厛鎺掗櫎閬垮厤璇綊澶х被
+        WHEN p.product_name LIKE '%鐞寸洅%' OR p.product_name LIKE '%鐞村%' OR p.product_name LIKE '%寮撶洅%' THEN NULL
+        WHEN p.product_name LIKE '%灏忔彁鐞�%'
+          OR COALESCE(pp.product_name, '') LIKE '%灏忔彁鐞�%' THEN '灏忔彁鐞�'
+        WHEN p.product_name LIKE '%澶ф彁鐞�%'
+          OR COALESCE(pp.product_name, '') LIKE '%澶ф彁鐞�%' THEN '澶ф彁鐞�'
+        WHEN p.product_name LIKE '%璐濇柉%' OR p.product_name LIKE '%璐濆徃%'
+          OR COALESCE(pp.product_name, '') LIKE '%璐濇柉%'
+          OR COALESCE(pp.product_name, '') LIKE '%璐濆徃%' THEN '璐濇柉'
+        ELSE NULL
+    END
+WHERE ppm.major_category IS NULL;
diff --git a/doc/20260918_add_rework_qty_for_report_distinguish.sql b/doc/20260918_add_rework_qty_for_report_distinguish.sql
new file mode 100644
index 0000000..72a3b76
--- /dev/null
+++ b/doc/20260918_add_rework_qty_for_report_distinguish.sql
@@ -0,0 +1,11 @@
+-- 鐢熶骇鎶ュ伐瀹屾垚鍚庡璁℃暟鐨勯浂浠跺仛鍖哄垎锛堝悎鏍�/杞诲井杩斿伐/涓ラ噸杩斿伐/鎶ュ簾锛夛紝杩涘叆璐ㄦ鍏ュ簱
+-- 1. 鎶ュ伐浜у嚭琛ㄥ鍔犺繑宸ュ垎绫绘暟閲忓垪
+ALTER TABLE production_product_output
+    ADD COLUMN rework_minor_qty DECIMAL(18, 4) NOT NULL DEFAULT 0 COMMENT '杞诲井杩斿伐鏁伴噺' AFTER scrap_qty,
+    ADD COLUMN rework_major_qty DECIMAL(18, 4) NOT NULL DEFAULT 0 COMMENT '涓ラ噸杩斿伐鏁伴噺' AFTER rework_minor_qty;
+
+-- 2. 涓嶅悎鏍煎搧琛ㄥ鍔犳潵婧愭姤宸ュ崟鍏宠仈鍒楋紙鎶ュ伐鍒嗙被鐩存帴浜х敓鐨勮繑宸ュ緟澶勭悊鍗曪級
+ALTER TABLE quality_unqualified
+    ADD COLUMN product_main_id BIGINT DEFAULT NULL COMMENT '鍏宠仈鐢熶骇鎶ュ伐涓昏〃id' AFTER inspect_id;
+
+CREATE INDEX idx_quality_unqualified_product_main_id ON quality_unqualified (product_main_id);
diff --git a/doc/20260918_release_report_distinguish_major_category.sql b/doc/20260918_release_report_distinguish_major_category.sql
new file mode 100644
index 0000000..99a850b
--- /dev/null
+++ b/doc/20260918_release_report_distinguish_major_category.sql
@@ -0,0 +1,69 @@
+-- =============================================================================
+-- 涓婄嚎杩佺Щ鑴氭湰锛堝悎骞剁増锛�  鏃ユ湡锛�2026-09-18
+-- 鐢ㄩ�旓細鐢熶骇鎶ュ伐銆岃鏁伴浂浠跺垎绫诲尯鍒� 鈫� 鍒嗙被杩涘叆璐ㄦ鍏ュ簱銆�+銆屾姤宸ヤ骇鍝佸ぇ绫汇��
+-- 鐩爣搴擄細MySQL 8.x锛堝湪 8.4.7 涓婇獙璇侀�氳繃锛�
+--
+-- 鎵ц鍓嶇姸鎬佹鏌ワ紙鍏堣窇杩欎竴娈碉紝鐪嬫竻妤氬摢浜涜繕娌℃墽琛岋級锛�
+--   SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS
+--   WHERE TABLE_SCHEMA = DATABASE()
+--     AND ((TABLE_NAME = 'production_product_output' AND COLUMN_NAME IN ('rework_minor_qty','rework_major_qty'))
+--       OR (TABLE_NAME = 'quality_unqualified'      AND COLUMN_NAME = 'product_main_id')
+--       OR (TABLE_NAME = 'production_product_main'  AND COLUMN_NAME = 'major_category'));
+--   鑻ユ煡璇㈢粨鏋滃凡缁忓寘鍚煇鍒楋紝瀵瑰簲璇彞灏辫烦杩囷紝涓嶈閲嶅鎵ц锛圡ySQL 8 鐨� ADD COLUMN 涓嶆敮鎸� IF NOT EXISTS锛夈��
+--
+-- 鍚勭幆澧冩墽琛屾儏鍐碉細dev锛�192.168.197.128 / product-inventory-management-tjxm锛夊凡鎵ц锛�
+--                娴嬭瘯銆佺敓浜х幆澧冮渶鎵ц鏈剼鏈��
+-- 鎵ц椤哄簭锛氣憼 鈫� 鈶� 鈫� 鈶紝鍏ㄩ儴鎵ц瀹屽啀鍙戝竷鏂扮増鏈悗绔唬鐮侊紝鍚﹀垯鎺ュ彛浼氬洜缂哄垪鎶ラ敊銆�
+-- =============================================================================
+
+-- -----------------------------------------------------------------------------
+-- 鈶� 鎶ュ伐浜у嚭琛細澧炲姞杩斿伐鍒嗙被鏁伴噺鍒楋紙涓庡悎鏍兼暟銆佹姤搴熸暟涓�璧锋瀯鎴�"璁℃暟闆朵欢鐨勫洓绫诲尯鍒�"锛�
+-- -----------------------------------------------------------------------------
+ALTER TABLE production_product_output
+    ADD COLUMN rework_minor_qty DECIMAL(18, 4) NOT NULL DEFAULT 0 COMMENT '杞诲井杩斿伐鏁伴噺' AFTER scrap_qty,
+    ADD COLUMN rework_major_qty DECIMAL(18, 4) NOT NULL DEFAULT 0 COMMENT '涓ラ噸杩斿伐鏁伴噺' AFTER rework_minor_qty;
+
+-- -----------------------------------------------------------------------------
+-- 鈶� 涓嶅悎鏍煎搧琛細澧炲姞鏉ユ簮鎶ュ伐鍗曞叧鑱斿垪锛堟姤宸ュ尯鍒嗗嚭鐨勮繑宸ヤ欢鐩存帴鐢熸垚寰呭鐞嗗崟锛岄潬姝ゅ垪鍥炴函鎶ュ伐鍗曪級
+-- -----------------------------------------------------------------------------
+ALTER TABLE quality_unqualified
+    ADD COLUMN product_main_id BIGINT DEFAULT NULL COMMENT '鍏宠仈鐢熶骇鎶ュ伐涓昏〃id' AFTER inspect_id;
+
+CREATE INDEX idx_quality_unqualified_product_main_id ON quality_unqualified (product_main_id);
+
+-- -----------------------------------------------------------------------------
+-- 鈶� 鎶ュ伐涓昏〃锛氬鍔犱骇鍝佸ぇ绫诲垪 + 鍘嗗彶鏁版嵁鍥炲~锛堟姤宸ヤ笉鍖哄垎闆朵欢锛屽彧鎸夊皬鎻愮惔/澶ф彁鐞�/璐濇柉缁熻锛�
+-- -----------------------------------------------------------------------------
+ALTER TABLE production_product_main
+    ADD COLUMN major_category VARCHAR(32) NULL COMMENT '鎶ュ伐浜у搧澶х被锛氬皬鎻愮惔/澶ф彁鐞�/璐濇柉' AFTER product_no;
+
+-- 鍘嗗彶鏁版嵁鍥炲~锛氫紭鍏堢敤浜у嚭浜у搧鑷韩鍚嶇О鍋氬叧閿瓧褰掔被锛堢幇鍦烘暟鎹�"璐濇柉"鍐欎綔"璐濆徃"锛夛紝
+-- 鏈懡涓啀鍙栦骇鍝佹爲鐖惰妭鐐瑰悕绉板厹搴曪紱鐞寸洅/鐞村/寮撶洅绛夐厤浠跺厛鎺掗櫎閬垮厤璇綊澶х被锛涗粛鏃犳硶褰掔被鐨勪繚鎸� NULL銆�
+UPDATE production_product_main ppm
+    JOIN production_product_output ppo ON ppo.production_product_main_id = ppm.id
+    JOIN product_model pm ON pm.id = ppo.product_model_id
+    JOIN product p ON p.id = pm.product_id
+    LEFT JOIN product pp ON pp.id = p.parent_id
+SET ppm.major_category = CASE
+        WHEN p.product_name LIKE '%鐞寸洅%' OR p.product_name LIKE '%鐞村%' OR p.product_name LIKE '%寮撶洅%' THEN NULL
+        WHEN p.product_name LIKE '%灏忔彁鐞�%'
+          OR COALESCE(pp.product_name, '') LIKE '%灏忔彁鐞�%' THEN '灏忔彁鐞�'
+        WHEN p.product_name LIKE '%澶ф彁鐞�%'
+          OR COALESCE(pp.product_name, '') LIKE '%澶ф彁鐞�%' THEN '澶ф彁鐞�'
+        WHEN p.product_name LIKE '%璐濇柉%' OR p.product_name LIKE '%璐濆徃%'
+          OR COALESCE(pp.product_name, '') LIKE '%璐濇柉%'
+          OR COALESCE(pp.product_name, '') LIKE '%璐濆徃%' THEN '璐濇柉'
+        ELSE NULL
+    END
+WHERE ppm.major_category IS NULL;
+
+-- -----------------------------------------------------------------------------
+-- 鎵ц鍚庢牎楠岋紙搴旇繑鍥� 4 鍒� / 1 绱㈠紩 / 澶х被鍒嗗竷锛�
+-- -----------------------------------------------------------------------------
+-- SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT FROM information_schema.COLUMNS
+-- WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME IN ('rework_minor_qty','rework_major_qty','product_main_id','major_category');
+--
+-- SHOW INDEX FROM quality_unqualified WHERE Key_name = 'idx_quality_unqualified_product_main_id';
+--
+-- SELECT major_category, COUNT(*) FROM production_product_main GROUP BY major_category;
+-- =============================================================================
diff --git a/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java b/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
index 85d3ed8..ad13722 100644
--- a/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
+++ b/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
@@ -63,6 +63,12 @@
     @Schema(description = "鎶ュ簾鏁伴噺")
     private BigDecimal scrapQty;
 
+    @Schema(description = "杞诲井杩斿伐鏁伴噺")
+    private BigDecimal reworkMinorQty;
+
+    @Schema(description = "涓ラ噸杩斿伐鏁伴噺")
+    private BigDecimal reworkMajorQty;
+
     @Schema(description = "浜у搧鍚嶇О")
     private String productName;
 
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java b/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
index 8679dfb..720917f 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
@@ -21,6 +21,11 @@
     @Excel(name = "鎶ュ伐鍗曞彿")
     private String productNo;
 
+    @Schema(description = "鎶ュ伐浜у搧澶х被锛氬皬鎻愮惔/澶ф彁鐞�/璐濇柉")
+    @TableField("major_category")
+    @Excel(name = "浜у搧澶х被")
+    private String majorCategory;
+
     @Schema(description = "鎶ュ伐浜哄憳id")
     private Long userId;
 
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java b/src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java
index 40f9a41..8c51aff 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java
@@ -33,6 +33,14 @@
 
     @Schema(description = "鎶ュ簾鏁伴噺")
     private BigDecimal scrapQty;
+
+    @Schema(description = "杞诲井杩斿伐鏁伴噺")
+    @TableField("rework_minor_qty")
+    private BigDecimal reworkMinorQty;
+
+    @Schema(description = "涓ラ噸杩斿伐鏁伴噺")
+    @TableField("rework_major_qty")
+    private BigDecimal reworkMajorQty;
     @Schema(description = "鍒涘缓鐢ㄦ埛")
     @TableField(fill = FieldFill.INSERT)
     private Integer createUser;
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
index 9c4c722..ce9eee2 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -22,7 +22,6 @@
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.procurementrecord.utils.StockUtils;
-import com.ruoyi.production.bean.dto.ProductStructureDto;
 import com.ruoyi.production.bean.dto.ProductionProductMainDto;
 import com.ruoyi.production.enums.ProductOrderStatusEnum;
 import com.ruoyi.production.mapper.*;
@@ -50,6 +49,7 @@
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -63,6 +63,16 @@
 @AllArgsConstructor
 @Transactional(rollbackFor = Exception.class)
 public class ProductionProductMainServiceImpl extends ServiceImpl<ProductionProductMainMapper, ProductionProductMain> implements ProductionProductMainService {
+
+    /**
+     * 鐢熶骇鎶ュ伐澶х被锛氬彧鍖哄垎灏忔彁鐞�/澶ф彁鐞�/璐濇柉锛屼笉鍐嶅尯鍒嗛浂浠躲��
+     * key涓哄叆搴撹鑼冨�硷紝value涓轰骇鍝佸悕绉板尮閰嶅埆鍚嶏紙鐜板満鏁版嵁璐濇柉鍐欎綔鈥滆礉鍙糕�濓級銆�
+     */
+    private static final Map<String, List<String>> MAJOR_CATEGORY_PATTERNS = new LinkedHashMap<>() {{
+        put("灏忔彁鐞�", List.of("灏忔彁鐞�"));
+        put("澶ф彁鐞�", List.of("澶ф彁鐞�"));
+        put("璐濇柉", List.of("璐濇柉", "璐濆徃"));
+    }};
 
     private final ProductionProductMainMapper productionProductMainMapper;
     private final SysUserMapper userMapper;
@@ -78,8 +88,6 @@
     private final ProductionAccountMapper productionAccountMapper;
     private final ProductionOperationTaskMapper productionOperationTaskMapper;
     private final ProductionOrderMapper productionOrderMapper;
-    private final ProductionOrderBomMapper productionOrderBomMapper;
-    private final ProductionBomStructureMapper productionBomStructureMapper;
     private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
     private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper;
     private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
@@ -279,13 +287,31 @@
             throw new ServiceException("浜у搧瑙勬牸涓嶅瓨鍦�");
         }
 
+        // 鎶ュ伐瀹屾垚鏃跺璁℃暟鐨勯浂浠跺仛鍖哄垎锛氬悎鏍�/杞诲井杩斿伐/涓ラ噸杩斿伐/鎶ュ簾鍥涚被鏁伴噺銆�
+        BigDecimal qualifiedQty = defaultDecimal(dto.getQuantity());
+        BigDecimal reworkMinorQty = defaultDecimal(dto.getReworkMinorQty());
+        BigDecimal reworkMajorQty = defaultDecimal(dto.getReworkMajorQty());
+        BigDecimal scrapQtyInput = defaultDecimal(dto.getScrapQty());
+        if (qualifiedQty.compareTo(BigDecimal.ZERO) < 0
+                || reworkMinorQty.compareTo(BigDecimal.ZERO) < 0
+                || reworkMajorQty.compareTo(BigDecimal.ZERO) < 0
+                || scrapQtyInput.compareTo(BigDecimal.ZERO) < 0) {
+            throw new ServiceException("鎶ュ伐鏁伴噺涓嶈兘涓鸿礋鏁�");
+        }
+        if (qualifiedQty.add(reworkMinorQty).add(reworkMajorQty).add(scrapQtyInput).compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("璇锋寜鍚堟牸/杞诲井杩斿伐/涓ラ噸杩斿伐/鎶ュ簾鍒嗙被濉啓闆朵欢鏁伴噺");
+        }
+
         ProductionProductMain productionProductMain = new ProductionProductMain();
         productionProductMain.setProductNo(generateProductNo());
+        productionProductMain.setMajorCategory(resolveMajorCategory(productModel));
         productionProductMain.setUserId(user == null ? dto.getUserId() : user.getUserId());
         productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName());
         productionProductMain.setProductionOperationTaskId(taskId);
         productionProductMain.setStatus(0);
-        productionProductMain.setReportType(dto.getReportType() == null ? 0 : dto.getReportType());
+        productionProductMain.setReportType(dto.getReportType() != null
+                ? dto.getReportType()
+                : deriveReportType(qualifiedQty, reworkMinorQty, reworkMajorQty, scrapQtyInput));
         productionProductMain.setAuditStatus(dto.getAuditStatus() == null ? 0 : dto.getAuditStatus());
         productionProductMain.setAuditRemark(dto.getAuditRemark());
         productionProductMain.setWorkHour(dto.getWorkHour());
@@ -293,30 +319,21 @@
         syncOperationParamInputValue(dto, routingOperation.getId(), productionProductMain.getId());
         createReportApprovalInstance(productionProductMain);
 
-        List<ProductStructureDto> productStructureDtos = resolveInputStructures(
-                productionOrder.getId(), routingOperation, productModel.getId());
-       // 濡傛灉娌℃湁bom瀛愯妭鐐逛簡锛岄偅涔堟姇鍏ュ氨鏄粬鏈韩
-        if (productStructureDtos.isEmpty()) {
-            ProductStructureDto fallbackInput = new ProductStructureDto();
-            fallbackInput.setProductModelId(productModel.getId());
-            fallbackInput.setUnitQuantity(BigDecimal.ONE);
-            productStructureDtos.add(fallbackInput);
-        }
-        for (ProductStructureDto item : productStructureDtos) {
-            // 褰撳墠瀹炵幇鎸夊伐搴忔垚鍝佺洿鎺ヤ綔涓烘姇鍏ワ紝鍚庣画鑻ユ帴鍏ラ鏂欒褰曞彲鍦ㄨ繖閲屾浛鎹㈡潵婧愩��
-            ProductionProductInput productionProductInput = new ProductionProductInput();
-            productionProductInput.setProductionProductMainId(productionProductMain.getId());
-            productionProductInput.setProductModelId(item.getProductModelId());
-            productionProductInput.setInputQuantity(item.getUnitQuantity().multiply(defaultDecimal(dto.getQuantity())));
-            productionProductInput.setQuantity(productionProductInput.getInputQuantity());
-            productionProductInputMapper.insert(productionProductInput);
-        }
+        // 闇�姹傚彉鏇达細鐢熶骇鎶ュ伐涓嶅啀鎸夐浂浠讹紙BOM缁撴瀯锛夋媶鍒嗘姇鍏ワ紝鎶曞叆鍗虫湰娆℃姤宸ョ殑浜у搧鏈韩锛屾寜澶х被缁熻銆�
+        ProductionProductInput productionProductInput = new ProductionProductInput();
+        productionProductInput.setProductionProductMainId(productionProductMain.getId());
+        productionProductInput.setProductModelId(productModel.getId());
+        productionProductInput.setInputQuantity(qualifiedQty);
+        productionProductInput.setQuantity(qualifiedQty);
+        productionProductInputMapper.insert(productionProductInput);
 
         ProductionProductOutput productionProductOutput = new ProductionProductOutput();
         productionProductOutput.setProductionProductMainId(productionProductMain.getId());
         productionProductOutput.setProductModelId(productModel.getId());
         productionProductOutput.setQuantity(defaultDecimal(dto.getQuantity()));
         productionProductOutput.setScrapQty(defaultDecimal(dto.getScrapQty()));
+        productionProductOutput.setReworkMinorQty(reworkMinorQty);
+        productionProductOutput.setReworkMajorQty(reworkMajorQty);
         productionProductOutputMapper.insert(productionProductOutput);
         BigDecimal reportQty = defaultDecimal(productionProductOutput.getQuantity());
         BigDecimal scrapQty = defaultDecimal(productionProductOutput.getScrapQty());
@@ -428,7 +445,69 @@
                     productionProductMain.getId(),
                     qualifiedBatchNo);
         }
+        // 鍖哄垎鍑虹殑杩斿伐闆朵欢鐢熸垚涓嶅悎鏍煎搧寰呭鐞嗗崟锛岃繘鍏ヤ笉鍚堟牸鍝佺鐞嗙殑鎶ュ伐澶勭悊闂幆銆�
+        createReworkUnqualifiedRecords(productionProductMain, productModel, reworkMinorQty, reworkMajorQty);
         return true;
+    }
+
+    private Integer deriveReportType(BigDecimal qualifiedQty, BigDecimal reworkMinorQty,
+                                     BigDecimal reworkMajorQty, BigDecimal scrapQty) {
+        if (qualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
+            return 0;
+        }
+        if (reworkMinorQty.compareTo(BigDecimal.ZERO) > 0) {
+            return 1;
+        }
+        if (reworkMajorQty.compareTo(BigDecimal.ZERO) > 0) {
+            return 2;
+        }
+        if (scrapQty.compareTo(BigDecimal.ZERO) > 0) {
+            return 3;
+        }
+        return 0;
+    }
+
+    private void createReworkUnqualifiedRecords(ProductionProductMain productionProductMain,
+                                                ProductModel productModel,
+                                                BigDecimal reworkMinorQty,
+                                                BigDecimal reworkMajorQty) {
+        if (reworkMinorQty.compareTo(BigDecimal.ZERO) <= 0 && reworkMajorQty.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        Product product = productMapper.selectById(productModel.getProductId());
+        if (product == null) {
+            throw new ServiceException("浜у搧涓嶅瓨鍦紝鏃犳硶鐢熸垚鎶ュ伐杩斿伐寰呭鐞嗗崟");
+        }
+        if (reworkMinorQty.compareTo(BigDecimal.ZERO) > 0) {
+            insertReworkUnqualified(productionProductMain, product, productModel, reworkMinorQty, 0);
+        }
+        if (reworkMajorQty.compareTo(BigDecimal.ZERO) > 0) {
+            insertReworkUnqualified(productionProductMain, product, productModel, reworkMajorQty, 1);
+        }
+    }
+
+    private void insertReworkUnqualified(ProductionProductMain productionProductMain,
+                                         Product product,
+                                         ProductModel productModel,
+                                         BigDecimal quantity,
+                                         int dealType) {
+        QualityUnqualified qualityUnqualified = new QualityUnqualified();
+        qualityUnqualified.setInspectType(1);
+        qualityUnqualified.setInspectState(0);
+        qualityUnqualified.setCheckTime(new Date());
+        qualityUnqualified.setCheckName(productionProductMain.getUserName());
+        qualityUnqualified.setProductId(product.getId());
+        qualityUnqualified.setProductName(product.getProductName());
+        qualityUnqualified.setModel(productModel.getModel());
+        qualityUnqualified.setUnit(productModel.getUnit());
+        qualityUnqualified.setQuantity(quantity);
+        qualityUnqualified.setProductModelId(productModel.getId());
+        qualityUnqualified.setProductMainId(productionProductMain.getId());
+        qualityUnqualified.setDefectivePhenomena("鐢熶骇鎶ュ伐銆�" + productionProductMain.getProductNo()
+                + "銆戣鏁板尯鍒�-" + (dealType == 0 ? "杞诲井杩斿伐" : "涓ラ噸杩斿伐"));
+        qualityUnqualified.setDealType(dealType);
+        qualityUnqualified.setDealResult("鎶ュ伐");
+        qualityUnqualifiedMapper.insert(qualityUnqualified);
     }
 
     private void createReportApprovalInstance(ProductionProductMain productionProductMain) {
@@ -468,6 +547,8 @@
         BigDecimal schedulingNum = task == null ? BigDecimal.ZERO : defaultDecimal(task.getPlanQuantity());
         BigDecimal finishedNum = output == null ? BigDecimal.ZERO : defaultDecimal(output.getQuantity());
         BigDecimal scrapQty = output == null ? BigDecimal.ZERO : defaultDecimal(output.getScrapQty());
+        BigDecimal reworkMinorNum = output == null ? BigDecimal.ZERO : defaultDecimal(output.getReworkMinorQty());
+        BigDecimal reworkMajorNum = output == null ? BigDecimal.ZERO : defaultDecimal(output.getReworkMajorQty());
         BigDecimal pendingNum = schedulingNum.subtract(finishedNum);
         if (pendingNum.compareTo(BigDecimal.ZERO) < 0) {
             pendingNum = BigDecimal.ZERO;
@@ -480,7 +561,9 @@
         fields.add(buildReportField("workOrderNo", "宸ュ崟缂栧彿", "text", resolveWorkOrderNo(productionProductMain.getProductionOperationTaskId())));
         fields.add(buildReportField("schedulingNum", "鎺掍骇鏁伴噺", "number", schedulingNum));
         fields.add(buildReportField("pendingNum", "寰呯敓浜ф暟閲�", "number", pendingNum));
-        fields.add(buildReportField("finishedNum", "鐢熶骇鍚堟牸鏁伴噺", "number", finishedNum));
+        fields.add(buildReportField("finishedNum", "鍚堟牸鏁伴噺", "number", finishedNum));
+        fields.add(buildReportField("reworkMinorQty", "杞诲井杩斿伐鏁伴噺", "number", reworkMinorNum));
+        fields.add(buildReportField("reworkMajorQty", "涓ラ噸杩斿伐鏁伴噺", "number", reworkMajorNum));
         fields.add(buildReportField("scrapQty", "鎶ュ簾鏁伴噺", "number", scrapQty));
         fields.add(buildReportField("reportType", "鎶ュ伐缁撴灉", "select", resolveReportTypeLabel(productionProductMain.getReportType())));
         fields.add(buildReportField("workHour", "宸ユ椂", "number", productionProductMain.getWorkHour()));
@@ -497,6 +580,8 @@
         formPayload.put("pendingNum", pendingNum);
         formPayload.put("finishedNum", finishedNum);
         formPayload.put("scrapQty", scrapQty);
+        formPayload.put("reworkMinorQty", reworkMinorNum);
+        formPayload.put("reworkMajorQty", reworkMajorNum);
         formPayload.put("reportType", resolveReportTypeLabel(productionProductMain.getReportType()));
         formPayload.put("workHour", productionProductMain.getWorkHour());
         formPayload.put("auditStatus", resolveAuditStatusLabel(productionProductMain.getAuditStatus()));
@@ -655,62 +740,46 @@
         return target;
     }
 
-    private List<ProductStructureDto> resolveInputStructures(Long productionOrderId,
-                                                             ProductionOrderRoutingOperation routingOperation,
-                                                             Long outputProductModelId) {
-        if (productionOrderId == null || routingOperation == null || routingOperation.getTechnologyOperationId() == null) {
-            return new ArrayList<>();
+    /**
+     * 闇�姹傦細鐢熶骇鎶ュ伐鍙尯鍒嗕骇鍝佸ぇ绫伙紙灏忔彁鐞�/澶ф彁鐞�/璐濇柉锛夛紝涓嶅啀鍖哄垎闆朵欢銆�
+     * 浠庝骇鍑轰骇鍝佽嚜韬悕绉板紑濮嬫部浜у搧鏍戝悜涓婂仛鍏抽敭瀛楀尮閰嶏紝鍛戒腑鍗冲彇瀵瑰簲瑙勮寖澶х被锛涙湭鍛戒腑杩斿洖 null銆�
+     * 浜у搧鏍戦《灞備负鈥滄垚鍝�/鍗婃垚鍝�/鍘熸枡鈥濈瓑搴撳瓨鍒嗙被锛屼笉鑳界洿鎺ュ綋浣滃ぇ绫伙紝鍥犳蹇呴』鑷簳鍚戜笂鍙栫涓�涓懡涓殑灞傜骇銆�
+     */
+    private String resolveMajorCategory(ProductModel productModel) {
+        if (productModel == null || productModel.getProductId() == null) {
+            return null;
         }
-        ProductionOrderBom orderBom = productionOrderBomMapper.selectOne(
-                Wrappers.<ProductionOrderBom>lambdaQuery()
-                        .eq(ProductionOrderBom::getProductionOrderId, productionOrderId)
-                        .orderByDesc(ProductionOrderBom::getId)
-                        .last("limit 1"));
-        if (orderBom == null || orderBom.getId() == null) {
-            return new ArrayList<>();
-        }
-
-        List<ProductionBomStructure> bomNodeList = productionBomStructureMapper.selectList(
-                Wrappers.<ProductionBomStructure>lambdaQuery()
-                        .eq(ProductionBomStructure::getProductionOrderBomId, orderBom.getId())
-                        .orderByAsc(ProductionBomStructure::getId));
-        if (bomNodeList.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        Map<Long, ProductionBomStructure> nodeMap = bomNodeList.stream()
-                .filter(item -> item != null && item.getId() != null)
-                .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
-        Long currentOutputModelId = routingOperation.getProductModelId() != null
-                ? routingOperation.getProductModelId()
-                : outputProductModelId;
-
-        Map<Long, BigDecimal> unitQtyByProductModel = new LinkedHashMap<>();
-        for (ProductionBomStructure node : bomNodeList) {
-            if (node == null || node.getParentId() == null || node.getProductModelId() == null) {
-                continue;
+        Product product = productMapper.selectById(productModel.getProductId());
+        Set<Long> visited = new LinkedHashSet<>();
+        while (product != null && visited.add(product.getId())) {
+            String category = matchMajorCategory(product.getProductName());
+            if (category != null) {
+                return category;
             }
-            if (!Objects.equals(node.getTechnologyOperationId(), routingOperation.getTechnologyOperationId())) {
-                continue;
+            if (product.getParentId() == null || product.getParentId() == 0L) {
+                break;
             }
-            ProductionBomStructure parent = nodeMap.get(node.getParentId());
-            if (parent == null || !Objects.equals(parent.getProductModelId(), currentOutputModelId)) {
-                continue;
-            }
-            unitQtyByProductModel.merge(node.getProductModelId(), defaultDecimal(node.getUnitQuantity()), BigDecimal::add);
+            product = productMapper.selectById(product.getParentId());
         }
+        return null;
+    }
 
-        List<ProductStructureDto> result = new ArrayList<>();
-        for (Map.Entry<Long, BigDecimal> entry : unitQtyByProductModel.entrySet()) {
-            if (entry.getValue().compareTo(BigDecimal.ZERO) <= 0) {
-                continue;
-            }
-            ProductStructureDto item = new ProductStructureDto();
-            item.setProductModelId(entry.getKey());
-            item.setUnitQuantity(entry.getValue());
-            result.add(item);
+    static String matchMajorCategory(String productName) {
+        if (productName == null || productName.isEmpty()) {
+            return null;
         }
-        return result;
+        // 鐞寸洅/鐞村绛夐厤浠跺悕绉板彲鑳藉甫鈥滆礉鍙糕�濈瓑瀛楁牱锛堝 SA璐濆徃濂楋級锛屾帓闄ゅ悗閬垮厤璇綊澶х被銆�
+        if (productName.contains("鐞寸洅") || productName.contains("鐞村") || productName.contains("寮撶洅")) {
+            return null;
+        }
+        for (Map.Entry<String, List<String>> entry : MAJOR_CATEGORY_PATTERNS.entrySet()) {
+            for (String alias : entry.getValue()) {
+                if (productName.contains(alias)) {
+                    return entry.getKey();
+                }
+            }
+        }
+        return null;
     }
 
     private Boolean removeProductMainByProductionTask(ProductionProductMain productionProductMain) {
@@ -726,6 +795,13 @@
             if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState() == 1) {
                 throw new ServiceException("璇ユ潯鎶ュ伐宸茬粡涓嶅悎鏍煎鐞嗕簡锛屼笉鍏佽鍒犻櫎");
             }
+        }
+        // 鎶ュ伐璁℃暟鍖哄垎鐩存帴浜х敓鐨勮繑宸ュ緟澶勭悊鍗曪細宸插鐞嗙殑涓嶅厑璁稿垹闄ゆ姤宸ワ紝鏈鐞嗙殑闅忔姤宸ヤ竴骞跺垹闄ゃ��
+        List<QualityUnqualified> reworkUnqualifieds = qualityUnqualifiedMapper.selectList(
+                Wrappers.<QualityUnqualified>lambdaQuery()
+                        .eq(QualityUnqualified::getProductMainId, productionProductMain.getId()));
+        if (reworkUnqualifieds.stream().anyMatch(q -> Integer.valueOf(1).equals(q.getInspectState()))) {
+            throw new ServiceException("璇ユ潯鎶ュ伐鐨勮繑宸ラ浂浠跺凡鍋氫笉鍚堟牸澶勭悊锛屼笉鍏佽鍒犻櫎");
         }
         ProductionProductOutput productionProductOutput = productionProductOutputMapper.selectList(
                 Wrappers.<ProductionProductOutput>lambdaQuery()
@@ -773,6 +849,8 @@
             }
         }
 
+        qualityUnqualifiedMapper.delete(new LambdaQueryWrapper<QualityUnqualified>()
+                .eq(QualityUnqualified::getProductMainId, productionProductMain.getId()));
         qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
                 .eq(QualityInspect::getProductMainId, productionProductMain.getId())).forEach(q -> {
             qualityInspectParamMapper.delete(new LambdaQueryWrapper<QualityInspectParam>()
diff --git a/src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java b/src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java
index 0a51a19..fd336e9 100644
--- a/src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java
+++ b/src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java
@@ -29,4 +29,7 @@
     private String workOrderNo;
     private String purchaseContractNo;
 
+    // 鏉ユ簮鎶ュ伐鍗曞彿锛堢敓浜ф姤宸ヨ嚜鍔ㄧ敓鎴愮殑妫�楠屽崟锛屾煡璇�/鍥炴樉鐢級
+    private String reportNo;
+
 }
diff --git a/src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java b/src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
index e6cb765..99c4811 100644
--- a/src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
+++ b/src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
@@ -154,6 +154,14 @@
     @Schema(description = "鍏宠仈妫�娴媔d")
     private Long inspectId;
 
+    @Schema(description = "鍏宠仈鐢熶骇鎶ュ伐涓昏〃id锛堟姤宸ュ垎绫讳骇鐢熺殑杩斿伐/鎶ュ簾寰呭鐞嗗崟锛�")
+    @TableField("product_main_id")
+    private Long productMainId;
+
+    @Schema(description = "鏉ユ簮鎶ュ伐鍗曞彿锛堟煡璇㈠洖鏄剧敤锛�")
+    @TableField(exist = false)
+    private String reportNo;
+
 
     @Schema(description = "鏄惁涓嶅悎鏍煎鐞嗚嚜宸辨柊澧�")
     @TableField(exist = false)
diff --git a/src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java b/src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
index ffd12d9..c77e4bf 100644
--- a/src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
+++ b/src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -63,7 +63,17 @@
     @Transactional(rollbackFor = Exception.class)
     public int deal(QualityUnqualified qualityUnqualified) {
         QualityUnqualified unqualified = qualityUnqualifiedMapper.selectById(qualityUnqualified.getId());
-        QualityInspect qualityInspect = qualityInspectService.getById(unqualified.getInspectId());
+        QualityInspect qualityInspect = unqualified.getInspectId() == null ? null
+                : qualityInspectService.getById(unqualified.getInspectId());
+        // 鎶ュ伐璁℃暟鍖哄垎鐩存帴浜х敓鐨勮繑宸ュ緟澶勭悊鍗曪紙鏃犳楠屽崟锛屼粎鏈� productMainId锛夛紝鎸夋姤宸ュ鐞嗛棴鐜墽琛屻��
+        if (qualityInspect == null && unqualified.getProductMainId() != null) {
+            ProductionProductMain reportSourceMain = productionProductMainMapper.selectById(unqualified.getProductMainId());
+            if ("鎶ュ伐".equals(qualityUnqualified.getDealResult())) {
+                handleProductionReportResult(qualityUnqualified, unqualified, reportSourceMain);
+            }
+            qualityUnqualified.setInspectState(1);
+            return qualityUnqualifiedMapper.updateById(qualityUnqualified);
+        }
         String batchNo = qualityInspect == null ? null
                 : resolveProductionBatchNo(qualityInspect.getProductMainId(), qualityInspect.getId(), qualityInspect.getProductModelId());
         if (ObjectUtils.isNotNull(qualityInspect) && qualityInspect.getInspectType() != 0) {
diff --git a/src/main/resources/mapper/production/ProductionProductMainMapper.xml b/src/main/resources/mapper/production/ProductionProductMainMapper.xml
index cf3cf9a..1b5dae2 100644
--- a/src/main/resources/mapper/production/ProductionProductMainMapper.xml
+++ b/src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -51,6 +51,8 @@
                    end as workOrderStatus,
                su.nick_name as nickName,
                ifnull(ppo.quantity, 0) as quantity,
+               ifnull(ppo.rework_minor_qty, 0) as reworkMinorQty,
+               ifnull(ppo.rework_major_qty, 0) as reworkMajorQty,
                ifnull(ppo.scrap_qty, 0) as scrapQty,
                p.product_name as productName,
                ifnull(p.standard_quantity, 0) as productStandardQuantity,
@@ -108,6 +110,9 @@
             <if test="c.process != null and c.process != ''">
                 and poro.operation_name like concat('%', #{c.process}, '%')
             </if>
+            <if test="c.majorCategory != null and c.majorCategory != ''">
+                and ppm.major_category = #{c.majorCategory}
+            </if>
             <if test="c.schedulingDate != null">
                 and date(ppm.create_time) = #{c.schedulingDate}
             </if>
diff --git a/src/main/resources/mapper/quality/QualityInspectMapper.xml b/src/main/resources/mapper/quality/QualityInspectMapper.xml
index 7e0bd51..74b0ef3 100644
--- a/src/main/resources/mapper/quality/QualityInspectMapper.xml
+++ b/src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -10,7 +10,8 @@
             </when>
             <otherwise>
                 pot.work_order_no,
-                po_sales.sales_contract_no
+                po_sales.sales_contract_no,
+                ppm.product_no as report_no
             </otherwise>
         </choose>
         FROM
@@ -60,6 +61,9 @@
         <if test="qualityInspect.workOrderNo != null and qualityInspect.workOrderNo != '' ">
             AND pot.work_order_no like concat('%',#{qualityInspect.workOrderNo},'%')
         </if>
+        <if test="qualityInspect.reportNo != null and qualityInspect.reportNo != '' ">
+            AND ppm.product_no like concat('%',#{qualityInspect.reportNo},'%')
+        </if>
         <if test="qualityInspect.salesContractNo != null and qualityInspect.salesContractNo != '' ">
             AND po_sales.sales_contract_no like concat('%',#{qualityInspect.salesContractNo},'%')
         </if>
diff --git a/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml b/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
index b8015b5..01e3c28 100644
--- a/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
+++ b/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
@@ -20,6 +20,9 @@
         qu.photo_urls,
         qu.deal_name,
         qu.deal_time,
+        qu.inspect_id,
+        qu.product_main_id,
+        ppm.product_no as report_no,
         CASE
         WHEN qu.model = pm.id THEN pm.model
         ELSE qu.model
@@ -30,6 +33,7 @@
         END AS method
         FROM quality_unqualified qu
         LEFT JOIN product_model pm ON qu.model = pm.id
+        LEFT JOIN production_product_main ppm ON qu.product_main_id = ppm.id
         <where>
             <if test="qualityUnqualified.inspectType != null ">
                 AND inspect_type = #{qualityUnqualified.inspectType}
@@ -45,6 +49,9 @@
             </if>
             <if test="qualityUnqualified.entryDateEnd != null and qualityUnqualified.entryDateEnd != '' ">
                 AND  check_time &lt;= DATE_FORMAT(#{qualityUnqualified.entryDateEnd},'%Y-%m-%d')
+            </if>
+            <if test="qualityUnqualified.reportNo != null and qualityUnqualified.reportNo != '' ">
+                AND ppm.product_no like concat('%',#{qualityUnqualified.reportNo},'%')
             </if>
         </where>
         order by qu.create_time desc
@@ -84,6 +91,9 @@
             qu.photo_urls,
             qu.deal_name,
             qu.deal_time,
+            qu.inspect_id,
+            qu.product_main_id,
+            ppm2.product_no as report_no,
             CASE
                 WHEN qu.model = pm.id THEN pm.model
                 ELSE qu.model
@@ -94,6 +104,7 @@
                 END AS method
         FROM quality_unqualified qu
                  LEFT JOIN product_model pm ON qu.product_model_id = pm.id
+                 LEFT JOIN production_product_main ppm2 ON qu.product_main_id = ppm2.id
         where
             1=1
         and qu.id = #{id}

--
Gitblit v1.9.3