package cn.iocoder.yudao.module.mes.service.pro.batch; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPageReqVO; 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.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; 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.batch.MesProBatchWorkerMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper; 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.system.api.user.AdminUserApi; 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.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 生产批次 Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class MesProBatchServiceImpl implements MesProBatchService { @Resource private MesProBatchMapper batchMapper; @Resource private MesProBatchWorkerMapper batchWorkerMapper; @Resource private MesProBagCodeMapper bagCodeMapper; @Resource private MesProPalletMapper palletMapper; @Resource private MesMdAutoCodeRecordService autoCodeRecordService; @Resource private AdminUserApi adminUserApi; @Resource private MesMdmItemService mdmItemService; @Resource private MesProLineService lineService; @Override @Transactional(rollbackFor = Exception.class) public Long createBatch(MesProBatchSaveReqVO createReqVO) { // 1. 校验关联数据(品种、产线、生产人员) MesMdmItemDO item = mdmItemService.validateItemExists(createReqVO.getItemId()); MesProLineDO line = lineService.validateLineExistsAndEnable(createReqVO.getLineId()); validateRelations(createReqVO); // 2. 生成批次号(编码规则:前缀 + 日期 + 产线编码 + 品种编码 + 流水号) String code = generateBatchCode(line.getCode(), item.getCode()); // 3. 插入批次(产线编码/名称冗余回填) MesProBatchDO batch = BeanUtils.toBean(createReqVO, MesProBatchDO.class) .setCode(code) .setLineCode(line.getCode()) .setLineName(line.getName()) .setBagQuantity(0) .setPalletQuantity(0) .setStatus(MesProBatchStatusEnum.DRAFT.getStatus()); batchMapper.insert(batch); // 4. 插入生产人员配置 insertBatchWorkers(batch.getId(), createReqVO.getWorkers()); return batch.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateBatch(MesProBatchSaveReqVO updateReqVO) { // 1. 校验存在 + 状态(草稿/生产中才允许修改) MesProBatchDO oldBatch = validateBatchExists(updateReqVO.getId()); validateBatchEditable(oldBatch); // 2. 校验关联数据(生产人员) validateRelations(updateReqVO); // 3. 品种/产线变化时重新生成批次号(批次号包含产线编码 + 品种编码要素) 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 (itemChanged || lineChanged) { MesMdmItemDO item = mdmItemService.validateItemExists(updateReqVO.getItemId()); // 变更产线时,目标产线必须启用;未变更则仅校验存在(避免停用产线的历史批次无法编辑其他字段) MesProLineDO line = lineChanged ? lineService.validateLineExistsAndEnable(updateReqVO.getLineId()) : lineService.validateLineExists(updateReqVO.getLineId()); code = generateBatchCode(line.getCode(), item.getCode()); updateObj.setLineCode(line.getCode()).setLineName(line.getName()); } // 4. 更新批次 updateObj.setCode(code); batchMapper.updateById(updateObj); // 5. 重建生产人员配置 batchWorkerMapper.deleteByBatchId(updateReqVO.getId()); insertBatchWorkers(updateReqVO.getId(), updateReqVO.getWorkers()); } @Override @Transactional(rollbackFor = Exception.class) public void deleteBatch(Long id) { // 1. 校验存在 MesProBatchDO batch = validateBatchExists(id); // 2. 校验状态:仅草稿、已作废可删除 if (!MesProBatchStatusEnum.DRAFT.getStatus().equals(batch.getStatus()) && !MesProBatchStatusEnum.CANCELED.getStatus().equals(batch.getStatus())) { throw exception(PRO_BATCH_STATUS_NOT_ALLOW_DELETE); } // 3. 校验未生成袋码、托盘码 if (bagCodeMapper.selectCountByBatchId(id) > 0 || palletMapper.selectListByBatchId(id).size() > 0) { throw exception(PRO_BATCH_HAS_CODE_NOT_DELETE); } // 4. 删除批次与人员配置 batchMapper.deleteById(id); batchWorkerMapper.deleteByBatchId(id); } @Override public void updateBatchStatus(Long id, Integer status) { // 1. 校验存在 MesProBatchDO batch = validateBatchExists(id); // 2. 校验状态流转合法性 Integer oldStatus = batch.getStatus(); boolean valid = false; if (MesProBatchStatusEnum.DRAFT.getStatus().equals(oldStatus)) { valid = MesProBatchStatusEnum.PRODUCING.getStatus().equals(status) || MesProBatchStatusEnum.CANCELED.getStatus().equals(status); } else if (MesProBatchStatusEnum.PRODUCING.getStatus().equals(oldStatus)) { valid = MesProBatchStatusEnum.FINISHED.getStatus().equals(status) || MesProBatchStatusEnum.CANCELED.getStatus().equals(status); } if (!valid) { throw exception(PRO_BATCH_STATUS_INVALID); } // 3. 更新状态 batchMapper.updateById(new MesProBatchDO().setId(id).setStatus(status)); } @Override public MesProBatchDO getBatch(Long id) { return batchMapper.selectById(id); } @Override public MesProBatchDO getBatchByCode(String code) { return batchMapper.selectByCode(code); } @Override public MesProBatchDO validateBatchExists(Long id) { MesProBatchDO batch = batchMapper.selectById(id); if (batch == null) { throw exception(PRO_BATCH_NOT_EXISTS); } return batch; } @Override public PageResult getBatchPage(MesProBatchPageReqVO pageReqVO) { return batchMapper.selectPage(pageReqVO); } @Override public List getBatchList(Collection ids) { if (CollUtil.isEmpty(ids)) { return List.of(); } return batchMapper.selectBatchIds(ids); } @Override public List getBatchWorkerListByBatchId(Long batchId) { return batchWorkerMapper.selectListByBatchId(batchId); } @Override public List getBatchWorkerListByBatchIds(Collection batchIds) { if (CollUtil.isEmpty(batchIds)) { return List.of(); } return batchWorkerMapper.selectListByBatchIds(batchIds); } // ==================== 私有方法 ==================== /** * 校验关联数据(生产人员:责任人 + 生产人员配置) *

* 品种、产线的校验在创建/更新流程中内联执行(需取编码参与批次号生成) */ private void validateRelations(MesProBatchSaveReqVO reqVO) { Set userIds = new HashSet<>(); userIds.add(reqVO.getOwnerUserId()); userIds.addAll(convertList(reqVO.getWorkers(), MesProBatchSaveReqVO.Worker::getUserId)); adminUserApi.validateUserList(userIds); } /** * 生成批次号 *

* 编码规则:固定前缀(PC) + 日期(yyyyMMdd) + 输入字符(产线编码 + 品种编码) + 流水号(按日循环) * * @param lineCode 产线编码 * @param itemCode 品种(物料)编码 * @return 批次号 */ private String generateBatchCode(String lineCode, String itemCode) { String inputChar = StrUtil.emptyToDefault(lineCode, "") + StrUtil.emptyToDefault(itemCode, ""); return autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_BATCH_CODE.getCode(), inputChar); } /** * 校验批次可编辑(草稿、生产中) */ private void validateBatchEditable(MesProBatchDO batch) { if (!MesProBatchStatusEnum.DRAFT.getStatus().equals(batch.getStatus()) && !MesProBatchStatusEnum.PRODUCING.getStatus().equals(batch.getStatus())) { throw exception(PRO_BATCH_STATUS_NOT_DRAFT_OR_PRODUCING); } } /** * 插入批次生产人员配置 */ private void insertBatchWorkers(Long batchId, List workers) { if (CollUtil.isEmpty(workers)) { return; } List workerDOs = convertList(workers, worker -> new MesProBatchWorkerDO() .setBatchId(batchId) .setUserId(worker.getUserId()) .setWorkerType(worker.getWorkerType()) .setRemark(worker.getRemark())); batchWorkerMapper.insertBatch(workerDOs); } }