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/batch/MesProBatchServiceImpl.java |  149 ++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 120 insertions(+), 29 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java
index c0efa4f..bae3140 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java
@@ -7,7 +7,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPreGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletBindBatchReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchWorkerDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.line.MesProLineDO;
@@ -15,11 +15,13 @@
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchWorkerMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
+import cn.iocoder.yudao.module.mes.dal.redis.MesCodeRedisDAO;
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
-import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
 import cn.iocoder.yudao.module.mes.service.pro.line.MesProLineService;
+import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService;
+import cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -58,49 +60,62 @@
     private MesProPalletMapper palletMapper;
 
     @Resource
+    private MesProPalletService palletService;
+
+    @Resource
+    private MesCodeRedisDAO codeRedisDao;
+
+    @Resource
     private MesMdAutoCodeRecordService autoCodeRecordService;
     @Resource
     private AdminUserApi adminUserApi;
     @Resource
-    private MesMdmItemService mdmItemService;
-    @Resource
     private MesProLineService lineService;
+    @Resource
+    private MesTraceIntegrityService integrityService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createBatch(MesProBatchSaveReqVO createReqVO) {
-        // 1. 鏍¢獙鍏宠仈鏁版嵁锛堝搧绉嶃�佷骇绾裤�佺敓浜т汉鍛橈級
-        MesMdmItemDO item = mdmItemService.validateItemExists(createReqVO.getItemId());
+        // 1. 鏍¢獙鍏宠仈鏁版嵁锛堜骇绾裤�佺敓浜т汉鍛橈級
         MesProLineDO line = lineService.validateLineExistsAndEnable(createReqVO.getLineId());
         validateRelations(createReqVO);
+        createReqVO.setItemId(resolveBindingItem(null, createReqVO));
 
-        // 2. 鐢熸垚鎵规鍙凤紙缂栫爜瑙勫垯锛氬墠缂� + 鏃ユ湡 + 浜х嚎缂栫爜 + 鍝佺缂栫爜 + 娴佹按鍙凤級
-        String code = generateBatchCode(line.getCode(), item.getCode());
+        // 2. 鐢熸垚鎵规鍙凤紙缂栫爜瑙勫垯锛氬墠缂� + 鏃ユ湡 + 浜х嚎缂栫爜 + 娴佹按鍙凤級
+        String code = generateBatchCode(line.getCode());
 
-        // 3. 鎻掑叆鎵规锛堜骇绾跨紪鐮�/鍚嶇О鍐椾綑鍥炲~锛�
+        // 3. 鎻掑叆鎵规锛堜骇绾跨紪鐮�/鍚嶇О鍐椾綑鍥炲~锛涘嬀閫夌粦瀹氭墭鐩樻椂鎵规鐩存帴杩涘叆鐢熶骇涓級
+        boolean bindPallets = CollUtil.isNotEmpty(createReqVO.getPalletIds());
         MesProBatchDO batch = BeanUtils.toBean(createReqVO, MesProBatchDO.class)
                 .setCode(code)
+                .setBusinessCode(codeRedisDao.generate())
                 .setLineCode(line.getCode())
                 .setLineName(line.getName())
                 .setBagQuantity(0)
                 .setPalletQuantity(0)
-                .setStatus(MesProBatchStatusEnum.DRAFT.getStatus());
+                .setStatus(bindPallets ? MesProBatchStatusEnum.PRODUCING.getStatus()
+                        : MesProBatchStatusEnum.DRAFT.getStatus());
         batchMapper.insert(batch);
 
         // 4. 鎻掑叆鐢熶骇浜哄憳閰嶇疆
         insertBatchWorkers(batch.getId(), createReqVO.getWorkers());
+        // 5. 缁戝畾鎵樼洏锛堢骇鑱旂敓鎴愭寮忔墭鐩樼爜骞跺綍鍏ユ墭鐩樹笅琚嬬爜锛屽け璐ユ暣浣撳洖婊氾級
+        bindPalletsToBatch(batch.getId(), createReqVO.getPalletIds());
+        // 6. 鐢熸垚鎵规瀹屾暣鎬ф憳瑕�
+        integrityService.recordBatchIntegrity(batch.getId());
         return batch.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public List<MesProBatchDO> preGenerateBatch(MesProBatchPreGenerateReqVO reqVO) {
-        // 1. 鐢熸垚涓存椂鎵规鐮侊紙鍏ㄥ眬 8 浣嶅簭鍙凤紝鏍煎紡 PB-00000001锛�
-        int startSeq = batchMapper.selectMaxTempCodeSeq() + 1;
+        // 1. 閫氳繃缁熶竴缂栫爜鐢熸垚鍣ㄥ垎閰嶄复鏃剁爜鍙婁笟鍔$爜锛屼笉鍐嶈В鏋愬巻鍙蹭复鏃剁爜搴忓彿銆�
         List<MesProBatchDO> list = new ArrayList<>(reqVO.getCount());
         for (int i = 0; i < reqVO.getCount(); i++) {
             list.add(new MesProBatchDO()
-                    .setTempCode("PB-" + StrUtil.padPre(String.valueOf(startSeq + i), 8, '0'))
+                    .setTempCode(codeRedisDao.generate())
+                    .setBusinessCode(codeRedisDao.generate())
                     .setBagQuantity(0)
                     .setPalletQuantity(0)
                     .setStatus(MesProBatchStatusEnum.DRAFT.getStatus())
@@ -125,53 +140,67 @@
             throw exception(PRO_BATCH_ALREADY_FILLED);
         }
         // 2. 鏍¢獙鍏宠仈鏁版嵁骞剁敓鎴愭寮忔壒娆″彿
-        MesMdmItemDO item = mdmItemService.validateItemExists(fillReqVO.getItemId());
         MesProLineDO line = lineService.validateLineExistsAndEnable(fillReqVO.getLineId());
         validateRelations(fillReqVO);
-        String code = generateBatchCode(line.getCode(), item.getCode());
-        // 3. 钀藉簱涓氬姟淇℃伅锛堜繚鐣欎复鏃舵壒娆$爜鐢ㄤ簬鍘嗗彶鎵爜杩芥函锛涗骇绾跨紪鐮�/鍚嶇О鍐椾綑鍥炲~锛�
+        fillReqVO.setItemId(resolveBindingItem(batch, fillReqVO));
+        String code = generateBatchCode(line.getCode());
+        // 3. 钀藉簱涓氬姟淇℃伅锛堜繚鐣欎复鏃舵壒娆$爜鐢ㄤ簬鍘嗗彶鎵爜杩芥函锛涗骇绾跨紪鐮�/鍚嶇О鍐椾綑鍥炲~锛涘嬀閫夌粦瀹氭墭鐩樻椂鎵规鐩存帴杩涘叆鐢熶骇涓級
+        boolean bindPallets = CollUtil.isNotEmpty(fillReqVO.getPalletIds());
         MesProBatchDO updateObj = BeanUtils.toBean(fillReqVO, MesProBatchDO.class)
                 .setCode(code)
                 .setLineCode(line.getCode())
                 .setLineName(line.getName())
-                .setStatus(MesProBatchStatusEnum.DRAFT.getStatus());
+                .setStatus(bindPallets ? MesProBatchStatusEnum.PRODUCING.getStatus()
+                        : MesProBatchStatusEnum.DRAFT.getStatus());
         batchMapper.updateById(updateObj);
         // 4. 閲嶅缓鐢熶骇浜哄憳閰嶇疆
         batchWorkerMapper.deleteByBatchId(fillReqVO.getId());
         insertBatchWorkers(fillReqVO.getId(), fillReqVO.getWorkers());
+        // 5. 缁戝畾鎵樼洏锛堢骇鑱旂敓鎴愭寮忔墭鐩樼爜骞跺綍鍏ユ墭鐩樹笅琚嬬爜锛屽け璐ユ暣浣撳洖婊氾級
+        bindPalletsToBatch(fillReqVO.getId(), fillReqVO.getPalletIds());
+        // 6. 鐢熸垚鎵规瀹屾暣鎬ф憳瑕侊紙琛ュ綍姝e紡淇℃伅鍚庤鐩栵級
+        integrityService.recordBatchIntegrity(fillReqVO.getId());
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateBatch(MesProBatchSaveReqVO updateReqVO) {
         // 1. 鏍¢獙瀛樺湪 + 鐘舵�侊紙鑽夌/鐢熶骇涓墠鍏佽淇敼锛�
-        MesProBatchDO oldBatch = validateBatchExists(updateReqVO.getId());
+        MesProBatchDO oldBatch = batchMapper.selectByIdForUpdate(updateReqVO.getId());
+        if (oldBatch == null) throw exception(PRO_BATCH_NOT_EXISTS);
         validateBatchEditable(oldBatch);
         // 2. 鏍¢獙鍏宠仈鏁版嵁锛堢敓浜т汉鍛橈級
         validateRelations(updateReqVO);
+        updateReqVO.setItemId(resolveBindingItem(oldBatch, updateReqVO));
 
-        // 3. 鏈敓鎴愭寮忔壒娆″彿鎴栧搧绉�/浜х嚎鍙樺寲鏃堕噸鏂扮敓鎴愭壒娆″彿锛堟壒娆″彿鍖呭惈浜х嚎缂栫爜 + 鍝佺缂栫爜瑕佺礌锛�
+        // 3. 鏈敓鎴愭寮忔壒娆″彿鎴栦骇绾垮彉鍖栨椂閲嶆柊鐢熸垚鎵规鍙凤紙鎵规鍙峰寘鍚骇绾跨紪鐮佽绱狅級
         boolean codeEmpty = StrUtil.isEmpty(oldBatch.getCode());
-        boolean itemChanged = !Objects.equals(oldBatch.getItemId(), updateReqVO.getItemId());
         boolean lineChanged = !Objects.equals(oldBatch.getLineId(), updateReqVO.getLineId());
         String code = oldBatch.getCode();
         MesProBatchDO updateObj = BeanUtils.toBean(updateReqVO, MesProBatchDO.class);
-        if (codeEmpty || itemChanged || lineChanged) {
-            MesMdmItemDO item = mdmItemService.validateItemExists(updateReqVO.getItemId());
+        if (codeEmpty || lineChanged) {
             // 鍙樻洿浜х嚎鏃讹紝鐩爣浜х嚎蹇呴』鍚敤锛涙湭鍙樻洿鍒欎粎鏍¢獙瀛樺湪锛堥伩鍏嶅仠鐢ㄤ骇绾跨殑鍘嗗彶鎵规鏃犳硶缂栬緫鍏朵粬瀛楁锛�
             MesProLineDO line = (lineChanged || codeEmpty)
                     ? lineService.validateLineExistsAndEnable(updateReqVO.getLineId())
                     : lineService.validateLineExists(updateReqVO.getLineId());
-            code = generateBatchCode(line.getCode(), item.getCode());
+            code = generateBatchCode(line.getCode());
             updateObj.setLineCode(line.getCode()).setLineName(line.getName());
         }
-        // 4. 鏇存柊鎵规
+        // 4. 鏇存柊鎵规锛堝嬀閫夌粦瀹氭墭鐩樻椂鎵规鐩存帴杩涘叆鐢熶骇涓紱鍙栨秷鍕鹃�変笉鍥為��锛屽凡缁戝畾鎵樼洏涓嶅彲瑙g粦锛�
+        boolean bindPallets = CollUtil.isNotEmpty(updateReqVO.getPalletIds());
         updateObj.setCode(code);
+        if (bindPallets) {
+            updateObj.setStatus(MesProBatchStatusEnum.PRODUCING.getStatus());
+        }
         batchMapper.updateById(updateObj);
 
         // 5. 閲嶅缓鐢熶骇浜哄憳閰嶇疆
         batchWorkerMapper.deleteByBatchId(updateReqVO.getId());
         insertBatchWorkers(updateReqVO.getId(), updateReqVO.getWorkers());
+        // 6. 缁戝畾鏂板鍕鹃�夌殑鎵樼洏锛堢骇鑱旂敓鎴愭寮忔墭鐩樼爜骞跺綍鍏ユ墭鐩樹笅琚嬬爜锛屽け璐ユ暣浣撳洖婊氾級
+        bindPalletsToBatch(updateReqVO.getId(), updateReqVO.getPalletIds());
+        // 7. 鎽樿瑕嗙洊鑼冨洿鍚搧绉�/浜х嚎/鐢熶骇鏃ユ湡/璁″垝鏁伴噺锛岀紪杈戝悗閲嶆柊鐢熸垚閬垮厤璇姤绡℃敼
+        integrityService.recordBatchIntegrity(updateReqVO.getId());
     }
 
     @Override
@@ -226,6 +255,33 @@
     }
 
     @Override
+    public MesProBatchDO getBatchByScanCode(String code) {
+        return batchMapper.selectByScanCode(code);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int generateBusinessCodes(java.util.List<Long> ids) {
+        if (ids == null || ids.isEmpty() || ids.size() > 200 || ids.stream().anyMatch(java.util.Objects::isNull))
+            throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "璇烽�夋嫨 1 鑷� 200 鏉¤褰�");
+        java.util.List<MesProBatchDO> records = new java.util.ArrayList<>();
+        for (Long id : ids.stream().distinct().sorted().toList()) {
+            MesProBatchDO record = batchMapper.selectByIdForUpdate(id);
+            if (record == null) throw exception(PRO_BATCH_NOT_EXISTS);
+            if (MesProBatchStatusEnum.CANCELED.getStatus().equals(record.getStatus()))
+                throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "宸蹭綔搴熻褰曚笉鍙ˉ鐢熸垚涓氬姟鐮�");
+            records.add(record);
+        }
+        int count = 0;
+        for (MesProBatchDO record : records) {
+            if (StrUtil.isNotBlank(record.getBusinessCode())) continue;
+            batchMapper.updateById(new MesProBatchDO().setId(record.getId()).setBusinessCode(codeRedisDao.generate()));
+            count++;
+        }
+        return count;
+    }
+
+    @Override
     public MesProBatchDO getBatchByCode(String code) {
         MesProBatchDO batch = batchMapper.selectByCode(code);
         if (batch == null) {
@@ -274,7 +330,7 @@
     /**
      * 鏍¢獙鍏宠仈鏁版嵁锛堢敓浜т汉鍛橈細璐d换浜� + 鐢熶骇浜哄憳閰嶇疆锛�
      * <p>
-     * 鍝佺銆佷骇绾跨殑鏍¢獙鍦ㄥ垱寤�/鏇存柊娴佺▼涓唴鑱旀墽琛岋紙闇�鍙栫紪鐮佸弬涓庢壒娆″彿鐢熸垚锛�
+     * 浜х嚎鐨勬牎楠屽湪鍒涘缓/鏇存柊娴佺▼涓唴鑱旀墽琛岋紙闇�鍙栫紪鐮佸弬涓庢壒娆″彿鐢熸垚锛�
      */
     private void validateRelations(MesProBatchSaveReqVO reqVO) {
         Set<Long> userIds = new HashSet<>();
@@ -284,16 +340,51 @@
     }
 
     /**
+     * 鎵规缁戝畾鎵樼洏锛堝彲閫夛級锛氬悓浜嬪姟鍐呭鐢ㄦ墭鐩樼粦瀹氭壒娆¢�昏緫锛�
+     * 鐢熸垚姝e紡鎵樼洏鐮佸苟绾ц仈褰曞叆鎵樼洏涓嬫湭缁戞壒娆$殑琚嬬爜锛屽け璐ユ暣浣撳洖婊�
+     */
+    private Long resolveBindingItem(MesProBatchDO batch, MesProBatchSaveReqVO req) {
+        Long itemId = batch == null ? null : batch.getItemId();
+        if (batch != null) {
+            for (Long existing : bagCodeMapper.selectDistinctItemIdsByBatchId(batch.getId()))
+                itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, existing);
+            for (var pallet : palletMapper.selectListByBatchId(batch.getId()))
+                itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId,
+                        cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.resolve(pallet, bagCodeMapper.selectListByPalletId(pallet.getId())));
+        }
+        if (CollUtil.isNotEmpty(req.getPalletIds())) for (Long id : req.getPalletIds().stream().distinct().sorted().toList()) {
+            var pallet = palletMapper.selectByIdForUpdate(id);
+            if (pallet == null) throw exception(PRO_PALLET_NOT_EXISTS);
+            Long candidate = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.resolve(pallet, bagCodeMapper.selectListByPalletId(id));
+            if (candidate == null) throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "鎵樼洏缂哄皯鍝佺锛岃鍏堢粦瀹氬悓鍝佺琚嬬爜");
+            itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, candidate);
+        }
+        // 璇锋眰閲岀殑鍝佺鍙兘涓庡凡缁戝畾/寰呯粦瀹氭墭鐩樹竴鑷达紝涓嶈兘閫氳繃缂栬緫鏀瑰彉鎵规鍝佺銆�
+        if (req.getItemId() != null && !Objects.equals(req.getItemId(), itemId))
+            throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "鍝佺鐢辩粦瀹氭墭鐩樺甫鍏ワ紝涓嶅彲淇敼涓哄叾浠栧搧绉�");
+        return itemId;
+    }
+
+    private void bindPalletsToBatch(Long batchId, List<Long> palletIds) {
+        if (CollUtil.isEmpty(palletIds)) {
+            return;
+        }
+        MesProPalletBindBatchReqVO bindReqVO = new MesProPalletBindBatchReqVO();
+        bindReqVO.setBatchId(batchId);
+        bindReqVO.setIds(palletIds);
+        palletService.bindBatch(bindReqVO);
+    }
+
+    /**
      * 鐢熸垚鎵规鍙�
      * <p>
-     * 缂栫爜瑙勫垯锛氬浐瀹氬墠缂�(PC) + 鏃ユ湡(yyyyMMdd) + 杈撳叆瀛楃(浜х嚎缂栫爜 + 鍝佺缂栫爜) + 娴佹按鍙�(鎸夋棩寰幆)
+     * 缂栫爜瑙勫垯锛氬浐瀹氬墠缂�(PC) + 鏃ユ湡(yyyyMMdd) + 杈撳叆瀛楃(浜х嚎缂栫爜) + 娴佹按鍙�(鎸夋棩寰幆)
      *
      * @param lineCode 浜х嚎缂栫爜
-     * @param itemCode 鍝佺锛堢墿鏂欙級缂栫爜
      * @return 鎵规鍙�
      */
-    private String generateBatchCode(String lineCode, String itemCode) {
-        String inputChar = StrUtil.emptyToDefault(lineCode, "") + StrUtil.emptyToDefault(itemCode, "");
+    private String generateBatchCode(String lineCode) {
+        String inputChar = StrUtil.emptyToDefault(lineCode, "");
         return autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_BATCH_CODE.getCode(), inputChar);
     }
 

--
Gitblit v1.9.3