| | |
| | | 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.transaction.vo.MesWmStockSummaryReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.transaction.vo.MesWmStockSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.transaction.vo.MesWmTransactionPageReqVO; |
| | | 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.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.stockwarning.MesWmStockWarningService; |
| | | 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.MesWmWarehouseDefaultService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | |
| | | private MesWmWarehouseLocationService locationService; |
| | | @Resource |
| | | private MesWmWarehouseAreaService areaService; |
| | | @Resource |
| | | private MesWmWarehouseDefaultService warehouseDefaultService; |
| | | @Resource |
| | | private MesWmStockWarningService stockWarningService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | checkQuantity(reqDTO, inbound); |
| | | // 1.3 入库事务:批次号校验 + batchId/batchCode 互补 |
| | | checkBatch(reqDTO, inbound); |
| | | // 1.3.1 入库事务未指定库区/库位时,自动落到仓库默认库区/库位(客户未启用库区/库位管理) |
| | | if (inbound) { |
| | | fillDefaultLocationAndArea(reqDTO); |
| | | } |
| | | // 1.4 入库事务:库位混放规则校验 |
| | | if (inbound) { |
| | | materialStockService.checkAreaMixingRule(reqDTO.getAreaId(), reqDTO.getItemId(), reqDTO.getBatchId()); |
| | |
| | | MesWmMaterialStockDO materialStock; |
| | | MesWmTransactionTypeEnum transactionType = MesWmTransactionTypeEnum.valueOf(reqDTO.getType()); |
| | | boolean isInbound = transactionType != null && transactionType.isInbound(); |
| | | boolean forceCreateNewStock = Boolean.TRUE.equals(reqDTO.getForceCreateNewStock()); |
| | | |
| | | if (reqDTO.getMaterialStockId() != null) { |
| | | // 传入了 materialStockId,直接使用 |
| | | materialStock = materialStockService.getMaterialStock(reqDTO.getMaterialStockId()); |
| | | if (materialStock == null) { |
| | | throw exception(WM_MATERIAL_STOCK_NOT_EXISTS); |
| | | } |
| | | // 校验物料、批次、仓库等维度一致(防止传入错误的 materialStockId) |
| | | // 校验物料、批次、仓库等维度一致(防止传入错误的 materialStockId)。 |
| | | // 若此处不一致,通常是该库存已被移库/合并/拆分,需退回重新拣货,故给出针对性提示 |
| | | if (ObjUtil.notEqual(materialStock.getItemId(), reqDTO.getItemId()) |
| | | || ObjUtil.notEqual(materialStock.getWarehouseId(), reqDTO.getWarehouseId()) |
| | | || ObjUtil.notEqual(materialStock.getLocationId(), reqDTO.getLocationId()) |
| | | || ObjUtil.notEqual(materialStock.getAreaId(), reqDTO.getAreaId())) { |
| | | throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH); |
| | | throw new ServiceException(WM_MATERIAL_STOCK_SELECTION_MISMATCH.getCode(), |
| | | "该库存记录已变动(可能被移库/合并/拆分),与出库明细不一致,无法扣减,请退回重新拣货"); |
| | | } |
| | | } else if (isInbound) { |
| | | // 入库事务:找不到则自动创建库存记录 |
| | | materialStock = materialStockService.getOrCreateMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | // 入库事务:根据 forceCreateNewStock 标志决定是创建新记录还是查找/创建 |
| | | if (forceCreateNewStock) { |
| | | materialStock = materialStockService.createMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | } else { |
| | | materialStock = materialStockService.getOrCreateMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | } |
| | | } else { |
| | | // 出库事务:库存记录必须已存在,不允许自动创建 |
| | | // 出库事务:库存记录必须已存在,不允许自动创建(除非 checkFlag=false 允许负库存) |
| | | materialStock = materialStockService.getMaterialStockByCompositeKey( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getVendorId()); |
| | | if (materialStock == null) { |
| | | throw exception(WM_MATERIAL_STOCK_NOT_EXISTS); |
| | | // checkFlag=false 时允许负库存,自动创建库存记录(数量将从 0 扣减为负数) |
| | | boolean allowNegative = !ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true); |
| | | if (allowNegative) { |
| | | materialStock = materialStockService.getOrCreateMaterialStock( |
| | | reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), |
| | | reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); |
| | | } else { |
| | | throw exception(WM_MATERIAL_STOCK_NOT_EXISTS); |
| | | } |
| | | } |
| | | } |
| | | if (materialStock == null || materialStock.getId() == null) { |
| | |
| | | // 2.3 更新库存数量 |
| | | boolean checkFlag = ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true); |
| | | materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag); |
| | | // 2.4 库存预警校验:本次变动导致库存跨越上下限时,自动发送站内通知(需求 5.1②) |
| | | stockWarningService.checkAfterStockChange(reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getQuantity()); |
| | | |
| | | // 3. 插入事务流水 |
| | | MesWmTransactionDO transaction = MesWmTransactionDO.builder() |
| | |
| | | return transactionMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesWmStockSummaryRespVO> getStockSummaryPage(MesWmStockSummaryReqVO reqVO) { |
| | | // 未指定时间时默认统计近三个月:期初为两个月前 1 日 0 点,期末为当前时间 |
| | | LocalDateTime endTime = reqVO.getEndTime() != null ? reqVO.getEndTime() : LocalDateTime.now(); |
| | | LocalDateTime startTime = reqVO.getStartTime() != null ? reqVO.getStartTime() |
| | | : endTime.toLocalDate().withDayOfMonth(1).minusMonths(2).atStartOfDay(); |
| | | if (startTime.isAfter(endTime)) { |
| | | throw new ServiceException(400, StrUtil.format( |
| | | "查询开始时间[{}]晚于结束时间[{}],请调整时间范围后重试", |
| | | startTime, endTime)); |
| | | } |
| | | IPage<MesWmStockSummaryRespVO> page = transactionMapper.selectStockSummaryPage( |
| | | new Page<>(reqVO.getPageNo(), reqVO.getPageSize()), |
| | | reqVO.getItemId(), reqVO.getWarehouseId(), startTime, endTime); |
| | | return new PageResult<>(page.getRecords(), page.getTotal()); |
| | | } |
| | | |
| | | // ==================== 私有校验方法 ==================== |
| | | |
| | | /** |
| | | * 入库事务未指定库区/库位时,自动使用仓库的默认库区/默认库位 |
| | | * |
| | | * 客户未启用库区/库位管理,前端不再让用户选择,库存仍需要完整的仓库-库区-库位维度。 |
| | | */ |
| | | private void fillDefaultLocationAndArea(MesWmTransactionSaveReqDTO reqDTO) { |
| | | if (reqDTO.getWarehouseId() == null |
| | | || (reqDTO.getLocationId() != null && reqDTO.getAreaId() != null)) { |
| | | return; |
| | | } |
| | | MesWmWarehouseAreaDO defaultArea = warehouseDefaultService.getOrCreateDefaultArea(reqDTO.getWarehouseId()); |
| | | reqDTO.setLocationId(defaultArea.getLocationId()); |
| | | reqDTO.setAreaId(defaultArea.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 校验数量正负号与事务方向一致 |
| | | */ |
| | | private void checkQuantity(MesWmTransactionSaveReqDTO reqDTO, boolean inbound) { |