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.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.mysql.pro.bagcode.MesProBagCodeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; 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.List; 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 MesProPalletService palletService; @Override @Transactional(rollbackFor = Exception.class) public String generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) { // 1. 校验批次存在且处于生产中状态 MesProBatchDO batch = batchMapper.selectById(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 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 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; } }