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/pro/scan/MesProScanEventServiceImpl.java |  131 ++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 114 insertions(+), 17 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
index e2c841c..f9412cc 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
@@ -5,74 +5,170 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.scan.MesProScanEventMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.MesProScanEventStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 
+@Slf4j
 @Service
 @Validated
 public class MesProScanEventServiceImpl implements MesProScanEventService {
     @Resource private MesProScanEventMapper eventMapper;
     @Resource private MesProBagCodeMapper bagCodeMapper;
     @Resource private MesProBatchMapper batchMapper;
+    @Resource private MesProPalletMapper palletMapper;
     @Resource private MesWmTransactionService transactionService;
     @Resource private MesWmWarehouseService warehouseService;
+    @Resource private MesWmBatchService mesWmBatchService;
+
+    /** 鎵爜绫诲瀷锛氳鐮侊紙榛樿锛� */
+    private static final String SCAN_TYPE_BAG = "BAG";
+    /** 鎵爜绫诲瀷锛氭墭鐩樼爜锛堟寜鎵樼洏宸茶琚嬫暟鏁存墭鍏ュ簱锛� */
+    private static final String SCAN_TYPE_PALLET = "PALLET";
+    /** 鎵爜绫诲瀷锛氭壒娆$爜锛堟寜鎵规琚嬫暟鏁存壒鍏ュ簱锛� */
+    private static final String SCAN_TYPE_BATCH = "BATCH";
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public MesProScanEventDO createInbound(MesProScanEventCreateReqVO req) {
+        String scanType = (req.getScanType() == null || req.getScanType().isBlank()) ? SCAN_TYPE_BAG : req.getScanType();
+        if (!List.of(SCAN_TYPE_BAG, SCAN_TYPE_PALLET, SCAN_TYPE_BATCH).contains(scanType)) {
+            throw new ServiceException(400, "涓嶆敮鎸佺殑鎵爜绫诲瀷");
+        }
         MesProScanEventDO existing = eventMapper.selectByEventIdForUpdate(req.getEventId());
         if (existing != null) {
-            if (!req.getBagCode().equals(existing.getBagCode()) || !req.getDeviceCode().equals(existing.getDeviceCode())) {
-                return failed(existing, "MES_SCAN_EVENT_ID_CONFLICT", "浜嬩欢鍙峰凡瀛樺湪浣嗗唴瀹逛笉涓�鑷�");
+            boolean contentMatch = req.getBagCode().equals(existing.getBagCode())
+                    && Objects.equals(req.getDeviceCode(), existing.getDeviceCode())
+                    && Objects.equals(req.getLineCode(), existing.getLineCode())
+                    && Objects.equals(scanType, existing.getScanType())
+                    && Objects.equals(req.getLocationId(), existing.getLocationId())
+                    && Objects.equals(req.getAreaId(), existing.getAreaId())
+                    && (!SCAN_TYPE_BAG.equals(scanType) || (existing.getQuantity() != null
+                        && req.getQuantity().compareTo(existing.getQuantity()) == 0))
+                    && Objects.equals(String.valueOf(SecurityFrameworkUtils.getLoginUserId()), existing.getCreator());
+            if (!contentMatch) {
+                throw new ServiceException(409, "浜嬩欢鍙峰凡瀛樺湪浣嗗唴瀹规垨鎿嶄綔浜轰笉涓�鑷达紝璇锋牳瀹炲師鎿嶄綔");
             }
             return existing;
         }
         MesProScanEventDO event = new MesProScanEventDO();
-        event.setEventId(req.getEventId()).setBagCode(req.getBagCode()).setQuantity(req.getQuantity())
-                .setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode()).setRemark(req.getRemark())
+        event.setEventId(req.getEventId()).setBagCode(req.getBagCode()).setScanType(scanType)
+                .setQuantity(req.getQuantity()).setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode())
+                .setRemark(req.getRemark()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId())
                 .setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now());
         eventMapper.insert(event);
-        MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode());
-        if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "琚嬬爜涓嶅瓨鍦�");
-        event.setBagCodeId(bag.getId());
-        if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "琚嬬爜宸插叆搴�");
-        MesProBatchDO batch = batchMapper.selectByIdForUpdate(bag.getBatchId());
-        if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "鐢熶骇鎵规涓嶅瓨鍦�");
-        event.setBatchId(batch.getId()).setBatchCode(batch.getCode()).setItemId(batch.getItemId());
+        // 1. 鎸夋壂鐮佺被鍨嬭В鏋愭壂鐮佺洰鏍囦笌鍏ュ簱鏁伴噺锛堟墭鐩樻寜宸茶琚嬫暟銆佹壒娆℃寜鎵规琚嬫暟鏁存壒/鏁存墭鍏ュ簱锛�
+        Long targetId;
+        Long batchId;
+        BigDecimal quantity;
+        MesProBatchDO batch;
+        // 鍗曡鎵爜鍏ュ簱鏃朵紶閫掕鐮侊紝渚涘簱瀛樿鐮佹槑缁嗗湪鍏ュ簱绗竴鏃堕棿寤虹珛锛涙墭鐩�/鎵规鏁存壒鍏ュ簱涓烘壒閲忥紝涓嶉�愯寤烘槑缁�
+        Long bagCodeId = null;
+        String bagCode = null;
+        if (SCAN_TYPE_PALLET.equals(scanType)) {
+            MesProPalletDO pallet = palletMapper.selectByScanCodeForUpdate(req.getBagCode());
+            if (pallet == null) return failed(event, "MES_SCAN_PALLET_NOT_EXISTS", "鎵樼洏鐮佷笉瀛樺湪");
+            if (MesProPalletStatusEnum.CANCELED.getStatus().equals(pallet.getStatus()))
+                return failed(event, "MES_SCAN_PALLET_CANCELED", "鎵樼洏宸蹭綔搴燂紝涓嶅彲鍏ュ簱");
+            if (pallet.getInboundEventId() != null) return failed(event, "MES_SCAN_PALLET_ALREADY_INBOUND", "鎵樼洏宸插叆搴�");
+            batch = batchMapper.selectByIdForUpdate(pallet.getBatchId());
+            if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "鐢熶骇鎵规涓嶅瓨鍦�");
+            batchId = batch.getId();
+            targetId = pallet.getId();
+            quantity = BigDecimal.valueOf(bagCodeMapper.selectPendingInboundCountByPalletId(pallet.getId()));
+        } else if (SCAN_TYPE_BATCH.equals(scanType)) {
+            batch = batchMapper.selectByScanCodeForUpdate(req.getBagCode());
+            if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "鐢熶骇鎵规涓嶅瓨鍦�");
+            batchId = batch.getId();
+            targetId = batch.getId();
+            quantity = BigDecimal.valueOf(bagCodeMapper.selectPendingInboundCountByBatchId(batch.getId()));
+        } else {
+            MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(req.getBagCode());
+            if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "琚嬬爜涓嶅瓨鍦�");
+            if (MesProBagCodeStatusEnum.CANCELED.getStatus().equals(bag.getStatus()))
+                return failed(event, "MES_SCAN_BAG_CANCELED", "琚嬬爜宸蹭綔搴燂紝涓嶅彲鍏ュ簱");
+            event.setBagCodeId(bag.getId());
+            bagCodeId = bag.getId();
+            // 搴撳瓨娴佹按淇濇寔鍘熻拷婧爜涓鸿鑼冪爜锛屼笟鍔$爜浠呯敤浜庢煡鎵惧悓涓�琚嬶紝閬垮厤鍙岀爜鎷嗘垚涓や唤搴撳瓨銆�
+            bagCode = bag.getCode();
+            if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "琚嬬爜宸插叆搴�");
+            batch = batchMapper.selectByIdForUpdate(bag.getBatchId());
+            if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "鐢熶骇鎵规涓嶅瓨鍦�");
+            batchId = batch.getId();
+            targetId = bag.getId();
+            quantity = req.getQuantity();
+        }
+        if (MesProBatchStatusEnum.CANCELED.getStatus().equals(batch.getStatus()))
+            return failed(event, "MES_SCAN_BATCH_CANCELED", "鐢熶骇鎵规宸蹭綔搴燂紝涓嶅彲鍏ュ簱");
+        if (quantity.signum() <= 0)
+            return failed(event, "MES_SCAN_NO_PENDING_BAGS", "娌℃湁寰呭叆搴撶殑鏈夋晥琚嬬爜锛岃鍕块噸澶嶅叆搴�");
+        event.setBatchId(batchId).setBatchCode(batch.getCode()).setItemId(batch.getItemId()).setQuantity(quantity);
         MesWmWarehouseDO warehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
         if (warehouse == null) return failed(event, "MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS", "WIP铏氭嫙浠撲笉瀛樺湪");
         event.setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId());
         try {
+            // 鐢熶骇鎵规妗ユ帴浠撳簱鎵规锛氬簱瀛�/娴佹按缁熶竴鎸傚湪 mes_wm_batch 涓婏紝鎵爜鍏ユ殏瀛樺簱寰呰川妫�
+            MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch);
+            if (wmBatch == null) {
+                return failed(event, "MES_SCAN_WM_BATCH_NOT_EXISTS", "浠撳簱鎵规妗ユ帴澶辫触");
+            }
             Long txId = transactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                     .setType(MesWmTransactionTypeEnum.IN.getType()).setBizType(MesBizTypeConstants.WM_PRODUCT_PRODUCE)
-                    .setBizId(batch.getId()).setBizCode(batch.getCode()).setBizLineId(bag.getId())
-                    .setItemId(batch.getItemId()).setQuantity(req.getQuantity()).setBatchCode(batch.getCode())
+                    .setBizId(batchId).setBizCode(batch.getCode()).setBizLineId(targetId)
+                    .setItemId(batch.getItemId()).setQuantity(quantity)
+                    .setBatchId(wmBatch.getId()).setBatchCode(batch.getCode())
+                    .setBagCodeId(bagCodeId).setBagCode(bagCode)
                     .setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId())
-                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true));
+                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true)
+                    .setStockState(MesWmStockStateEnum.TEMP.getState()));
             event.setTransactionId(txId).setStatus(MesProScanEventStatusEnum.SUCCESS).setEventTime(LocalDateTime.now());
             eventMapper.updateById(event);
-            bagCodeMapper.markInbound(bag.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
-            batchMapper.markInbound(batch.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
+            // 2. 鍥炲啓鍏ュ簱鏍囪锛堟墭鐩�/鎵规鏁存墭/鏁存壒鍏ュ簱鏃跺悓姝ユ爣璁板叾涓嬫湭鍏ュ簱琚嬬爜锛屼繚鎸佺姸鎬佷竴鑷达級
+            if (SCAN_TYPE_PALLET.equals(scanType)) {
+                palletMapper.markInbound(targetId, event.getId());
+                bagCodeMapper.markInboundByPalletId(targetId, event.getId(), req.getLineCode());
+            } else if (SCAN_TYPE_BATCH.equals(scanType)) {
+                bagCodeMapper.markInboundByBatchId(targetId, event.getId(), req.getLineCode());
+            } else {
+                bagCodeMapper.markInbound(targetId, event.getId(), req.getDeviceCode(), req.getLineCode());
+            }
+            palletMapper.markCompletedInboundByBatchId(batchId, event.getId());
+            batchMapper.refreshInboundProgress(batchId, event.getId(), req.getDeviceCode(), req.getLineCode());
             return event;
         } catch (Exception ex) {
-            return failed(event, "MES_SCAN_TRANSACTION_FAILED", "搴撳瓨浜嬪姟澶勭悊澶辫触");
+            log.error("[createInbound] 搴撳瓨浜嬪姟澶勭悊澶辫触锛宔ventId={}, bagCode={}, batchId={}, itemId={}",
+                    req.getEventId(), req.getBagCode(), batchId, batch.getItemId(), ex);
+            throw ex;
         }
     }
 
@@ -86,6 +182,7 @@
     @Override public MesProScanEventDO getByEventId(String eventId) { return eventMapper.selectOne(MesProScanEventDO::getEventId, eventId); }
     @Override public PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO) { return eventMapper.selectPage(reqVO); }
     @Override public MesProScanEventDO getLatest(String deviceCode) { return eventMapper.selectLatest(deviceCode); }
+    @Override public List<MesProScanEventDO> getByBatchId(Long batchId) { return eventMapper.selectListByBatchId(batchId); }
     @Override public Map<String, Object> getDashboard() {
         Map<String, Object> result = new HashMap<>();
         result.put("latest", eventMapper.selectLatest(null));

--
Gitblit v1.9.3