From c215f6c20445b973ff730dcdf4206426992da81d Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 19 十月 2026 11:30:57 +0800
Subject: [PATCH] feat(mes): 完善质检流程功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java |  164 ++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 146 insertions(+), 18 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
index ada99e6..2a3ba96 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO;
@@ -43,6 +44,7 @@
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_NOT_EXISTS;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_TYPE_NOT_QUALIFIED;
@@ -50,7 +52,10 @@
 @Service @Validated @Slf4j
 public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService {
     private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture");
+    private static final int WAIT_RETEST = 10, WAIT_PROCESS = 20, WAIT_COMPANY = 30,
+            RELEASED = 40, WAIT_PROCESS_FAIL = 50, WAIT_COMPANY_FAIL = 60, SCRAPPED = 70;
     @Resource private MesQcSeedQualityMapper qualityMapper;
+    @Resource private MesQcSeedQualityActionMapper actionMapper;
     @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
     @Resource private MesProBagCodeMapper bagCodeMapper;
     @Resource private cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper palletMapper;
@@ -197,33 +202,52 @@
         if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())) {
             throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED);
         }
-        // 瑙f瀽鍚堟牸搴撻粯璁ゅ簱鍖�/搴撲綅锛堝彇璇ヤ粨搴撲笅绗竴涓簱鍖恒�佸叾涓嬬涓�涓簱浣嶏紝鏈厤缃垯涓� null锛�
-        Long qualifiedLocationId = null;
-        Long qualifiedAreaId = null;
-        List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId());
-        if (CollUtil.isNotEmpty(locations)) {
-            qualifiedLocationId = locations.get(0).getId();
-            List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(qualifiedLocationId);
-            if (CollUtil.isNotEmpty(areas)) {
-                qualifiedAreaId = areas.get(0).getId();
-            }
-        }
         Set<String> codes = new HashSet<>();
         boolean passed = true;
         MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(bag.getCode()).itemId(batch.getItemId())
-                .batchCode(batch.getCode()).reason(reqVO.getReason()).result(0)
+                .batchCode(batch.getCode()).batchId(batch.getId()).reason(reqVO.getReason()).result(0)
+                .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(getLoginUserId())
                 .inspectedAt(LocalDateTime.now()).build();
         qualityMapper.insert(quality);
         for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
             if (!REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())) throw new IllegalArgumentException("鎸囨爣蹇呴』涓虹函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒嗕笖涓嶅彲閲嶅");
-            if (item.getMinValue().compareTo(item.getMaxValue()) > 0 || item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
+            if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺");
+            if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
             indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode())
                     .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
         }
         if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("鍥涢」鎸囨爣涓嶅畬鏁�");
         if (!passed && StrUtil.isBlank(reqVO.getReason())) throw new IllegalArgumentException("璐ㄦ涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
         quality.setResult(passed ? 1 : 2);
+        quality.setWorkflowStatus(passed ? WAIT_PROCESS : WAIT_RETEST);
         qualityMapper.updateById(quality);
+        recordAction(quality.getId(), "INITIAL", quality.getResult(), reqVO.getReason(), reqVO.getIndicators());
+        // 鍒濇浠呰褰曠粨璁猴紱琚嬩粛鐣欏湪鏆傚瓨搴擄紝寰呭妫�鍙婁袱绾у鎵瑰悗鍐嶆斁琛屾垨鎶ュ簾銆�
+        return quality.getId();
+    }
+
+    private void finalizeStock(MesQcSeedQualityDO quality, boolean passed) {
+        MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(quality.getCode());
+        if (bag == null || bag.getInboundEventId() == null || !Objects.equals(bag.getQcStatus(), MesProBagCodeQcStatusEnum.UNCHECKED.getStatus()))
+            throw new IllegalArgumentException("琚嬬爜鐘舵�佸凡鍙樺寲锛岀姝㈤噸澶嶅簱瀛樻祦杞�");
+        MesProBatchDO batch = proBatchService.getBatch(bag.getBatchId());
+        if (batch == null) throw new IllegalArgumentException("琚嬬爜鎵�灞炵敓浜ф壒娆′笉瀛樺湪");
+        MesWmBatchDO wmBatch = mesWmBatchMapper.selectByCode(batch.getCode());
+        if (wmBatch == null) throw new IllegalArgumentException("鏆傚瓨搴撳瓨鎵�灞炰粨搴撴壒娆′笉瀛樺湪");
+        MesWmWarehouseDO qualifiedWarehouse = warehouseService.getWarehouse(quality.getQualifiedWarehouseId());
+        if (passed && (qualifiedWarehouse == null || Boolean.TRUE.equals(qualifiedWarehouse.getFrozen())
+                || !MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())))
+            throw new IllegalArgumentException("鐩爣鍚堟牸搴撲笉瀛樺湪鎴栫被鍨嬪凡鍙樺寲");
+        Long qualifiedLocationId = null;
+        Long qualifiedAreaId = null;
+        if (passed) {
+            List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId());
+            if (CollUtil.isNotEmpty(locations)) {
+                qualifiedLocationId = locations.get(0).getId();
+                List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(qualifiedLocationId);
+                if (CollUtil.isNotEmpty(areas)) qualifiedAreaId = areas.get(0).getId();
+            }
+        }
         // 鎸夎鐮佺矑搴︽祦杞簱瀛橈細閿佸畾璇ヨ鍦ㄦ殏瀛樺簱涓殑娲诲姩鏄庣粏锛岀姝㈡寜鎵规浠绘剰鎵e噺
         MesWmMaterialStockBagDO stockBag = materialStockBagMapper.selectActiveByBagCode(bag.getCode());
         MesWmMaterialStockDO tempStock = resolveTempStock(bag, wmBatch, stockBag);
@@ -232,7 +256,7 @@
                 .setType(MesWmTransactionTypeEnum.OUT.getType())
                 .setBizType(MesBizTypeConstants.QC_SEED_QUALITY)
                 .setBizId(quality.getId())
-                .setBizCode(reqVO.getCode())
+                .setBizCode(quality.getCode())
                 .setBizLineId(quality.getId())
                 .setItemId(batch.getItemId())
                 .setQuantity(BigDecimal.ONE.negate())
@@ -255,7 +279,7 @@
                     .setType(MesWmTransactionTypeEnum.IN.getType())
                     .setBizType(MesBizTypeConstants.QC_SEED_QUALITY)
                     .setBizId(quality.getId())
-                    .setBizCode(reqVO.getCode())
+                    .setBizCode(quality.getCode())
                     .setBizLineId(quality.getId())
                     .setItemId(batch.getItemId())
                     .setQuantity(BigDecimal.ONE)
@@ -275,14 +299,113 @@
             // B2. 鍚屾杩佺Щ琚嬬爜鏄庣粏鍒板悎鏍煎簱瀛樿惤鐐�
             materialStockBagService.moveIn(qualInReq, qualifiedStock, qualInTxId);
             bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.QUALIFIED.getStatus());
-            log.info("[submit][璐ㄦ鍚堟牸锛岃鐮� {} 鐢辨殏瀛樺簱杞悎鏍煎簱 {}]", bag.getCode(), qualifiedWarehouse.getName());
+            log.info("[finalizeStock][瀹℃壒鏀捐锛岃鐮� {} 鐢辨殏瀛樺簱杞悎鏍煎簱 {}]", bag.getCode(), qualifiedWarehouse.getName());
         } else {
             // C. 璐ㄦ涓嶅悎鏍� 鈫� 璇ヨ鎶ュ簾锛涙殏瀛樺嚭搴撲簨鍔″凡鎵e噺鏆傚瓨搴撳瓨锛屾爣璁拌鐮佹姤搴�
             materialStockBagService.scrap(tempOutReq, tempOutTxId);
             bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.UNQUALIFIED.getStatus());
-            log.info("[submit][璐ㄦ涓嶅悎鏍硷紝琚嬬爜 {} 鎶ュ簾]", bag.getCode());
+            log.info("[finalizeStock][瀹℃壒鎶ュ簾锛岃鐮� {}]", bag.getCode());
         }
-        return quality.getId();
+    }
+
+    @Override @Transactional(rollbackFor = Exception.class)
+    public void retest(Long id, cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO req) {
+        MesQcSeedQualityDO quality = requireStatus(id, WAIT_RETEST);
+        if (!Objects.equals(quality.getCode(), req.getCode())) throw new IllegalArgumentException("澶嶆琚嬬爜涓庡師璐ㄦ璁板綍涓嶄竴鑷�");
+        boolean passed = validateIndicators(req.getIndicators());
+        if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("澶嶆涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
+        recordAction(id, "RETEST", passed ? 1 : 2, req.getReason(), req.getIndicators());
+        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).result(passed ? 1 : 2)
+                .reason(passed ? "" : req.getReason().trim()).retestResult(passed ? 1 : 2).retestedAt(LocalDateTime.now())
+                .inspectorId(getLoginUserId()).inspectedAt(LocalDateTime.now())
+                .workflowStatus(passed ? WAIT_PROCESS : WAIT_PROCESS_FAIL).build());
+        // 椤甸潰銆佸叕寮�杩芥函璇诲彇涓绘寚鏍囪〃锛涘悓姝ヤ负鏈�鏂颁竴娆℃祴鍊硷紝鍘嗘娴嬪�煎湪鎿嶄綔鐣欑棔琛ㄤ繚鐣欍��
+        Map<String, MesQcSeedQualityIndicatorDO> existing = new HashMap<>();
+        for (MesQcSeedQualityIndicatorDO indicator : indicatorMapper.selectListByQualityId(id))
+            existing.put(indicator.getIndicatorCode(), indicator);
+        for (MesQcSeedQualitySubmitReqVO.Indicator item : req.getIndicators()) {
+            MesQcSeedQualityIndicatorDO indicator = existing.get(item.getCode());
+            if (indicator == null) throw new IllegalArgumentException("璐ㄦ鎸囨爣璁板綍缂哄け");
+            indicatorMapper.updateById(MesQcSeedQualityIndicatorDO.builder().id(indicator.getId())
+                    .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
+        }
+    }
+
+    @Override @Transactional(rollbackFor = Exception.class)
+    public void processApprove(Long id, boolean approved, String opinion) {
+        MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
+        if (quality == null || !Set.of(WAIT_PROCESS, WAIT_PROCESS_FAIL).contains(quality.getWorkflowStatus()))
+            throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽姞宸ラ儴瀹℃壒");
+        if (StrUtil.isBlank(opinion)) throw new IllegalArgumentException("璇峰~鍐欏鎵规剰瑙�");
+        int next = approved ? (quality.getWorkflowStatus() == WAIT_PROCESS ? WAIT_COMPANY : WAIT_COMPANY_FAIL) : WAIT_RETEST;
+        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next)
+                .processApprovedAt(LocalDateTime.now()).build());
+        recordAction(id, approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), opinion, null);
+    }
+
+    @Override @Transactional(rollbackFor = Exception.class)
+    public void companyApprove(Long id, boolean approved, String opinion) {
+        MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
+        if (quality == null || !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus()))
+            throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽叕鍙歌川妫�瀹℃壒");
+        if (StrUtil.isBlank(opinion)) throw new IllegalArgumentException("璇峰~鍐欏鎵规剰瑙�");
+        if (approved) finalizeStock(quality, quality.getWorkflowStatus() == WAIT_COMPANY);
+        int next = approved ? (quality.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : SCRAPPED) : WAIT_RETEST;
+        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next)
+                .companyApprovedAt(LocalDateTime.now()).build());
+        recordAction(id, approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", quality.getResult(), opinion, null);
+    }
+
+    @Override @Transactional(rollbackFor = Exception.class)
+    public void processApproveBatch(List<Long> ids, boolean approved, String opinion) {
+        for (Long id : approvalIds(ids)) processApprove(id, approved, opinion);
+    }
+
+    @Override @Transactional(rollbackFor = Exception.class)
+    public void companyApproveBatch(List<Long> ids, boolean approved, String opinion) {
+        for (Long id : approvalIds(ids)) companyApprove(id, approved, opinion);
+    }
+
+    private List<Long> approvalIds(List<Long> ids) {
+        if (ids == null || ids.isEmpty() || ids.size() > 500 || ids.stream().anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("璇烽�夋嫨 1 鑷� 500 鏉¤川妫�璁板綍");
+        List<Long> sorted = ids.stream().distinct().sorted().toList();
+        if (sorted.size() != ids.size()) throw new IllegalArgumentException("璐ㄦ璁板綍涓嶈兘閲嶅閫夋嫨");
+        return sorted;
+    }
+
+    private MesQcSeedQualityDO requireStatus(Long id, int expected) {
+        MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
+        if (quality == null || !Objects.equals(quality.getWorkflowStatus(), expected))
+            throw new IllegalArgumentException("璐ㄦ璁板綍鐘舵�佸凡鍙樺寲锛岃鍒锋柊鍚庨噸璇�");
+        return quality;
+    }
+
+    private boolean validateIndicators(List<MesQcSeedQualitySubmitReqVO.Indicator> indicators) {
+        if (indicators == null || indicators.size() != REQUIRED.size()) throw new IllegalArgumentException("蹇呴』鎻愪氦鍥涢」鎸囨爣");
+        Set<String> codes = new HashSet<>();
+        boolean passed = true;
+        for (MesQcSeedQualitySubmitReqVO.Indicator item : indicators) {
+            if (item == null || !REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())
+                    || item.getValue() == null || item.getMinValue() == null || item.getMaxValue() == null)
+                throw new IllegalArgumentException("鎸囨爣蹇呴』涓虹函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒嗕笖涓嶅彲閲嶅");
+            if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺");
+            if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
+        }
+        return passed;
+    }
+
+    private void recordAction(Long id, String action, Integer result, String opinion,
+                              List<MesQcSeedQualitySubmitReqVO.Indicator> indicators) {
+        MesQcSeedQualityActionDO event = new MesQcSeedQualityActionDO();
+        event.setQualityId(id);
+        event.setAction(action);
+        event.setResult(result);
+        event.setOpinion(opinion);
+        event.setIndicatorsJson(indicators == null ? null : JSONUtil.toJsonStr(indicators));
+        event.setOperatorId(getLoginUserId());
+        event.setCreateTime(LocalDateTime.now());
+        actionMapper.insert(event);
     }
 
     /**
@@ -392,4 +515,9 @@
             return List.of();
         }
     }
+
+    @Override
+    public List<MesQcSeedQualityActionDO> getActions(Long qualityId) {
+        return actionMapper.selectByQualityId(qualityId);
+    }
 }

--
Gitblit v1.9.3