package cn.iocoder.yudao.module.mes.service.wm.materialstockbag; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper; import cn.iocoder.yudao.module.mes.enums.wm.MesWmMaterialStockBagStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 库存袋码明细 Service 实现类 * * 袋码明细生命周期:首次入库建立(ACTIVE)→ 移库/质检迁移位置状态 → 出库/报废结束(OUT/SCRAPPED)。 * 唯一约束 bag_code 保证每个袋码仅一条明细,复用同一行完成全生命周期流转。 */ @Service @Validated public class MesWmMaterialStockBagServiceImpl implements MesWmMaterialStockBagService { @Resource private MesWmMaterialStockBagMapper materialStockBagMapper; @Override @Transactional(rollbackFor = Exception.class) public MesWmMaterialStockBagDO inbound(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO stock, Long transactionId) { String bagCode = reqDTO.getBagCode(); if (StrUtil.isBlank(bagCode)) { throw exception(WM_MATERIAL_STOCK_BAG_REQUIRED); } // 1. 查询该袋码既有明细:优先按 bagCodeId,其次按 bagCode MesWmMaterialStockBagDO existing = materialStockBagMapper.selectByBagCode(bagCode); if (existing == null && reqDTO.getBagCodeId() != null) { existing = materialStockBagMapper.selectByBagCodeId(reqDTO.getBagCodeId()); } if (existing != null) { // 2. 已在库 → 重复入库,禁止 if (MesWmMaterialStockBagStatusEnum.ACTIVE.getStatus().equals(existing.getStatus())) { throw exception(WM_MATERIAL_STOCK_BAG_DUPLICATE, bagCode); } // 3. 已出库/报废 → 重新激活并更新位置 return reactivate(existing, reqDTO, stock, transactionId); } // 4. 首次入库 → 新建明细 MesWmMaterialStockBagDO bag = MesWmMaterialStockBagDO.builder() .materialStockId(stock.getId()) .bagCodeId(reqDTO.getBagCodeId()) .bagCode(bagCode) .itemId(stock.getItemId()) .batchId(stock.getBatchId()) .batchCode(stock.getBatchCode()) .vendorId(stock.getVendorId()) .warehouseId(stock.getWarehouseId()) .locationId(stock.getLocationId()) .areaId(stock.getAreaId()) .stockState(ObjUtil.defaultIfNull(reqDTO.getStockState(), stock.getStockState())) .status(MesWmMaterialStockBagStatusEnum.ACTIVE.getStatus()) .quantity(BigDecimal.ONE) .frozen(false) .lastTransactionId(transactionId) .inboundTime(LocalDateTime.now()) .build(); materialStockBagMapper.insert(bag); return bag; } @Override @Transactional(rollbackFor = Exception.class) public void outbound(MesWmTransactionSaveReqDTO reqDTO, Long transactionId) { MesWmMaterialStockBagDO bag = requireActiveBag(reqDTO); MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO(); update.setId(bag.getId()); update.setStatus(MesWmMaterialStockBagStatusEnum.OUT.getStatus()); update.setStockState(MesWmStockStateEnum.OUT.getState()); update.setLastTransactionId(transactionId); update.setOutboundTime(LocalDateTime.now()); materialStockBagMapper.updateById(update); } @Override @Transactional(rollbackFor = Exception.class) public void moveIn(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO targetStock, Long transactionId) { MesWmMaterialStockBagDO bag = requireActiveBag(reqDTO); MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO(); update.setId(bag.getId()); update.setMaterialStockId(targetStock.getId()); update.setWarehouseId(targetStock.getWarehouseId()); update.setLocationId(targetStock.getLocationId()); update.setAreaId(targetStock.getAreaId()); update.setStockState(ObjUtil.defaultIfNull(reqDTO.getStockState(), targetStock.getStockState())); update.setLastTransactionId(transactionId); materialStockBagMapper.updateById(update); } @Override @Transactional(rollbackFor = Exception.class) public void scrap(MesWmTransactionSaveReqDTO reqDTO, Long transactionId) { MesWmMaterialStockBagDO bag = requireActiveBag(reqDTO); MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO(); update.setId(bag.getId()); update.setStatus(MesWmMaterialStockBagStatusEnum.SCRAPPED.getStatus()); update.setStockState(MesWmStockStateEnum.OUT.getState()); update.setQuantity(BigDecimal.ZERO); update.setLastTransactionId(transactionId); update.setOutboundTime(LocalDateTime.now()); materialStockBagMapper.updateById(update); } @Override public MesWmMaterialStockBagDO getActiveByMaterialStockId(Long materialStockId) { return materialStockBagMapper.selectOneActiveByMaterialStockId(materialStockId); } /** * 查询指定袋码的活动明细,不存在或已出库/报废则抛异常。 */ private MesWmMaterialStockBagDO requireActiveBag(MesWmTransactionSaveReqDTO reqDTO) { MesWmMaterialStockBagDO bag = null; if (reqDTO.getBagCodeId() != null) { bag = materialStockBagMapper.selectActiveByBagCodeId(reqDTO.getBagCodeId()); } if (bag == null && StrUtil.isNotBlank(reqDTO.getBagCode())) { bag = materialStockBagMapper.selectActiveByBagCode(reqDTO.getBagCode()); } if (bag == null) { String code = StrUtil.blankToDefault(reqDTO.getBagCode(), String.valueOf(reqDTO.getBagCodeId())); throw exception(WM_MATERIAL_STOCK_BAG_NOT_ACTIVE, code); } return bag; } private MesWmMaterialStockBagDO reactivate(MesWmMaterialStockBagDO existing, MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO stock, Long transactionId) { MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO(); update.setId(existing.getId()); update.setMaterialStockId(stock.getId()); update.setItemId(stock.getItemId()); update.setBatchId(stock.getBatchId()); update.setBatchCode(stock.getBatchCode()); update.setVendorId(stock.getVendorId()); update.setWarehouseId(stock.getWarehouseId()); update.setLocationId(stock.getLocationId()); update.setAreaId(stock.getAreaId()); update.setStockState(ObjUtil.defaultIfNull(reqDTO.getStockState(), stock.getStockState())); update.setStatus(MesWmMaterialStockBagStatusEnum.ACTIVE.getStatus()); update.setQuantity(BigDecimal.ONE); update.setFrozen(false); update.setLastTransactionId(transactionId); update.setInboundTime(LocalDateTime.now()); update.setOutboundTime(null); materialStockBagMapper.updateById(update); return existing; } }