| | |
| | | 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.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.materialstock.MesWmMaterialStockDO; |
| | |
| | | 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.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; |
| | | 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; |
| | |
| | | @Resource |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private MesMdItemService mdItemService; |
| | | @Resource |
| | | private MesMdItemTypeService itemTypeService; |
| | | @Resource |
| | | private MesWmWarehouseAreaService areaService; |
| | |
| | | @Resource |
| | | @Lazy |
| | | private MesWmTransactionService wmTransactionService; |
| | | @Resource |
| | | @Lazy |
| | | private MesWmBatchService batchService; |
| | | |
| | | @Override |
| | | public MesWmMaterialStockDO getMaterialStock(Long id) { |
| | |
| | | List<MesMdItemTypeDO> children = itemTypeService.getItemTypeChildrenList(pageReqVO.getItemTypeId()); |
| | | itemTypeIds.addAll(convertSet(children, MesMdItemTypeDO::getId)); |
| | | } |
| | | // 1.2 解析 itemId |
| | | // 1.2 解析 itemId:前端传的是 mes_md_item.id,库存表存的是 mdm_item_id,需要转换 |
| | | Set<Long> itemIds = null; |
| | | if (pageReqVO.getItemId() != null) { |
| | | itemIds = SetUtils.asSet(pageReqVO.getItemId()); |
| | | MesMdItemDO item = mdItemService.getItem(pageReqVO.getItemId()); |
| | | if (item != null && item.getMdmItemId() != null) { |
| | | itemIds = SetUtils.asSet(item.getMdmItemId()); |
| | | } |
| | | } |
| | | // 1.3 解析 virtualFilter:转换为虚拟仓 warehouseId |
| | | Long virtualWarehouseId = null; |
| | |
| | | .frozen(false) |
| | | .build(); |
| | | materialStockMapper.insert(newStock); |
| | | return newStock; |
| | | // 3. 重新查询,确保拿到数据库生成的完整记录(id、createTime 等) |
| | | MesWmMaterialStockDO inserted = materialStockMapper.selectById(newStock.getId()); |
| | | return inserted != null ? inserted : newStock; |
| | | } |
| | | |
| | | @Override |
| | | public MesWmMaterialStockDO getMaterialStockByCompositeKey(Long itemId, Long warehouseId, Long locationId, Long areaId, |
| | | Long batchId, Long vendorId) { |
| | | return materialStockMapper.selectByCompositeKey(itemId, warehouseId, locationId, areaId, batchId, vendorId); |
| | | } |
| | | |
| | | @Override |
| | |
| | | // 1. 校验物料存在且启用 |
| | | MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(reqVO.getItemId()); |
| | | |
| | | // 2. 校验库位混放规则 |
| | | checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), reqVO.getBatchId()); |
| | | // 2. 处理批次(仅入库时需要处理批次) |
| | | Long batchId = reqVO.getBatchId(); |
| | | String batchCode = reqVO.getBatchCode(); |
| | | if (isInbound) { |
| | | MesWmBatchDO batch = resolveBatch(reqVO, item); |
| | | if (batch != null) { |
| | | batchId = batch.getId(); |
| | | batchCode = batch.getCode(); |
| | | } |
| | | } else { |
| | | // 出库时校验批次存在 |
| | | if (batchId != null) { |
| | | batchService.validateBatchExists(batchId, reqVO.getItemId()); |
| | | } |
| | | } |
| | | |
| | | // 3. 获取或创建库存记录 |
| | | // 3. 校验库位混放规则 |
| | | checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), batchId); |
| | | |
| | | // 4. 获取或创建库存记录 |
| | | MesWmMaterialStockDO stock = getOrCreateMaterialStock( |
| | | reqVO.getItemId(), |
| | | reqVO.getWarehouseId(), |
| | | reqVO.getLocationId(), |
| | | reqVO.getAreaId(), |
| | | reqVO.getBatchId(), |
| | | reqVO.getBatchCode(), |
| | | batchId, |
| | | batchCode, |
| | | reqVO.getVendorId(), |
| | | LocalDateTime.now() |
| | | ); |
| | | |
| | | // 4. 计算变动数量:入库为正,出库为负 |
| | | // 5. 计算变动数量:入库为正,出库为负 |
| | | BigDecimal quantity = isInbound ? reqVO.getQuantity() : reqVO.getQuantity().negate(); |
| | | |
| | | // 5. 更新库存数量(出库时校验库存充足) |
| | | updateMaterialStockQuantity(stock.getId(), quantity, !isInbound); |
| | | |
| | | // 6. 创建库存事务流水 |
| | | // 6. 创建库存事务流水(内部会更新库存数量) |
| | | int bizType = isInbound ? MesBizTypeConstants.WM_STOCK_ADJUST_IN : MesBizTypeConstants.WM_STOCK_ADJUST_OUT; |
| | | MesWmTransactionSaveReqDTO transaction = new MesWmTransactionSaveReqDTO() |
| | | .setType(isInbound ? MesWmTransactionTypeEnum.IN.getType() : MesWmTransactionTypeEnum.OUT.getType()) |
| | |
| | | .setBizLineId(stock.getId()) |
| | | .setItemId(reqVO.getItemId()) |
| | | .setQuantity(quantity) |
| | | .setBatchId(reqVO.getBatchId()) |
| | | .setBatchCode(reqVO.getBatchCode()) |
| | | .setBatchId(batchId) |
| | | .setBatchCode(batchCode) |
| | | .setWarehouseId(reqVO.getWarehouseId()) |
| | | .setLocationId(reqVO.getLocationId()) |
| | | .setAreaId(reqVO.getAreaId()) |
| | |
| | | return stock.getId(); |
| | | } |
| | | |
| | | /** |
| | | * 解析批次 |
| | | * <p> |
| | | * 入库时的批次处理逻辑: |
| | | * 1. 物料未启用批次管理:返回 null |
| | | * 2. 传入 batchId:校验并使用已有批次 |
| | | * 3. 未传 batchId:根据 batchCode 等参数自动创建批次 |
| | | * |
| | | * @param reqVO 入库请求 |
| | | * @param item 物料信息 |
| | | * @return 批次记录(物料未启用批次管理时返回 null) |
| | | */ |
| | | private MesWmBatchDO resolveBatch(MesWmMaterialStockAdjustReqVO reqVO, MesMdmItemDO item) { |
| | | // 1. 物料未启用批次管理,返回 null |
| | | if (Boolean.FALSE.equals(item.getIsBatchManaged())) { |
| | | return null; |
| | | } |
| | | |
| | | // 2. 传入 batchId,校验并返回已有批次 |
| | | if (reqVO.getBatchId() != null) { |
| | | return batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId()); |
| | | } |
| | | |
| | | // 3. 未传 batchId,自动创建批次 |
| | | MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO() |
| | | .setItemId(reqVO.getItemId()) |
| | | .setBatchCode(reqVO.getBatchCode()) |
| | | .setVendorId(reqVO.getVendorId()) |
| | | .setProduceDate(reqVO.getProduceDate() != null ? reqVO.getProduceDate().atStartOfDay() : null) |
| | | .setExpireDate(reqVO.getExpireDate() != null ? reqVO.getExpireDate().atStartOfDay() : null) |
| | | .setReceiptDate(LocalDateTime.now()) |
| | | .setLotNumber(reqVO.getLotNumber()) |
| | | .setQualityStatus(parseQualityStatus(reqVO.getQualityStatus())); |
| | | return batchService.getOrGenerateBatchCode(batchReqVO); |
| | | } |
| | | |
| | | /** |
| | | * 解析质量状态(支持数字或字符串) |
| | | */ |
| | | private Integer parseQualityStatus(Object qualityStatus) { |
| | | if (qualityStatus == null) { |
| | | return null; |
| | | } |
| | | if (qualityStatus instanceof Integer) { |
| | | return (Integer) qualityStatus; |
| | | } |
| | | if (qualityStatus instanceof String) { |
| | | String str = (String) qualityStatus; |
| | | // 支持数字字符串 |
| | | if (str.matches("\\d+")) { |
| | | return Integer.parseInt(str); |
| | | } |
| | | // 支持常见字符串映射 |
| | | switch (str.toUpperCase()) { |
| | | case "PENDING": |
| | | case "待检验": |
| | | case "待检": |
| | | return 0; |
| | | case "PASS": |
| | | case "QUALIFIED": |
| | | case "合格": |
| | | return 1; |
| | | case "FAIL": |
| | | case "UNQUALIFIED": |
| | | case "不合格": |
| | | return 2; |
| | | default: |
| | | throw new IllegalArgumentException("无效的质量状态: " + str); |
| | | } |
| | | } |
| | | 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); |
| | | } |
| | | |
| | | } |