package cn.iocoder.yudao.module.mes.service.pro.bagcode; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceItemRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO; 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.mysql.pro.bagcode.MesProBagCodeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeReplaceMapper; 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.enums.pro.MesProBagCodeStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum; import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; 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.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 袋码赋码(一袋一码)Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class MesProBagCodeServiceImpl implements MesProBagCodeService { @Resource private MesProBagCodeMapper bagCodeMapper; @Resource private MesProBatchMapper batchMapper; @Resource private MesProBagCodeReplaceMapper replaceMapper; @Resource private MesProPalletMapper palletMapper; @Resource private MesProPalletService palletService; @Override @Transactional(rollbackFor = Exception.class) public String generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) { // 1. 校验批次存在且处于生产中状态 MesProBatchDO batch = batchMapper.selectByIdForUpdate(generateReqVO.getBatchId()); if (batch == null) { throw exception(PRO_BATCH_NOT_EXISTS); } if (!MesProBatchStatusEnum.PRODUCING.getStatus().equals(batch.getStatus())) { throw exception(PRO_BATCH_STATUS_NOT_GENERATE_CODE); } if (generateReqVO.getCount() == null || generateReqVO.getCount() <= 0) { throw exception(PRO_BAG_CODE_GENERATE_COUNT_INVALID); } // 2. 计算起始序号(包含已逻辑删除记录,保证序号不回退、码值唯一) int startNo = bagCodeMapper.selectMaxBagNo(batch.getId()) + 1; // 3. 批量生成袋码:批次号 + - + 4 位袋序号,同一次生成共享同一 UUID(数据包) String uuid = IdUtil.fastSimpleUUID(); List bagCodes = new ArrayList<>(generateReqVO.getCount()); for (int i = 0; i < generateReqVO.getCount(); i++) { int bagNo = startNo + i; bagCodes.add(new MesProBagCodeDO() .setUuid(uuid) .setCode(batch.getCode() + "-" + StrUtil.padPre(String.valueOf(bagNo), 4, '0')) .setBagNo(bagNo) .setBatchId(batch.getId()) .setBatchCode(batch.getCode()) .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus()) .setRemark(generateReqVO.getRemark())); } bagCodeMapper.insertBatch(bagCodes); // 4. 回写批次袋码数量 batchMapper.updateById(new MesProBatchDO().setId(batch.getId()) .setBagQuantity((batch.getBagQuantity() == null ? 0 : batch.getBagQuantity()) + generateReqVO.getCount())); return uuid; } @Override @Transactional(rollbackFor = Exception.class) public MesProBagCodeReplaceRespVO replaceBagCodes(MesProBagCodeReplaceReqVO replaceReqVO) { MesProBatchDO batch = batchMapper.selectByIdForUpdate(replaceReqVO.getBatchId()); if (batch == null) { throw exception(PRO_BATCH_NOT_EXISTS); } if (!MesProBatchStatusEnum.PRODUCING.getStatus().equals(batch.getStatus()) && !MesProBatchStatusEnum.FINISHED.getStatus().equals(batch.getStatus())) { throw exception(PRO_BAG_CODE_REPLACE_BATCH_STATUS_INVALID); } List oldIds = replaceReqVO.getOldIds().stream() .distinct().sorted().toList(); List oldBagCodes = bagCodeMapper.selectListByIdsForUpdate(oldIds); if (oldBagCodes.size() != oldIds.size()) { throw exception(PRO_BAG_CODE_NOT_EXISTS); } for (MesProBagCodeDO oldBagCode : oldBagCodes) { if (!batch.getId().equals(oldBagCode.getBatchId())) { throw exception(PRO_BAG_CODE_REPLACE_BATCH_MISMATCH); } if (!MesProBagCodeStatusEnum.GENERATED.getStatus().equals(oldBagCode.getStatus()) && !MesProBagCodeStatusEnum.PRINTED.getStatus().equals(oldBagCode.getStatus())) { throw exception(PRO_BAG_CODE_REPLACE_STATUS_INVALID); } if (replaceMapper.selectByOldBagCodeId(oldBagCode.getId()) != null) { throw exception(PRO_BAG_CODE_REPLACE_ALREADY_REPLACED); } } Map palletMap = new LinkedHashMap<>(); oldBagCodes.stream().map(MesProBagCodeDO::getPalletId).filter(id -> id != null) .distinct().sorted().forEach(palletId -> { MesProPalletDO pallet = palletMapper.selectByIdForUpdate(palletId); if (pallet == null || !MesProPalletStatusEnum.IN_USE.getStatus().equals(pallet.getStatus())) { throw exception(PRO_BAG_CODE_REPLACE_PALLET_INVALID); } palletMap.put(palletId, pallet); }); String replaceUuid = IdUtil.fastSimpleUUID(); LocalDateTime replaceTime = LocalDateTime.now(); int startNo = bagCodeMapper.selectMaxBagNo(batch.getId()) + 1; List newBagCodes = new ArrayList<>(oldBagCodes.size()); List replacements = new ArrayList<>(oldBagCodes.size()); List items = new ArrayList<>(oldBagCodes.size()); for (int i = 0; i < oldBagCodes.size(); i++) { MesProBagCodeDO oldBagCode = oldBagCodes.get(i); if (bagCodeMapper.cancelById(oldBagCode.getId(), oldBagCode.getStatus(), MesProBagCodeStatusEnum.CANCELED.getStatus(), replaceTime, replaceReqVO.getReason()) != 1) { throw exception(PRO_BAG_CODE_REPLACE_STATUS_INVALID); } int newBagNo = startNo + i; MesProPalletDO pallet = oldBagCode.getPalletId() == null ? null : palletMap.get(oldBagCode.getPalletId()); MesProBagCodeDO newBagCode = new MesProBagCodeDO() .setUuid(replaceUuid) .setCode(batch.getCode() + "-" + StrUtil.padPre(String.valueOf(newBagNo), 4, '0')) .setBagNo(newBagNo) .setBatchId(batch.getId()) .setBatchCode(batch.getCode()) .setPalletId(pallet == null ? null : pallet.getId()) .setPalletCode(pallet == null ? null : pallet.getCode()) .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus()) .setRemark(replaceReqVO.getReason()); newBagCodes.add(newBagCode); replacements.add(new MesProBagCodeReplaceDO() .setReplaceUuid(replaceUuid) .setBatchId(batch.getId()) .setBatchCode(batch.getCode()) .setOldBagCodeId(oldBagCode.getId()) .setOldCode(oldBagCode.getCode()) .setOldBagNo(oldBagCode.getBagNo()) .setOldStatus(oldBagCode.getStatus()) .setOldPalletId(oldBagCode.getPalletId()) .setOldPalletCode(oldBagCode.getPalletCode()) .setNewCode(newBagCode.getCode()) .setNewBagNo(newBagNo) .setReason(replaceReqVO.getReason()) .setReplaceTime(replaceTime)); items.add(new MesProBagCodeReplaceItemRespVO() .setOldBagCodeId(oldBagCode.getId()) .setOldCode(oldBagCode.getCode()) .setOldBagNo(oldBagCode.getBagNo()) .setOldStatus(oldBagCode.getStatus()) .setOldPalletId(oldBagCode.getPalletId()) .setOldPalletCode(oldBagCode.getPalletCode()) .setNewCode(newBagCode.getCode()) .setNewBagNo(newBagNo) .setNewPalletId(newBagCode.getPalletId()) .setNewPalletCode(newBagCode.getPalletCode()) .setReason(replaceReqVO.getReason()) .setReplaceTime(replaceTime)); } bagCodeMapper.insertBatch(newBagCodes); for (int i = 0; i < replacements.size(); i++) { replacements.get(i).setNewBagCodeId(newBagCodes.get(i).getId()); items.get(i).setNewBagCodeId(newBagCodes.get(i).getId()); } replaceMapper.insertBatch(replacements); batchMapper.updateById(new MesProBatchDO().setId(batch.getId()) .setBagQuantity(bagCodeMapper.selectEffectiveCountByBatchId(batch.getId()))); return new MesProBagCodeReplaceRespVO() .setReplaceUuid(replaceUuid) .setBatchId(batch.getId()) .setBatchCode(batch.getCode()) .setCount(newBagCodes.size()) .setItems(items); } @Override @Transactional(rollbackFor = Exception.class) public void deleteBagCodesByUuid(String uuid) { // 1. 校验生成组存在 List bagCodes = bagCodeMapper.selectListByUuid(uuid); if (CollUtil.isEmpty(bagCodes)) { throw exception(PRO_BAG_CODE_UUID_NOT_EXISTS); } // 2. 校验均为已生成状态且未绑定托盘 if (bagCodes.stream().anyMatch(bagCode -> !MesProBagCodeStatusEnum.GENERATED.getStatus().equals(bagCode.getStatus()))) { throw exception(PRO_BAG_CODE_STATUS_NOT_GENERATED); } if (bagCodes.stream().anyMatch(bagCode -> bagCode.getPalletId() != null)) { throw exception(PRO_BAG_CODE_BIND_PALLET_BOUND); } // 3. 删除生成组 bagCodeMapper.deleteByIds(bagCodes.stream().map(MesProBagCodeDO::getId).toList()); // 4. 回减批次袋码数量 MesProBatchDO batch = batchMapper.selectById(bagCodes.get(0).getBatchId()); if (batch != null && batch.getBagQuantity() != null) { batchMapper.updateById(new MesProBatchDO().setId(batch.getId()) .setBagQuantity(Math.max(0, batch.getBagQuantity() - bagCodes.size()))); } } @Override @Transactional(rollbackFor = Exception.class) public void bindPallet(MesProBagCodeBindReqVO bindReqVO) { // 1. 校验托盘存在 MesProPalletDO pallet = palletService.validatePalletExists(bindReqVO.getPalletId()); // 2. 校验袋码 List bagCodes = validateBagCodesForBind(bindReqVO.getIds(), pallet); // 3. 校验不超过托盘可装袋数 int currentCount = pallet.getBagCount() == null ? 0 : pallet.getBagCount(); if (pallet.getCapacity() != null && pallet.getCapacity() > 0 && currentCount + bagCodes.size() > pallet.getCapacity()) { throw exception(PRO_BAG_CODE_BIND_PALLET_OVER_CAPACITY); } // 4. 绑定袋码并累加托盘已装袋数 bagCodeMapper.bindPalletByIds(bindReqVO.getIds(), pallet.getId(), pallet.getCode()); palletService.updatePalletBagCount(pallet.getId(), currentCount + bagCodes.size()); } @Override @Transactional(rollbackFor = Exception.class) public void unbindPallet(MesProBagCodeBindReqVO bindReqVO) { // 1. 校验托盘存在 MesProPalletDO pallet = palletService.validatePalletExists(bindReqVO.getPalletId()); // 2. 校验袋码均绑定在当前托盘上 List bagCodes = bagCodeMapper.selectListByIds(bindReqVO.getIds()); if (bagCodes.size() != bindReqVO.getIds().size()) { throw exception(PRO_BAG_CODE_NOT_EXISTS); } if (bagCodes.stream().anyMatch(bagCode -> !pallet.getId().equals(bagCode.getPalletId()))) { throw exception(PRO_BAG_CODE_BIND_PALLET_MISMATCH); } // 3. 解绑袋码并回减托盘已装袋数 bagCodeMapper.unbindPalletByIds(bindReqVO.getIds()); int currentCount = pallet.getBagCount() == null ? 0 : pallet.getBagCount(); palletService.updatePalletBagCount(pallet.getId(), Math.max(0, currentCount - bagCodes.size())); } @Override public void markBagCodesPrinted(Collection ids) { if (CollUtil.isEmpty(ids)) { return; } bagCodeMapper.updateStatusByIds(ids, MesProBagCodeStatusEnum.GENERATED.getStatus(), MesProBagCodeStatusEnum.PRINTED.getStatus(), LocalDateTime.now()); } @Override public MesProBagCodeDO getBagCode(Long id) { return bagCodeMapper.selectById(id); } @Override public MesProBagCodeDO validateBagCodeExists(Long id) { MesProBagCodeDO bagCode = bagCodeMapper.selectById(id); if (bagCode == null) { throw exception(PRO_BAG_CODE_NOT_EXISTS); } return bagCode; } @Override public MesProBagCodeDO getBagCodeByCode(String code) { return bagCodeMapper.selectByCode(code); } @Override public MesProBagCodeReplaceDO getReplacementByOldBagCodeId(Long oldBagCodeId) { return replaceMapper.selectByOldBagCodeId(oldBagCodeId); } @Override public MesProBagCodeReplaceDO getReplacementByNewBagCodeId(Long newBagCodeId) { return replaceMapper.selectByNewBagCodeId(newBagCodeId); } @Override public PageResult getBagCodePage(MesProBagCodePageReqVO pageReqVO) { return bagCodeMapper.selectPage(pageReqVO); } @Override public List getBagCodeListByUuid(String uuid) { return bagCodeMapper.selectListByUuid(uuid); } @Override public List getBagCodeListByPalletId(Long palletId) { return bagCodeMapper.selectListByPalletId(palletId); } @Override public List getBagCodeListByBatchId(Long batchId) { return bagCodeMapper.selectListByBatchId(batchId); } // ==================== 私有方法 ==================== /** * 校验待绑定的袋码:存在、与托盘同批次、未绑定过托盘、状态有效(已生成/已导出印刷) */ private List validateBagCodesForBind(List ids, MesProPalletDO pallet) { List bagCodes = bagCodeMapper.selectListByIds(ids); if (bagCodes.size() != ids.size()) { throw exception(PRO_BAG_CODE_NOT_EXISTS); } for (MesProBagCodeDO bagCode : bagCodes) { if (!pallet.getBatchId().equals(bagCode.getBatchId())) { throw exception(PRO_BAG_CODE_BIND_PALLET_MISMATCH); } if (bagCode.getPalletId() != null) { throw exception(PRO_BAG_CODE_BIND_PALLET_BOUND); } if (MesProBagCodeStatusEnum.CANCELED.getStatus().equals(bagCode.getStatus())) { throw exception(PRO_BAG_CODE_STATUS_NOT_GENERATED); } } return bagCodes; } }