From 704265308b1227dbada5a36b2027b588c062ded2 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 12 十月 2026 09:14:37 +0800
Subject: [PATCH] feat(mes): 新增业务码支持并优化追溯查询功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java |  109 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 105 insertions(+), 4 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 492d1d0..ada99e6 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
@@ -53,6 +53,8 @@
     @Resource private MesQcSeedQualityMapper qualityMapper;
     @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
     @Resource private MesProBagCodeMapper bagCodeMapper;
+    @Resource private cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper palletMapper;
+    @Resource private cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper batchMapper;
     @Resource private MesProBatchService proBatchService;
     @Resource private MesWmBatchMapper mesWmBatchMapper;
     @Resource private MesWmMaterialStockMapper materialStockMapper;
@@ -65,22 +67,120 @@
 
     @Override
     public PageResult<MesQcSeedQualityDO> getQualityPage(MesQcSeedQualityPageReqVO reqVO) {
+        if (StrUtil.isNotBlank(reqVO.getCode())) {
+            MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(reqVO.getCode());
+            if (bag != null) reqVO.setCode(bag.getCode());
+        }
         return qualityMapper.selectPage(reqVO);
+    }
+
+    @Override
+    public Map<String, Object> resolveScan(String code) {
+        MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(code);
+        if (bag == null) throw new IllegalArgumentException("鏈В鏋愬埌鏈夋晥琚嬬爜锛岃鎵弿琚嬬爜");
+        Map<String, Object> result = new HashMap<>();
+        result.put("code", bag.getCode());
+        result.put("businessCode", bag.getBusinessCode());
+        result.put("batchCode", bag.getBatchCode());
+        result.put("inbound", bag.getInboundEventId() != null);
+        result.put("canceled", Integer.valueOf(30).equals(bag.getStatus()));
+        MesQcSeedQualityDO quality = qualityMapper.selectByCode(bag.getCode());
+        result.put("qualityId", quality == null ? null : quality.getId());
+        result.put("result", quality == null ? null : quality.getResult());
+        return result;
+    }
+
+    private record Scope(String code, String businessCode, String batchCode, boolean canceled, List<MesProBagCodeDO> bags) { }
+
+    private Scope scope(String code, String type, boolean lock) {
+        if ("BAG".equals(type)) {
+            MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(code);
+            if (bag == null) throw new IllegalArgumentException("鏈壘鍒拌鐮侊紝璇锋牳瀵规壂鐮佺被鍨�");
+            return new Scope(bag.getCode(), bag.getBusinessCode(), bag.getBatchCode(), Integer.valueOf(30).equals(bag.getStatus()), List.of(bag));
+        }
+        if ("PALLET".equals(type)) {
+            var pallet = lock ? palletMapper.selectByScanCodeForUpdate(code) : palletMapper.selectByScanCode(code);
+            if (pallet == null) throw new IllegalArgumentException("鏈壘鍒版墭鐩樼爜锛岃鏍稿鎵爜绫诲瀷");
+            if (pallet.getBatchId() == null) throw new IllegalArgumentException("鎵樼洏灏氭湭缁戝畾鐢熶骇鎵规锛屾棤娉曡川妫�");
+            return new Scope(StrUtil.blankToDefault(pallet.getCode(), pallet.getTempCode()), pallet.getBusinessCode(), pallet.getBatchCode(),
+                    Integer.valueOf(20).equals(pallet.getStatus()), lock ? bagCodeMapper.selectListByPalletIdForUpdate(pallet.getId()) : bagCodeMapper.selectListByPalletId(pallet.getId()));
+        }
+        if ("BATCH".equals(type)) {
+            var batch = lock ? batchMapper.selectByScanCodeForUpdate(code) : batchMapper.selectByScanCode(code);
+            if (batch == null) throw new IllegalArgumentException("鏈壘鍒扮敓浜ф壒娆$爜锛岃鏍稿鎵爜绫诲瀷");
+            return new Scope(StrUtil.blankToDefault(batch.getCode(), batch.getTempCode()), batch.getBusinessCode(), batch.getCode(),
+                    Integer.valueOf(40).equals(batch.getStatus()), lock ? bagCodeMapper.selectListByBatchIdForUpdate(batch.getId()) : bagCodeMapper.selectListByBatchId(batch.getId()));
+        }
+        throw new IllegalArgumentException("涓嶆敮鎸佺殑璐ㄦ鎵爜绫诲瀷");
+    }
+
+    @Override
+    public Map<String, Object> resolveScan(String code, String scanType) {
+        Scope scope = scope(code, scanType, false);
+        List<MesProBagCodeDO> effective = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).toList();
+        Set<String> inspected = qualityMapper.selectListByCodes(effective.stream().map(MesProBagCodeDO::getCode).toList()).stream()
+                .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet());
+        Map<String, Object> result = new HashMap<>();
+        result.put("scanType", scanType); result.put("code", scope.code()); result.put("businessCode", scope.businessCode());
+        result.put("batchCode", scope.batchCode()); result.put("canceled", scope.canceled());
+        result.put("totalCount", effective.size());
+        result.put("inbound", !effective.isEmpty() && effective.stream().allMatch(b -> b.getInboundEventId() != null));
+        result.put("pendingBagIds", effective.stream().filter(b -> b.getInboundEventId() != null && !inspected.contains(b.getCode())).map(MesProBagCodeDO::getId).sorted().toList());
+        result.put("completedBagIds", effective.stream().filter(b -> inspected.contains(b.getCode())).map(MesProBagCodeDO::getId).sorted().toList());
+        if ("BAG".equals(scanType)) {
+            MesQcSeedQualityDO quality = qualityMapper.selectByCode(scope.code());
+            result.put("qualityId", quality == null ? null : quality.getId()); result.put("result", quality == null ? null : quality.getResult());
+        }
+        return result;
+    }
+
+    @Override @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> submitGroup(cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityGroupSubmitReqVO req) {
+        Scope scope = scope(req.getCode(), req.getScanType(), true);
+        if (scope.canceled()) throw new IllegalArgumentException("璐ㄦ瀵硅薄宸蹭綔搴�");
+        List<MesProBagCodeDO> effective = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).toList();
+        if (effective.isEmpty()) throw new IllegalArgumentException("娌℃湁鏈夋晥琚嬬爜");
+        List<MesProBagCodeDO> locked = bagCodeMapper.selectListByIdsForUpdate(effective.stream().map(MesProBagCodeDO::getId).toList());
+        if (locked.size() != effective.size() || locked.stream().anyMatch(b -> Integer.valueOf(30).equals(b.getStatus()) || b.getInboundEventId() == null))
+            throw new IllegalArgumentException("琚嬬爜宸插彉鍖栨垨灏氭湭鍏ㄩ儴鍏ュ簱锛岃閲嶆柊鎵弿鏍稿");
+        // 鍦ㄨ鐮侀攣涔嬪悗閲嶆柊鏍稿褰掑睘锛涘叾浠栫粦瀹氭搷浣滀笉寰楁敼鍙樺凡纭鐨勬楠岃寖鍥淬��
+        Scope current = scope(req.getCode(), req.getScanType(), false);
+        Set<Long> currentIds = current.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).map(MesProBagCodeDO::getId).collect(java.util.stream.Collectors.toSet());
+        if (!currentIds.equals(new HashSet<>(locked.stream().map(MesProBagCodeDO::getId).toList())))
+            throw new IllegalArgumentException("琚嬬爜褰掑睘宸插彉鍖栵紝璇烽噸鏂版壂鎻忔牳瀵�");
+        Set<String> inspected = qualityMapper.selectListByCodesForUpdate(locked.stream().map(MesProBagCodeDO::getCode).toList()).stream()
+                .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet());
+        List<MesProBagCodeDO> pending = locked.stream().filter(b -> !inspected.contains(b.getCode())).sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList();
+        List<Long> confirmed = req.getBagIds().stream().sorted().toList();
+        if (pending.isEmpty() || !confirmed.equals(pending.stream().map(MesProBagCodeDO::getId).toList()))
+            throw new IllegalArgumentException("寰呰川妫�娓呭崟宸插彉鍖栨垨宸插叏閮ㄨ川妫�锛岃閲嶆柊鎵弿锛岀姝㈤噸澶嶅鐞�");
+        for (MesProBagCodeDO bag : pending) {
+            MesQcSeedQualitySubmitReqVO single = new MesQcSeedQualitySubmitReqVO();
+            single.setCode(bag.getCode());single.setIndicators(req.getIndicators());single.setReason(req.getReason());
+            single.setQualifiedWarehouseId(req.getQualifiedWarehouseId());
+            // 澶栧眰浜嬪姟瑕嗙洊鍏ㄩ儴琚嬬爜锛涗换浣曚竴琚嬪簱瀛樻祦杞け璐ラ兘浼氬洖婊氭暣娆℃搷浣溿��
+            submit(single);
+        }
+        return Map.of("processedCount", pending.size());
     }
 
     @Override @Transactional(rollbackFor = Exception.class)
     public Long submit(MesQcSeedQualitySubmitReqVO reqVO) {
         if (reqVO.getIndicators().size() != 4) throw new IllegalArgumentException("蹇呴』鎻愪氦鍥涢」鎸囨爣");
-        if (qualityMapper.selectByCode(reqVO.getCode()) != null) throw new IllegalArgumentException("璇ヨ鐮佸凡璐ㄦ锛岀姝㈤噸澶嶆彁浜�");
         // 绉嶅瓙璐ㄦ蹇呴』浠ヨ鐮侊紙鍗曡锛夋彁浜わ紝鐗╂枡淇℃伅鐢辫鐮佽嚜鍔ㄨВ鏋愶紝鍓嶇涓嶅啀鍗曠嫭閫夋嫨鐗╂枡
-        MesProBagCodeDO bag = bagCodeMapper.selectByCode(reqVO.getCode());
+        MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(reqVO.getCode());
         if (bag == null) {
             throw new IllegalArgumentException("鏈В鏋愬埌鏈夋晥琚嬬爜锛岀瀛愯川妫�蹇呴』浠ヨ鐮佹彁浜�");
         }
-        MesProBatchDO batch = resolveBatch(reqVO.getCode(), reqVO.getBatchCode());
+        if (Integer.valueOf(30).equals(bag.getStatus()) || bag.getInboundEventId() == null)
+            throw new IllegalArgumentException("琚嬬爜宸蹭綔搴熸垨灏氭湭鍏ュ簱锛屾棤娉曡川妫�");
+        // 鍙岀爜閿佸畾鍚屼竴琚嬶紝骞朵互杩芥函鐮佽褰曠粨鏋滐紝闃叉鎹㈢爜鎴栧苟鍙戦噸澶嶆墸鍑忓簱瀛樸��
+        if (qualityMapper.selectByCodeForUpdate(bag.getCode()) != null) throw new IllegalArgumentException("璇ヨ鐮佸凡璐ㄦ锛岀姝㈤噸澶嶆彁浜�");
+        MesProBatchDO batch = bag.getBatchId() == null ? null : proBatchService.getBatch(bag.getBatchId());
         if (batch == null || batch.getItemId() == null) {
             throw new IllegalArgumentException("鏈牴鎹鐮佽В鏋愬埌鐗╂枡淇℃伅锛岃纭鎵爜鍐呭鎴栨壒娆″彿姝g‘");
         }
+        if (Integer.valueOf(40).equals(batch.getStatus())) throw new IllegalArgumentException("鐢熶骇鎵规宸蹭綔搴燂紝鏃犳硶璐ㄦ");
         // 琚嬬爜瀵瑰簲鎵规鐨勫簱瀛樺繀椤诲浜庢殏瀛樺簱锛堝緟璐ㄦ锛変腑锛屾墠鑳借繘琛岀瀛愯川妫�
         MesWmBatchDO wmBatch = mesWmBatchMapper.selectByCode(batch.getCode());
         if (wmBatch == null || materialStockMapper.selectTempStockCountByBatchId(wmBatch.getId()) == 0) {
@@ -110,7 +210,7 @@
         }
         Set<String> codes = new HashSet<>();
         boolean passed = true;
-        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(reqVO.getCode()).itemId(batch.getItemId())
+        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(bag.getCode()).itemId(batch.getItemId())
                 .batchCode(batch.getCode()).reason(reqVO.getReason()).result(0)
                 .inspectedAt(LocalDateTime.now()).build();
         qualityMapper.insert(quality);
@@ -121,6 +221,7 @@
                     .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);
         qualityMapper.updateById(quality);
         // 鎸夎鐮佺矑搴︽祦杞簱瀛橈細閿佸畾璇ヨ鍦ㄦ殏瀛樺簱涓殑娲诲姩鏄庣粏锛岀姝㈡寜鎵规浠绘剰鎵e噺

--
Gitblit v1.9.3