diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockOutOrder/service/ExecutionStockOutOrderDomainService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockOutOrder/service/ExecutionStockOutOrderDomainService.java index 19d42bb59..311b600ae 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockOutOrder/service/ExecutionStockOutOrderDomainService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockOutOrder/service/ExecutionStockOutOrderDomainService.java @@ -146,6 +146,8 @@ public class ExecutionStockOutOrderDomainService { */ @Transactional(rollbackFor = Exception.class) public Boolean insert(ExecutionStockOutOrderDO stockOutOrderDO) { + //建单校验:明细数量必须大于0(0数量单下发WMS后自动分配必失败,出库单会卡死在已审核状态) + validateMaterialDetailQuantity(stockOutOrderDO.getMaterialDetailList()); //设置单号 Long warehouseId = stockOutOrderDO.getWarehouseId(); AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId); @@ -252,6 +254,8 @@ public class ExecutionStockOutOrderDomainService { stockOutOrderDO.setBusinessOrderNo(prefix + seq); } //设置物料信息 + //建单校验:明细数量必须大于0(0数量单下发WMS后自动分配必失败,出库单会卡死在已审核状态) + validateMaterialDetailQuantity(stockOutOrderDO.getMaterialDetailList()); setMaterialDetailInfo(stockOutOrderDO); //统计种类 stockOutOrderDO.setMaterialQuantity(stockOutOrderDO.getMaterialDetailList().size()); @@ -493,10 +497,20 @@ public class ExecutionStockOutOrderDomainService { .set(ExecutionStockOutOrder::getUpdateTime, new Date()) .in(ExecutionStockOutOrder::getOutOrderId, stockOutOrderDO.getOutOrderIds())); - //todo下发推送到wms + //下发前统一校验所有单据:全部通过后再推送WMS,避免部分单据推送成功、部分失败造成两边数据不一致 List inOrderIds = stockOutOrderDO.getOutOrderIds(); + List issueOrderList = new ArrayList<>(); for (Long inOrderId : inOrderIds) { ExecutionStockOutOrder byId = stockOutOrderService.getById(inOrderId); + if (byId == null) { + throw new ServiceException("出库单不存在,outOrderId=" + inOrderId); + } + validateIssueOrderDetail(byId); + issueOrderList.add(byId); + } + + //下发推送到wms + for (ExecutionStockOutOrder byId : issueOrderList) { StockOutOrder stockOutOrder = new StockOutOrder(); BeanUtils.copyProperties(byId, stockOutOrder,"outOrderId"); Long warehouseId = stockOutOrder.getWarehouseId(); @@ -535,13 +549,64 @@ public class ExecutionStockOutOrderDomainService { String outOrderNumber = byId.getOutOrderNumber(); com.mhd.system.api.domain.StockOutOrderDTO stockOutOrderDTO = new com.mhd.system.api.domain.StockOutOrderDTO(); stockOutOrderDTO.setOutOrderNumber(outOrderNumber); - wmsServiceFeign.execution(stockOutOrderDTO); + //WMS自动分配:校验返回值。此前不校验时,WMS侧分配失败被Feign降级吞掉,OMS显示成功但出库单卡死在WMS"已审核"状态,拣货单永远不生成 + AjaxResult executionResult = wmsServiceFeign.execution(stockOutOrderDTO); + if (executionResult == null || !"200".equals(String.valueOf(executionResult.get("code")))) { + throw new ServiceException("出库单[" + outOrderNumber + "]WMS自动分配失败:" + + (executionResult == null ? "WMS服务不可用或已降级" : executionResult.get("msg"))); + } - wmsServiceFeign.genPickingOrderByOms(stockOutOrderDTO); + //WMS生成拣货单:同样校验返回值,失败即中断并回滚,让用户能感知到错误 + AjaxResult genPickingResult = wmsServiceFeign.genPickingOrderByOms(stockOutOrderDTO); + if (genPickingResult == null || !"200".equals(String.valueOf(genPickingResult.get("code")))) { + throw new ServiceException("出库单[" + outOrderNumber + "]WMS生成拣货单失败:" + + (genPickingResult == null ? "WMS服务不可用或已降级" : genPickingResult.get("msg"))); + } } return update; } + /** + * 建单校验:出库明细数量必须大于0 + * 历史缺陷:0数量明细的单下发到WMS后自动分配必然失败,且异常被吞,出库单卡死在"已审核"状态无法生成拣货单 + */ + private void validateMaterialDetailQuantity(List materialDetailList) { + if (CollectionUtils.isEmpty(materialDetailList)) { + throw new ServiceException("出库单明细不能为空"); + } + for (ExecutionOutMaterialDetailDO detail : materialDetailList) { + if (detail.getQuantity() == null || detail.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException("出库明细数量必须大于0,物料:" + + (detail.getMaterialCode() != null ? detail.getMaterialCode() : detail.getMaterialName())); + } + } + } + + /** + * 下发前校验出库单明细: + * 1.必须有物料明细,且数量大于0; + * 2.明细必须已关联库存(materialInventoryId),否则WMS按库存ID自动分配会空指针; + * 校验放在推送WMS之前,失败直接报错回滚,避免出库单卡死在WMS"已审核"状态 + */ + private void validateIssueOrderDetail(ExecutionStockOutOrder order) { + String outOrderNumber = order.getOutOrderNumber(); + List detailList = outMaterialDetailService.list(new QueryWrapper().lambda() + .eq(ExecutionOutMaterialDetail::getOutOrderNumber, outOrderNumber)); + if (CollectionUtils.isEmpty(detailList)) { + throw new ServiceException("出库单[" + outOrderNumber + "]没有物料明细,无法下发"); + } + for (ExecutionOutMaterialDetail detail : detailList) { + String materialDesc = detail.getMaterialCode() != null ? detail.getMaterialCode() + : (detail.getMaterialName() != null ? detail.getMaterialName() : String.valueOf(detail.getMaterialDetailId())); + if (detail.getQuantity() == null || detail.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException("出库单[" + outOrderNumber + "]物料[" + materialDesc + "]数量为0,无法下发"); + } + if (detail.getMaterialInventoryId() == null || detail.getMaterialInventoryId() <= 0) { + throw new ServiceException("出库单[" + outOrderNumber + "]物料[" + materialDesc + "]未关联库存,无法下发"); + } + } + } + /** * @description 修改出库单 * @author ZhouGY diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockOutOrder/StockOutOrderApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockOutOrder/StockOutOrderApplicationService.java index 50c3e19cd..7dfe88da6 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockOutOrder/StockOutOrderApplicationService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockOutOrder/StockOutOrderApplicationService.java @@ -1122,8 +1122,20 @@ public class StockOutOrderApplicationService { // throw new ServiceException("库存不存在:" + batchRefNo + " " + sheetRefNo + " " + boxPalletNo); // } Long materialInventoryId = detail.getMaterialInventoryId(); + String materialDesc = detail.getMaterialCode() != null ? detail.getMaterialCode() + : (detail.getMaterialName() != null ? detail.getMaterialName() : String.valueOf(detail.getUniqueId())); + //OMS推送的库存ID缺失或在WMS不存在时明确报错(此前直接解引用空指针,异常被Feign降级吞掉后单据卡死) + if (materialInventoryId == null || materialInventoryId <= 0) { + throw new ServiceException("出库单[" + outOrderNumber + "]物料[" + materialDesc + "]未关联库存,无法自动分配"); + } MaterialInventory materialInventory = materialInventoryMapper.selectById(materialInventoryId); + if (materialInventory == null) { + throw new ServiceException("出库单[" + outOrderNumber + "]物料[" + materialDesc + "]库存不存在,库存ID=" + materialInventoryId + ",无法自动分配"); + } BigDecimal remainingQuantity = detail.getQuantity(); // 计划数量 + if (remainingQuantity == null || remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException("出库单[" + outOrderNumber + "]物料[" + materialDesc + "]计划数量为0,无法自动分配"); + } // 当前物料的可用库存 BigDecimal availableStock = materialInventory.getAllocationQuantity(); @@ -1132,9 +1144,9 @@ public class StockOutOrderApplicationService { continue; } if (availableStock.compareTo(remainingQuantity) < 0) { - throw new RuntimeException( - String.format("可用库存不足,计划数量:%s,当前可用库存:%s", - remainingQuantity, availableStock) + throw new ServiceException( + String.format("出库单[%s]物料[%s]可用库存不足,计划数量:%s,当前可用库存:%s", + outOrderNumber, materialDesc, remainingQuantity, availableStock) ); } // detail.setMaterialInventoryId(materialInventory.getMaterialInventoryId()); @@ -1148,6 +1160,10 @@ public class StockOutOrderApplicationService { detail.setAllocationQuantity(remainingQuantity); materialDetailList2.add(detail); } + //全部明细因可用库存为0被跳过时明确报错(此前空明细落入batchAssignUpdate抛出笼统的"物料明细不能为空",难定位) + if (materialDetailList2.isEmpty()) { + throw new ServiceException("出库单[" + stockOutOrderDb.getOutOrderNumber() + "]所有物料明细的可用库存为0,无法自动分配"); + } stockOutOrderD.setMaterialDetailList(materialDetailList2); StockOutOrderDO assembleStockOutOrderDO = assembleParamByOutOrder(stockOutOrderD); Boolean assignResult = stockOutOrderDomainService.autoAssign(assembleStockOutOrderDO); diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/service/StockOutOrderDomainService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/service/StockOutOrderDomainService.java index cd3ac6527..18af0e37e 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/service/StockOutOrderDomainService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/service/StockOutOrderDomainService.java @@ -2542,6 +2542,13 @@ public class StockOutOrderDomainService { private static final String OMS_AUTO_AUDIT_REMARK = "OMS下发自动审核"; public void omsOrderAdd(StockOutOrder stockOutOrder) { + //幂等保护:重复下发时同单号活动单已存在则跳过,避免插入重复出库单(下发失败重试场景此前会产生重复数据) + StockOutOrder existOrder = stockOutOrderService.getOne(new QueryWrapper().lambda() + .eq(StockOutOrder::getOutOrderNumber, stockOutOrder.getOutOrderNumber()) + .eq(StockOutOrder::getDelFlag, 1), false); + if (existOrder != null) { + return; + } // 任务下发用户id String userId = stockInOrderMapper.queryWarehouseUserId(stockOutOrder.getOrganizationId(), stockOutOrder.getWarehouseId(), stockOutOrder.getTopOrganizationId()); stockOutOrder.setTaskIssueUserId(userId); @@ -2608,6 +2615,11 @@ public class StockOutOrderDomainService { if (CollectionUtils.isEmpty(outMaterialDetails)) { return; } + //幂等保护:重复下发时先逻辑删除同单号已存在的活动明细,避免重复插入导致分配/拣货数据错乱 + outMaterialDetailService.update(new UpdateWrapper().lambda() + .set(OutMaterialDetail::getDelFlag, 2) + .eq(OutMaterialDetail::getOutOrderNumber, outMaterialDetails.get(0).getOutOrderNumber()) + .eq(OutMaterialDetail::getDelFlag, 1)); outMaterialDetailService.saveBatch(outMaterialDetails); autoAuditOmsOutOrder(outMaterialDetails.get(0).getOutOrderNumber()); } @@ -2617,6 +2629,14 @@ public class StockOutOrderDomainService { if (CollectionUtils.isEmpty(deliveryDetailsLinks)) { return; } + //幂等保护:重复下发时先逻辑删除同单号已存在的配送明细,避免重复插入 + String firstOrderNumber = deliveryDetailsLinks.get(0).getOrderNumber(); + if (StringUtils.isNotEmpty(firstOrderNumber)) { + deliveryDetailsLinkService.update(new UpdateWrapper().lambda() + .set(com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink::getDelFlag, 2) + .eq(com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink::getOrderNumber, firstOrderNumber) + .eq(com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink::getDelFlag, 1)); + } for (DeliveryDetailsLink deliveryDetailsLink : deliveryDetailsLinks) { com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink deliveryDetailsLinkEntity = new com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink(); BeanUtils.copyProperties(deliveryDetailsLink, deliveryDetailsLinkEntity);