package cn.iocoder.yudao.module.mes.service.wm.sn; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjUtil; 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.wm.sn.vo.MesWmSnDetailRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGenerateReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnGroupRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnInStockReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnOutStockReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.sn.vo.MesWmSnPageReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.sn.MesWmSnMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmSnStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; 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.workorder.MesProWorkOrderService; import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; 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.math.BigDecimal; import java.util.ArrayList; import java.util.List; 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 SN 码 Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class MesWmSnServiceImpl implements MesWmSnService { @Resource private MesWmSnMapper snMapper; @Resource private MesWmTransactionMapper transactionMapper; @Resource private MesMdAutoCodeRecordService autoCodeRecordService; @Resource private MesMdmItemService mdmItemService; @Resource private MesProWorkOrderService workOrderService; @Resource private MesWmBarcodeService barcodeService; @Resource private MesWmTransactionService transactionService; @Resource private MesWmWarehouseService warehouseService; @Resource private MesWmWarehouseLocationService locationService; @Resource private MesWmWarehouseAreaService areaService; @Override @Transactional(rollbackFor = Exception.class) public void generateSnCodes(MesWmSnGenerateReqVO reqVO) { // 校验物料是否存在 mdmItemService.validateItemExists(reqVO.getItemId()); // 校验工单是否存在 if (reqVO.getWorkOrderId() != null) { workOrderService.validateWorkOrderExists(reqVO.getWorkOrderId()); } List sns = new ArrayList<>(reqVO.getCount()); // 生成批次 UUID String uuid = IdUtil.fastSimpleUUID(); // 批量生成 SN 码 for (int i = 0; i < reqVO.getCount(); i++) { String snCode = autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.WM_SN_CODE.getCode()); sns.add(new MesWmSnDO().setUuid(uuid).setCode(snCode) .setItemId(reqVO.getItemId()).setBatchCode(reqVO.getBatchCode()) .setWorkOrderId(reqVO.getWorkOrderId())); } // 批量插入 snMapper.insertBatch(sns); // 自动生成条码 sns.forEach(sn -> barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.SN.getValue(), sn.getId(), sn.getCode(), sn.getCode())); } @Override public PageResult getSnGroupPage(MesWmSnPageReqVO reqVO) { return snMapper.selectPageGroupByUuid(reqVO); } @Override public List getSnListByUuid(String uuid) { return snMapper.selectListByUuid(uuid); } @Override @Transactional(rollbackFor = Exception.class) public void deleteSnByUuid(String uuid) { List sns = snMapper.selectListByUuid(uuid); if (CollUtil.isEmpty(sns)) { return; } // 删除 SN 码关联的条码记录 barcodeService.deleteBarcodeByBizTypeAndBizIds(BarcodeBizTypeEnum.SN.getValue(), convertList(sns, MesWmSnDO::getId)); // 删除 SN 码 snMapper.deleteByUuid(uuid); } @Override @Transactional(rollbackFor = Exception.class) public void registerSnInStock(MesWmSnInStockReqVO reqVO) { // 1. 校验 SN 存在且物料匹配 MesWmSnDO sn = validateSnExists(reqVO.getCode()); validateSnItemMatch(sn, reqVO.getItemId()); // 2. 校验物料启用序列号管理 MesMdmItemDO item = mdmItemService.validateItemExists(reqVO.getItemId()); if (!Boolean.TRUE.equals(item.getIsSerialManaged())) { throw exception(WM_SN_ITEM_NOT_SERIAL); } // 3. 状态校验:已在库则拒绝重复登记 if (ObjUtil.equal(sn.getStatus(), MesWmSnStatusEnum.IN_STOCK.getStatus())) { throw exception(WM_SN_ALREADY_IN_STOCK); } // 4. 校验库存位置存在(冻结与混放规则由事务内部兜底) warehouseService.validateWarehouseExists(reqVO.getWarehouseId()); locationService.validateWarehouseLocationExists(reqVO.getLocationId()); areaService.validateWarehouseAreaExists(reqVO.getAreaId()); // 5. 创建单件入库事务(+1) transactionService.createTransaction(new MesWmTransactionSaveReqDTO() .setType(MesWmTransactionTypeEnum.IN.getType()) .setBizType(MesBizTypeConstants.WM_SN) .setBizId(sn.getId()).setBizCode(sn.getCode()).setBizLineId(sn.getId()) .setItemId(sn.getItemId()).setQuantity(BigDecimal.ONE) .setBatchId(sn.getBatchId()).setBatchCode(sn.getBatchCode()) .setSnCode(sn.getCode()) .setWarehouseId(reqVO.getWarehouseId()).setLocationId(reqVO.getLocationId()).setAreaId(reqVO.getAreaId())); // 6. 更新 SN:在库 + 记录库位 snMapper.updateById(new MesWmSnDO().setId(sn.getId()) .setStatus(MesWmSnStatusEnum.IN_STOCK.getStatus()) .setWarehouseId(reqVO.getWarehouseId()).setLocationId(reqVO.getLocationId()).setAreaId(reqVO.getAreaId()) .setRemark(reqVO.getRemark())); } @Override @Transactional(rollbackFor = Exception.class) public void outboundSn(MesWmSnOutStockReqVO reqVO) { // 1. 校验 SN 存在且物料匹配 MesWmSnDO sn = validateSnExists(reqVO.getCode()); validateSnItemMatch(sn, reqVO.getItemId()); // 2. 状态校验:必须在库且记录过库位 if (!ObjUtil.equal(sn.getStatus(), MesWmSnStatusEnum.IN_STOCK.getStatus()) || sn.getWarehouseId() == null || sn.getLocationId() == null || sn.getAreaId() == null) { throw exception(WM_SN_NOT_IN_STOCK); } // 3. 创建单件出库事务(-1),按 SN 在库位置精确定位库存记录 transactionService.createTransaction(new MesWmTransactionSaveReqDTO() .setType(MesWmTransactionTypeEnum.OUT.getType()) .setBizType(MesBizTypeConstants.WM_SN) .setBizId(sn.getId()).setBizCode(sn.getCode()).setBizLineId(sn.getId()) .setItemId(sn.getItemId()).setQuantity(BigDecimal.ONE.negate()) .setBatchId(sn.getBatchId()).setBatchCode(sn.getBatchCode()) .setSnCode(sn.getCode()) .setWarehouseId(sn.getWarehouseId()).setLocationId(sn.getLocationId()).setAreaId(sn.getAreaId())); // 4. 更新 SN:已出库 + 清空在库位置 snMapper.markOutStockAndClearLocation(sn.getId()); snMapper.updateById(new MesWmSnDO().setId(sn.getId()).setRemark(reqVO.getRemark())); } @Override public MesWmSnDetailRespVO getSnDetail(String snCode) { MesWmSnDO sn = validateSnExists(snCode); MesWmSnDetailRespVO resp = BeanUtils.toBean(sn, MesWmSnDetailRespVO.class); MesWmTransactionDO transaction = transactionMapper.selectLatestBySnCode(snCode); if (transaction != null) { MesWmTransactionTypeEnum typeEnum = MesWmTransactionTypeEnum.valueOf(transaction.getType()); resp.setLastTransactionType(transaction.getType()); resp.setLastTransactionTypeName(typeEnum != null ? typeEnum.getName() : null); resp.setLastTransactionQuantity(transaction.getQuantity()); resp.setLastTransactionBizType(transaction.getBizType()); resp.setLastTransactionBizCode(transaction.getBizCode()); resp.setLastTransactionTime(transaction.getTransactionTime()); } return resp; } @Override public PageResult getSnInStockPage(MesWmSnInStockPageReqVO reqVO) { return snMapper.selectStatusPage(reqVO); } private MesWmSnDO validateSnExists(String code) { MesWmSnDO sn = snMapper.selectByCode(code); if (sn == null) { throw exception(WM_SN_NOT_EXISTS); } return sn; } private void validateSnItemMatch(MesWmSnDO sn, Long itemId) { if (ObjUtil.notEqual(sn.getItemId(), itemId)) { throw exception(WM_SN_ITEM_MISMATCH); } } }