package cn.iocoder.yudao.module.mes.service.wm.batch; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPageReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; 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.wm.barcode.MesWmBarcodeService; import cn.iocoder.yudao.module.mdm.api.item.MdmItemBatchConfigApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemBatchConfigRespDTO; 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.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * 批次管理 Service 实现类 * * @author 芋道源码 */ @Service @Validated @Slf4j public class MesWmBatchServiceImpl implements MesWmBatchService { /** * 批次追溯最大递归深度,防止极端场景下性能问题 */ private static final int MAX_TRACE_DEPTH = 20; @Resource private MesWmBatchMapper batchMapper; @Resource private MesMdmItemService mdmItemService; @Resource private MdmItemBatchConfigApi itemBatchConfigApi; @Resource private MesMdAutoCodeRecordService autoCodeRecordService; @Resource private MesWmBarcodeService barcodeService; @Override public PageResult getBatchPage(MesWmBatchPageReqVO pageReqVO) { return batchMapper.selectPage(pageReqVO); } @Override public MesWmBatchDO getBatch(Long id) { return batchMapper.selectById(id); } @Override public MesWmBatchDO getBatchByCode(String code) { return batchMapper.selectByCode(code); } @Override @Transactional(rollbackFor = Exception.class) public MesWmBatchDO getOrGenerateBatchCode(MesWmBatchGenerateReqVO reqVO) { // 1.1 查询物料信息,检查是否启用批次管理 MesMdmItemDO item = mdmItemService.validateItemExists(reqVO.getItemId()); if (Boolean.FALSE.equals(item.getIsBatchManaged())) { // 未启用批次管理,返回 null return null; } // 调试日志:打印接收到的日期 log.info("[getOrGenerateBatchCode] 收到请求, itemId={}, batchCode={}, produceDate={}", reqVO.getItemId(), reqVO.getBatchCode(), reqVO.getProduceDate()); // 1.2 查询物料批次配置(从 MDM 获取,不存在则使用默认配置) MdmItemBatchConfigRespDTO config = itemBatchConfigApi.getItemBatchConfig(reqVO.getItemId()); // 2. 根据配置校验必填参数并清空不需要的参数 MesWmBatchDO batch = MesWmBatchDO.builder().itemId(reqVO.getItemId()).build(); if (config != null) { // 生产日期 if (Boolean.TRUE.equals(config.getProduceDateFlag())) { if (ObjUtil.isNull(reqVO.getProduceDate())) { throw exception(WM_BATCH_PRODUCE_DATE_REQUIRED); } batch.setProduceDate(reqVO.getProduceDate()); } // 入库日期 if (Boolean.TRUE.equals(config.getReceiptDateFlag())) { if (ObjUtil.isNull(reqVO.getReceiptDate())) { throw exception(WM_BATCH_RECEIPT_DATE_REQUIRED); } batch.setReceiptDate(reqVO.getReceiptDate()); } // 有效期 if (Boolean.TRUE.equals(config.getExpireDateFlag())) { if (ObjUtil.isNull(reqVO.getExpireDate())) { throw exception(WM_BATCH_EXPIRE_DATE_REQUIRED); } batch.setExpireDate(reqVO.getExpireDate()); } // 供应商 if (Boolean.TRUE.equals(config.getVendorFlag())) { if (ObjUtil.isNull(reqVO.getVendorId())) { throw exception(WM_BATCH_VENDOR_REQUIRED); } batch.setVendorId(reqVO.getVendorId()); } // 客户 if (Boolean.TRUE.equals(config.getClientFlag())) { if (ObjUtil.isNull(reqVO.getClientId())) { throw exception(WM_BATCH_CLIENT_REQUIRED); } batch.setClientId(reqVO.getClientId()); } // 采购订单编号 if (Boolean.TRUE.equals(config.getPurchaseOrderCodeFlag())) { if (ObjUtil.isNull(reqVO.getPurchaseOrderCode())) { throw exception(WM_BATCH_PURCHASE_ORDER_CODE_REQUIRED); } batch.setPurchaseOrderCode(reqVO.getPurchaseOrderCode()); } // 销售订单编号 if (Boolean.TRUE.equals(config.getSalesOrderCodeFlag())) { if (ObjUtil.isNull(reqVO.getSalesOrderCode())) { throw exception(WM_BATCH_CUSTOMER_ORDER_CODE_REQUIRED); } batch.setSalesOrderCode(reqVO.getSalesOrderCode()); } // 生产工单 if (Boolean.TRUE.equals(config.getWorkOrderFlag())) { if (ObjUtil.isNull(reqVO.getWorkOrderId())) { throw exception(WM_BATCH_WORK_ORDER_REQUIRED); } batch.setWorkOrderId(reqVO.getWorkOrderId()); } // 生产任务 if (Boolean.TRUE.equals(config.getTaskFlag())) { if (ObjUtil.isNull(reqVO.getTaskId())) { throw exception(WM_BATCH_TASK_REQUIRED); } batch.setTaskId(reqVO.getTaskId()); } // 工作站 if (Boolean.TRUE.equals(config.getWorkstationFlag())) { if (ObjUtil.isNull(reqVO.getWorkstationId())) { throw exception(WM_BATCH_WORKSTATION_REQUIRED); } batch.setWorkstationId(reqVO.getWorkstationId()); } // 工具 if (Boolean.TRUE.equals(config.getToolFlag())) { if (ObjUtil.isNull(reqVO.getToolId())) { throw exception(WM_BATCH_TOOL_REQUIRED); } batch.setToolId(reqVO.getToolId()); } // 模具 if (Boolean.TRUE.equals(config.getMoldFlag())) { if (ObjUtil.isNull(reqVO.getMoldId())) { throw exception(WM_BATCH_MOLD_REQUIRED); } batch.setMoldId(reqVO.getMoldId()); } // 生产批号 if (Boolean.TRUE.equals(config.getLotNumberFlag())) { if (ObjUtil.isNull(reqVO.getLotNumber())) { throw exception(WM_BATCH_LOT_NUMBER_REQUIRED); } batch.setLotNumber(reqVO.getLotNumber()); } // 质量状态 if (Boolean.TRUE.equals(config.getQualityStatusFlag())) { if (ObjUtil.isNull(reqVO.getQualityStatus())) { throw exception(WM_BATCH_QUALITY_STATUS_REQUIRED); } batch.setQualityStatus(reqVO.getQualityStatus()); } } // 无批次配置时,使用传入的参数(宽松模式,适用于自定义入库等场景) else { if (reqVO.getProduceDate() != null) { batch.setProduceDate(reqVO.getProduceDate()); } if (reqVO.getReceiptDate() != null) { batch.setReceiptDate(reqVO.getReceiptDate()); } if (reqVO.getExpireDate() != null) { batch.setExpireDate(reqVO.getExpireDate()); } if (reqVO.getVendorId() != null) { batch.setVendorId(reqVO.getVendorId()); } if (reqVO.getClientId() != null) { batch.setClientId(reqVO.getClientId()); } } // 3.1 情况一:传入了 batchCode,查询是否存在该批次号 if (StrUtil.isNotBlank(reqVO.getBatchCode())) { MesWmBatchDO existingBatch = batchMapper.selectByCode(reqVO.getBatchCode()); if (existingBatch != null) { // 校验物料是否匹配 if (!existingBatch.getItemId().equals(reqVO.getItemId())) { throw exception(WM_BATCH_ITEM_MISMATCH); } // 更新批次属性(仅更新传入的非空值) updateBatchProperties(existingBatch, batch); return existingBatch; } // 不存在则使用传入的 batchCode 创建新批次 batch.setCode(reqVO.getBatchCode()); } else { // 3.2 情况二:查询是否存在匹配的批次(根据属性匹配) MesWmBatchDO existingBatch = batchMapper.selectFirst(batch); if (existingBatch != null) { return existingBatch; } // 3.3 情况三:自动生成新批次号 String batchCode = autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.WM_BATCH_CODE.getCode()); batch.setCode(batchCode); } batchMapper.insert(batch); // 4. 生成条码 barcodeService.autoGenerateBarcode(MesBizTypeConstants.WM_BATCH, batch.getId(), batch.getCode(), item.getName()); return batch; } /** * 更新已有批次的属性(仅更新传入的非空值) * * @param existingBatch 已有批次 * @param newBatch 新批次属性 */ private void updateBatchProperties(MesWmBatchDO existingBatch, MesWmBatchDO newBatch) { boolean needUpdate = false; if (newBatch.getProduceDate() != null && !newBatch.getProduceDate().equals(existingBatch.getProduceDate())) { existingBatch.setProduceDate(newBatch.getProduceDate()); needUpdate = true; } if (newBatch.getExpireDate() != null && !newBatch.getExpireDate().equals(existingBatch.getExpireDate())) { existingBatch.setExpireDate(newBatch.getExpireDate()); needUpdate = true; } if (newBatch.getReceiptDate() != null && !newBatch.getReceiptDate().equals(existingBatch.getReceiptDate())) { existingBatch.setReceiptDate(newBatch.getReceiptDate()); needUpdate = true; } if (newBatch.getVendorId() != null && !newBatch.getVendorId().equals(existingBatch.getVendorId())) { existingBatch.setVendorId(newBatch.getVendorId()); needUpdate = true; } if (newBatch.getClientId() != null && !newBatch.getClientId().equals(existingBatch.getClientId())) { existingBatch.setClientId(newBatch.getClientId()); needUpdate = true; } if (newBatch.getSalesOrderCode() != null && !newBatch.getSalesOrderCode().equals(existingBatch.getSalesOrderCode())) { existingBatch.setSalesOrderCode(newBatch.getSalesOrderCode()); needUpdate = true; } if (newBatch.getPurchaseOrderCode() != null && !newBatch.getPurchaseOrderCode().equals(existingBatch.getPurchaseOrderCode())) { existingBatch.setPurchaseOrderCode(newBatch.getPurchaseOrderCode()); needUpdate = true; } if (newBatch.getWorkOrderId() != null && !newBatch.getWorkOrderId().equals(existingBatch.getWorkOrderId())) { existingBatch.setWorkOrderId(newBatch.getWorkOrderId()); needUpdate = true; } if (newBatch.getTaskId() != null && !newBatch.getTaskId().equals(existingBatch.getTaskId())) { existingBatch.setTaskId(newBatch.getTaskId()); needUpdate = true; } if (newBatch.getWorkstationId() != null && !newBatch.getWorkstationId().equals(existingBatch.getWorkstationId())) { existingBatch.setWorkstationId(newBatch.getWorkstationId()); needUpdate = true; } if (newBatch.getToolId() != null && !newBatch.getToolId().equals(existingBatch.getToolId())) { existingBatch.setToolId(newBatch.getToolId()); needUpdate = true; } if (newBatch.getMoldId() != null && !newBatch.getMoldId().equals(existingBatch.getMoldId())) { existingBatch.setMoldId(newBatch.getMoldId()); needUpdate = true; } if (newBatch.getLotNumber() != null && !newBatch.getLotNumber().equals(existingBatch.getLotNumber())) { existingBatch.setLotNumber(newBatch.getLotNumber()); needUpdate = true; } if (newBatch.getQualityStatus() != null && !newBatch.getQualityStatus().equals(existingBatch.getQualityStatus())) { existingBatch.setQualityStatus(newBatch.getQualityStatus()); needUpdate = true; } if (needUpdate) { batchMapper.updateById(existingBatch); } } @Override public List getForwardBatchList(String code) { return getForwardBatchList(code, new HashSet<>(), 0); } private List getForwardBatchList(String code, Set visited, int depth) { if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) { return new ArrayList<>(); } List list = batchMapper.selectListByForward(code); if (CollUtil.isEmpty(list)) { return new ArrayList<>(); } // 继续递归查询下游批次 List results = new ArrayList<>(list); for (MesWmBatchDO batch : list) { results.addAll(getForwardBatchList(batch.getCode(), visited, depth + 1)); } return results; } @Override public List getBackwardBatchList(String code) { return getBackwardBatchList(code, new HashSet<>(), 0); } private List getBackwardBatchList(String code, Set visited, int depth) { if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) { return new ArrayList<>(); } List list = batchMapper.selectListByBackward(code); if (CollUtil.isEmpty(list)) { return new ArrayList<>(); } // 继续递归查询上游批次 List results = new ArrayList<>(list); for (MesWmBatchDO batch : list) { results.addAll(getBackwardBatchList(batch.getCode(), visited, depth + 1)); } return results; } @Override public MesWmBatchDO validateBatchExists(Long batchId, Long itemId) { MesWmBatchDO batch = batchMapper.selectById(batchId); if (batch == null) { throw exception(WM_BATCH_NOT_EXISTS); } if (ObjUtil.notEqual(batch.getItemId(), itemId)) { throw exception(WM_BATCH_ITEM_MISMATCH); } return batch; } @Override public MesWmBatchDO validateBatchExists(Long batchId, Long itemId, Long clientId, Long vendorId) { MesWmBatchDO batch = validateBatchExists(batchId, itemId); if (clientId != null && ObjUtil.notEqual(batch.getClientId(), clientId)) { throw exception(WM_BATCH_CLIENT_MISMATCH); } if (vendorId != null && ObjUtil.notEqual(batch.getVendorId(), vendorId)) { throw exception(WM_BATCH_VENDOR_MISMATCH); } return batch; } @Override public Long getBatchCountByToolId(Long toolId) { return batchMapper.selectCountByToolId(toolId); } }