| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockAdjustReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.*; |
| | | import java.util.function.BiConsumer; |
| | | import java.util.function.Function; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<MesWmStockSummaryRespVO> getStockSummaryByItem(MesWmMaterialStockListReqVO reqVO) { |
| | | List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO); |
| | | List<MesWmStockSummaryRespVO> list = buildStockSummary(stocks, |
| | | stock -> String.valueOf(stock.getItemId()), |
| | | (vo, stock) -> vo.setItemId(stock.getItemId())); |
| | | Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap( |
| | | convertSet(stocks, MesWmMaterialStockDO::getItemId)); |
| | | list.forEach(vo -> { |
| | | MesMdmItemDO item = itemMap.get(vo.getItemId()); |
| | | if (item != null) { |
| | | vo.setItemCode(item.getCode()).setItemName(item.getName()); |
| | | } |
| | | }); |
| | | return list; |
| | | } |
| | | |
| | | @Override |
| | | public List<MesWmStockSummaryRespVO> getStockSummaryByBatch(MesWmMaterialStockListReqVO reqVO) { |
| | | List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO); |
| | | return buildStockSummary(stocks, |
| | | stock -> stock.getBatchId() + "|" + (stock.getBatchCode() != null ? stock.getBatchCode() : ""), |
| | | (vo, stock) -> vo.setBatchId(stock.getBatchId()).setBatchCode(stock.getBatchCode())); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesWmStockSummaryRespVO> getStockSummaryByWarehouse(MesWmMaterialStockListReqVO reqVO) { |
| | | List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO); |
| | | List<MesWmStockSummaryRespVO> list = buildStockSummary(stocks, |
| | | stock -> String.valueOf(stock.getWarehouseId()), |
| | | (vo, stock) -> vo.setWarehouseId(stock.getWarehouseId())); |
| | | Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap( |
| | | convertSet(stocks, MesWmMaterialStockDO::getWarehouseId)); |
| | | list.forEach(vo -> { |
| | | MesWmWarehouseDO warehouse = warehouseMap.get(vo.getWarehouseId()); |
| | | if (warehouse != null) { |
| | | vo.setWarehouseName(warehouse.getName()); |
| | | } |
| | | }); |
| | | return list; |
| | | } |
| | | |
| | | /** |
| | | * 内存聚合库存数量:按维度键 + 库存状态分组,累加在库数量 |
| | | */ |
| | | private List<MesWmStockSummaryRespVO> buildStockSummary(List<MesWmMaterialStockDO> stocks, |
| | | Function<MesWmMaterialStockDO, String> dimKeyFn, |
| | | BiConsumer<MesWmStockSummaryRespVO, MesWmMaterialStockDO> dimSetter) { |
| | | if (CollUtil.isEmpty(stocks)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | Map<String, MesWmStockSummaryRespVO> map = new LinkedHashMap<>(); |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | String key = dimKeyFn.apply(stock) + "|" + stock.getStockState(); |
| | | MesWmStockSummaryRespVO vo = map.computeIfAbsent(key, k -> { |
| | | MesWmStockSummaryRespVO newVo = new MesWmStockSummaryRespVO(); |
| | | dimSetter.accept(newVo, stock); |
| | | newVo.setStockState(stock.getStockState()); |
| | | newVo.setStockStateName(stockStateName(stock.getStockState())); |
| | | newVo.setTotalQuantity(BigDecimal.ZERO); |
| | | return newVo; |
| | | }); |
| | | BigDecimal qty = stock.getQuantity() != null ? stock.getQuantity() : BigDecimal.ZERO; |
| | | vo.setTotalQuantity(vo.getTotalQuantity().add(qty)); |
| | | } |
| | | return new ArrayList<>(map.values()); |
| | | } |
| | | |
| | | private String stockStateName(Integer state) { |
| | | if (state == null) { |
| | | return ""; |
| | | } |
| | | for (MesWmStockStateEnum e : MesWmStockStateEnum.values()) { |
| | | if (e.getState().equals(state)) { |
| | | return e.getName(); |
| | | } |
| | | } |
| | | return String.valueOf(state); |
| | | } |
| | | |
| | | @Override |
| | | public MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId, |
| | | Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) { |
| | | // 1. 查找已有库存记录(vendorId 参与唯一键) |
| | |
| | | .batchId(batchId).batchCode(batchCode).vendorId(vendorId) |
| | | .quantity(BigDecimal.ZERO) // 初始数量为 0,由 updateMaterialStockQuantity 更新 |
| | | .receiptTime(receiptTime != null ? receiptTime : LocalDateTime.now()) |
| | | .stockState(MesWmStockStateEnum.TEMP.getState()) |
| | | .frozen(false) |
| | | .build(); |
| | | materialStockMapper.insert(newStock); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<MesWmMaterialStockDO> getMaterialStockListByItemAndBatch(Long itemId, Long batchId) { |
| | | return materialStockMapper.selectListByItemAndBatch(itemId, batchId); |
| | | } |
| | | |
| | | @Override |
| | | public void updateMaterialStockQuantity(Long id, BigDecimal quantity, boolean checkFlag) { |
| | | // 更新库存数量(Mapper 层 CAS 防负库存;checkFlag=false 时允许负数) |
| | | int updatedRows = materialStockMapper.updateQuantity(id, quantity, checkFlag); |
| | | if (updatedRows == 0) { |
| | | throw exception(WM_MATERIAL_STOCK_INSUFFICIENT); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void updateMaterialStockState(Long id, Integer stockState) { |
| | | materialStockMapper.updateById(new MesWmMaterialStockDO().setId(id).setStockState(stockState)); |
| | | } |
| | | |
| | | @Override |
| | | public int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState) { |
| | | return materialStockMapper.updateStockStateByBatchId(batchId, toState, fromState); |
| | | } |
| | | |
| | | @Override |
| | |
| | | throw new IllegalArgumentException("无效的质量状态类型: " + qualityStatus.getClass().getName()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void moveMaterialStock(MesWmMaterialStockMoveReqVO reqVO) { |
| | | // 1. 查询原库存记录 |
| | | MesWmMaterialStockDO sourceStock = validateMaterialStockExists(reqVO.getMaterialStockId()); |
| | | |
| | | // 2. 校验库存数量充足(可用量 = 在库数量 - 冻结数量 - 占用量) |
| | | BigDecimal availableQty = sourceStock.getQuantity() != null ? sourceStock.getQuantity() : BigDecimal.ZERO; |
| | | if (sourceStock.getFrozenQuantity() != null) { |
| | | availableQty = availableQty.subtract(sourceStock.getFrozenQuantity()); |
| | | } |
| | | if (sourceStock.getReservedQuantity() != null) { |
| | | availableQty = availableQty.subtract(sourceStock.getReservedQuantity()); |
| | | } |
| | | if (availableQty.compareTo(reqVO.getQuantity()) < 0) { |
| | | throw exception(WM_MATERIAL_STOCK_INSUFFICIENT); |
| | | } |
| | | |
| | | // 3. 校验库存未冻结 |
| | | if (Boolean.TRUE.equals(sourceStock.getFrozen())) { |
| | | throw exception(WM_MATERIAL_STOCK_FROZEN); |
| | | } |
| | | |
| | | // 4. 校验目标库位混放规则 |
| | | checkAreaMixingRule(reqVO.getToAreaId(), sourceStock.getItemId(), sourceStock.getBatchId()); |
| | | |
| | | // 5. 生成移库单号 |
| | | String moveCode = "MOVE_" + System.currentTimeMillis(); |
| | | |
| | | // 6. 创建调拨移出事务(负数) |
| | | MesWmTransactionSaveReqDTO outTransaction = new MesWmTransactionSaveReqDTO() |
| | | .setType(MesWmTransactionTypeEnum.MOVE_OUT.getType()) |
| | | .setBizType(MesBizTypeConstants.WM_TRANSFER_OUT) |
| | | .setBizId(sourceStock.getId()) |
| | | .setBizCode(moveCode) |
| | | .setBizLineId(sourceStock.getId()) |
| | | .setItemId(sourceStock.getItemId()) |
| | | .setQuantity(reqVO.getQuantity().negate()) |
| | | .setBatchId(sourceStock.getBatchId()) |
| | | .setBatchCode(sourceStock.getBatchCode()) |
| | | .setWarehouseId(sourceStock.getWarehouseId()) |
| | | .setLocationId(sourceStock.getLocationId()) |
| | | .setAreaId(sourceStock.getAreaId()) |
| | | .setVendorId(sourceStock.getVendorId()) |
| | | .setMaterialStockId(sourceStock.getId()) |
| | | .setCheckFlag(true); |
| | | Long outTransactionId = wmTransactionService.createTransaction(outTransaction); |
| | | |
| | | // 7. 查找目标库位的库存记录(忽略供应商),用于合并库存 |
| | | MesWmMaterialStockDO targetStock = materialStockMapper.selectByCompositeKeyIgnoreVendor( |
| | | sourceStock.getItemId(), |
| | | reqVO.getToWarehouseId(), |
| | | reqVO.getToLocationId(), |
| | | reqVO.getToAreaId(), |
| | | sourceStock.getBatchId() |
| | | ); |
| | | |
| | | // 8. 创建调拨移入事务(正数),关联出库事务 |
| | | // 使用目标库位已有库存记录的 vendorId(如果有),否则不传 |
| | | MesWmTransactionSaveReqDTO inTransaction = new MesWmTransactionSaveReqDTO() |
| | | .setType(MesWmTransactionTypeEnum.MOVE_IN.getType()) |
| | | .setBizType(MesBizTypeConstants.WM_TRANSFER_IN) |
| | | .setBizId(sourceStock.getId()) |
| | | .setBizCode(moveCode) |
| | | .setBizLineId(sourceStock.getId()) |
| | | .setItemId(sourceStock.getItemId()) |
| | | .setQuantity(reqVO.getQuantity()) |
| | | .setBatchId(sourceStock.getBatchId()) |
| | | .setBatchCode(sourceStock.getBatchCode()) |
| | | .setWarehouseId(reqVO.getToWarehouseId()) |
| | | .setLocationId(reqVO.getToLocationId()) |
| | | .setAreaId(reqVO.getToAreaId()) |
| | | .setVendorId(targetStock != null ? targetStock.getVendorId() : null) |
| | | .setRelatedTransactionId(outTransactionId) |
| | | .setCheckFlag(false); |
| | | wmTransactionService.createTransaction(inTransaction); |
| | | } |
| | | |
| | | } |