From 5072520172e8f45e3ccb8afc26cf0c8186ba4cdd Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 19 十月 2026 16:31:42 +0800
Subject: [PATCH] refactor(mes): 重构质量管理和批次绑定功能
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java | 150 ++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 140 insertions(+), 10 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 7b63e97..084b5a0 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,6 +7,10 @@
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.controller.admin.pro.bagcode.vo.MesProBagCodeBindBatchReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
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;
@@ -15,10 +19,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.pro.line.MesProLineService;
+import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService;
+import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
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;
@@ -60,6 +67,15 @@
@Resource
private MesProPalletService palletService;
+ @Resource
+ private MesProBagCodeService bagCodeService;
+ @Resource
+ private MesMdmItemService mdmItemService;
+ @Resource
+ private MdmUnitMeasureApi unitMeasureApi;
+
+ @Resource
+ private MesCodeRedisDAO codeRedisDao;
@Resource
private MesMdAutoCodeRecordService autoCodeRecordService;
@@ -76,19 +92,21 @@
// 1. 鏍¢獙鍏宠仈鏁版嵁锛堜骇绾裤�佺敓浜т汉鍛橈級
MesProLineDO line = lineService.validateLineExistsAndEnable(createReqVO.getLineId());
validateRelations(createReqVO);
+ createReqVO.setItemId(resolveBindingItem(null, createReqVO));
// 2. 鐢熸垚鎵规鍙凤紙缂栫爜瑙勫垯锛氬墠缂� + 鏃ユ湡 + 浜х嚎缂栫爜 + 娴佹按鍙凤級
String code = generateBatchCode(line.getCode());
// 3. 鎻掑叆鎵规锛堜骇绾跨紪鐮�/鍚嶇О鍐椾綑鍥炲~锛涘嬀閫夌粦瀹氭墭鐩樻椂鎵规鐩存帴杩涘叆鐢熶骇涓級
- boolean bindPallets = CollUtil.isNotEmpty(createReqVO.getPalletIds());
+ boolean hasBindings = hasBindings(createReqVO);
MesProBatchDO batch = BeanUtils.toBean(createReqVO, MesProBatchDO.class)
.setCode(code)
+ .setBusinessCode(codeRedisDao.generate())
.setLineCode(line.getCode())
.setLineName(line.getName())
.setBagQuantity(0)
.setPalletQuantity(0)
- .setStatus(bindPallets ? MesProBatchStatusEnum.PRODUCING.getStatus()
+ .setStatus(hasBindings ? MesProBatchStatusEnum.PRODUCING.getStatus()
: MesProBatchStatusEnum.DRAFT.getStatus());
batchMapper.insert(batch);
@@ -96,6 +114,7 @@
insertBatchWorkers(batch.getId(), createReqVO.getWorkers());
// 5. 缁戝畾鎵樼洏锛堢骇鑱旂敓鎴愭寮忔墭鐩樼爜骞跺綍鍏ユ墭鐩樹笅琚嬬爜锛屽け璐ユ暣浣撳洖婊氾級
bindPalletsToBatch(batch.getId(), createReqVO.getPalletIds());
+ bindTonBagsToBatch(batch.getId(), createReqVO.getTonBagIds());
// 6. 鐢熸垚鎵规瀹屾暣鎬ф憳瑕�
integrityService.recordBatchIntegrity(batch.getId());
return batch.getId();
@@ -104,12 +123,12 @@
@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())
@@ -136,14 +155,15 @@
// 2. 鏍¢獙鍏宠仈鏁版嵁骞剁敓鎴愭寮忔壒娆″彿
MesProLineDO line = lineService.validateLineExistsAndEnable(fillReqVO.getLineId());
validateRelations(fillReqVO);
+ fillReqVO.setItemId(resolveBindingItem(batch, fillReqVO));
String code = generateBatchCode(line.getCode());
// 3. 钀藉簱涓氬姟淇℃伅锛堜繚鐣欎复鏃舵壒娆$爜鐢ㄤ簬鍘嗗彶鎵爜杩芥函锛涗骇绾跨紪鐮�/鍚嶇О鍐椾綑鍥炲~锛涘嬀閫夌粦瀹氭墭鐩樻椂鎵规鐩存帴杩涘叆鐢熶骇涓級
- boolean bindPallets = CollUtil.isNotEmpty(fillReqVO.getPalletIds());
+ boolean hasBindings = hasBindings(fillReqVO);
MesProBatchDO updateObj = BeanUtils.toBean(fillReqVO, MesProBatchDO.class)
.setCode(code)
.setLineCode(line.getCode())
.setLineName(line.getName())
- .setStatus(bindPallets ? MesProBatchStatusEnum.PRODUCING.getStatus()
+ .setStatus(hasBindings ? MesProBatchStatusEnum.PRODUCING.getStatus()
: MesProBatchStatusEnum.DRAFT.getStatus());
batchMapper.updateById(updateObj);
// 4. 閲嶅缓鐢熶骇浜哄憳閰嶇疆
@@ -151,6 +171,7 @@
insertBatchWorkers(fillReqVO.getId(), fillReqVO.getWorkers());
// 5. 缁戝畾鎵樼洏锛堢骇鑱旂敓鎴愭寮忔墭鐩樼爜骞跺綍鍏ユ墭鐩樹笅琚嬬爜锛屽け璐ユ暣浣撳洖婊氾級
bindPalletsToBatch(fillReqVO.getId(), fillReqVO.getPalletIds());
+ bindTonBagsToBatch(fillReqVO.getId(), fillReqVO.getTonBagIds());
// 6. 鐢熸垚鎵规瀹屾暣鎬ф憳瑕侊紙琛ュ綍姝e紡淇℃伅鍚庤鐩栵級
integrityService.recordBatchIntegrity(fillReqVO.getId());
}
@@ -159,10 +180,12 @@
@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. 鏈敓鎴愭寮忔壒娆″彿鎴栦骇绾垮彉鍖栨椂閲嶆柊鐢熸垚鎵规鍙凤紙鎵规鍙峰寘鍚骇绾跨紪鐮佽绱狅級
boolean codeEmpty = StrUtil.isEmpty(oldBatch.getCode());
@@ -178,9 +201,9 @@
updateObj.setLineCode(line.getCode()).setLineName(line.getName());
}
// 4. 鏇存柊鎵规锛堝嬀閫夌粦瀹氭墭鐩樻椂鎵规鐩存帴杩涘叆鐢熶骇涓紱鍙栨秷鍕鹃�変笉鍥為��锛屽凡缁戝畾鎵樼洏涓嶅彲瑙g粦锛�
- boolean bindPallets = CollUtil.isNotEmpty(updateReqVO.getPalletIds());
+ boolean hasBindings = hasBindings(updateReqVO);
updateObj.setCode(code);
- if (bindPallets) {
+ if (hasBindings) {
updateObj.setStatus(MesProBatchStatusEnum.PRODUCING.getStatus());
}
batchMapper.updateById(updateObj);
@@ -190,6 +213,7 @@
insertBatchWorkers(updateReqVO.getId(), updateReqVO.getWorkers());
// 6. 缁戝畾鏂板鍕鹃�夌殑鎵樼洏锛堢骇鑱旂敓鎴愭寮忔墭鐩樼爜骞跺綍鍏ユ墭鐩樹笅琚嬬爜锛屽け璐ユ暣浣撳洖婊氾級
bindPalletsToBatch(updateReqVO.getId(), updateReqVO.getPalletIds());
+ bindTonBagsToBatch(updateReqVO.getId(), updateReqVO.getTonBagIds());
// 7. 鎽樿瑕嗙洊鑼冨洿鍚搧绉�/浜х嚎/鐢熶骇鏃ユ湡/璁″垝鏁伴噺锛岀紪杈戝悗閲嶆柊鐢熸垚閬垮厤璇姤绡℃敼
integrityService.recordBatchIntegrity(updateReqVO.getId());
}
@@ -243,6 +267,45 @@
@Override
public MesProBatchDO getBatch(Long id) {
return batchMapper.selectById(id);
+ }
+
+ @Override
+ public String getBindingMode(Long id) {
+ if (!palletMapper.selectListByBatchId(id).isEmpty()) return "pallet";
+ return bagCodeMapper.selectListByBatchId(id).stream()
+ .filter(bag -> bag.getPalletId() == null)
+ .map(MesProBagCodeDO::getItemId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .anyMatch(this::isTonItem)
+ ? "tonBag" : null;
+ }
+
+ @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
@@ -307,6 +370,73 @@
* 鎵规缁戝畾鎵樼洏锛堝彲閫夛級锛氬悓浜嬪姟鍐呭鐢ㄦ墭鐩樼粦瀹氭壒娆¢�昏緫锛�
* 鐢熸垚姝e紡鎵樼洏鐮佸苟绾ц仈褰曞叆鎵樼洏涓嬫湭缁戞壒娆$殑琚嬬爜锛屽け璐ユ暣浣撳洖婊�
*/
+ private Long resolveBindingItem(MesProBatchDO batch, MesProBatchSaveReqVO req) {
+ boolean palletsRequested = CollUtil.isNotEmpty(req.getPalletIds());
+ boolean tonsRequested = CollUtil.isNotEmpty(req.getTonBagIds());
+ if (palletsRequested && tonsRequested) throw bindingError("缁戝畾鎵樼洏鍜岀粦瀹氬惃琚嬪彧鑳介�夋嫨涓�绉�");
+ if (batch != null && (palletsRequested || tonsRequested)) {
+ String existingMode = getBindingMode(batch.getId());
+ if ((tonsRequested && "pallet".equals(existingMode))
+ || (palletsRequested && "tonBag".equals(existingMode)))
+ throw bindingError("璇ユ壒娆″凡缁戝畾鍙︿竴绉嶇爜锛岀粦瀹氭墭鐩樺拰缁戝畾鍚ㄨ鍙兘閫夋嫨涓�绉�");
+ }
+ 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 (tonsRequested) {
+ if (req.getTonBagIds().contains(null)) throw bindingError("璇烽�夋嫨鏈夋晥鐨勫惃琚�");
+ List<Long> ids = req.getTonBagIds().stream().distinct().sorted().toList();
+ List<MesProBagCodeDO> bags = bagCodeMapper.selectListByIdsForUpdate(ids);
+ if (bags.size() != ids.size()) throw bindingError("鎵�閫夊惃琚嬩笉瀛樺湪");
+ for (MesProBagCodeDO bag : bags) {
+ if (bag.getBatchId() != null || bag.getPalletId() != null || !isTonItem(bag.getItemId()))
+ throw bindingError("鍙兘缁戝畾鏈綍鍏ユ壒娆°�佹病鏈夋墭鐩樹笖璁¢噺鍗曚綅涓哄惃鐨勮鐮�");
+ itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, bag.getItemId());
+ }
+ }
+ // 璇锋眰閲岀殑鍝佺鍙兘涓庡凡缁戝畾鎴栧緟缁戝畾鐨勬墭鐩�/鍚ㄨ涓�鑷达紝涓嶈兘閫氳繃缂栬緫鏀瑰彉鎵规鍝佺銆�
+ if (req.getItemId() != null && !Objects.equals(req.getItemId(), itemId))
+ throw bindingError("鍝佺鐢辩粦瀹氭墭鐩樻垨鍚ㄨ甯﹀叆锛屼笉鍙慨鏀逛负鍏朵粬鍝佺");
+ return itemId;
+ }
+
+ private boolean hasBindings(MesProBatchSaveReqVO req) {
+ return CollUtil.isNotEmpty(req.getPalletIds()) || CollUtil.isNotEmpty(req.getTonBagIds());
+ }
+
+ private boolean isTonItem(Long itemId) {
+ if (itemId == null) return false;
+ MesMdmItemDO item = mdmItemService.getItem(itemId);
+ if (item == null || item.getUnitMeasureId() == null) return false;
+ var result = unitMeasureApi.getUnitMeasure(item.getUnitMeasureId());
+ return result != null && result.isSuccess() && result.getData() != null
+ && "鍚�".equals(StrUtil.trim(result.getData().getType()));
+ }
+
+ private cn.iocoder.yudao.framework.common.exception.ServiceException bindingError(String message) {
+ return new cn.iocoder.yudao.framework.common.exception.ServiceException(400, message);
+ }
+
+ private void bindTonBagsToBatch(Long batchId, List<Long> tonBagIds) {
+ if (CollUtil.isEmpty(tonBagIds)) return;
+ MesProBagCodeBindBatchReqVO req = new MesProBagCodeBindBatchReqVO();
+ req.setBatchId(batchId);
+ req.setIds(tonBagIds);
+ bagCodeService.bindBatch(req);
+ }
+
private void bindPalletsToBatch(Long batchId, List<Long> palletIds) {
if (CollUtil.isEmpty(palletIds)) {
return;
--
Gitblit v1.9.3