diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockInOrder/service/ExecutionStockInOrderDomainService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockInOrder/service/ExecutionStockInOrderDomainService.java index b28feee99..22d304bfe 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockInOrder/service/ExecutionStockInOrderDomainService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/executionStockInOrder/service/ExecutionStockInOrderDomainService.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * 入库单 @@ -564,6 +565,24 @@ public class ExecutionStockInOrderDomainService { */ @Transactional(rollbackFor = Exception.class) public Boolean issueOrder(ExecutionStockInOrderDO stockInOrderDO) { + // 防重复下发校验(与 executeAndIssue 防重逻辑对齐):issueTime 有值表示已下发过。 + // 此前缺少该校验,executeAndIssue 已自动下发过的执行单仍可被手动再次下发, + // WMS接收端无幂等,重复推送导致WMS入库单/明细与收货单明细翻倍 + List inOrderIds = stockInOrderDO.getInOrderIds(); + if (inOrderIds == null || inOrderIds.isEmpty()) { + throw new ServiceException("下发失败:未选择单据"); + } + // 入参去重:同一请求内重复ID会导致同一执行单被多次推送WMS + List distinctInOrderIds = inOrderIds.stream().distinct().collect(Collectors.toList()); + List checkOrders = stockInOrderService.list( + new QueryWrapper().lambda() + .in(ExecutionStockInOrder::getInOrderId, distinctInOrderIds)); + for (ExecutionStockInOrder order : checkOrders) { + if (order.getIssueTime() != null) { + throw new ServiceException("下发失败:单据[" + order.getInOrderNumber() + "]已下发,不可重复下发;如需重新下发请先取消下发"); + } + } + LoginUser loginUser = SecurityUtils.getLoginUser(); // 获取用户姓名(优先从UserPo获取,如果为空则使用realname,最后使用username作为兜底) @@ -578,11 +597,10 @@ public class ExecutionStockInOrderDomainService { .set(ExecutionStockInOrder::getUpdateBy, loginUser.getUserid()) .set(ExecutionStockInOrder::getUpdateByName, userRealName) .set(ExecutionStockInOrder::getUpdateTime, new Date()) - .in(ExecutionStockInOrder::getInOrderId, stockInOrderDO.getInOrderIds())); + .in(ExecutionStockInOrder::getInOrderId, distinctInOrderIds)); //todo下发推送到wms - List inOrderIds = stockInOrderDO.getInOrderIds(); - for (Long inOrderId : inOrderIds) { + for (Long inOrderId : distinctInOrderIds) { ExecutionStockInOrder byId = stockInOrderService.getById(inOrderId); StockInOrder stockInOrder = new StockInOrder(); BeanUtils.copyProperties(byId, stockInOrder,"inOrderId"); diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/service/ReservationStockInOrderDomainService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/service/ReservationStockInOrderDomainService.java index 586e10ba5..591c799b1 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/service/ReservationStockInOrderDomainService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/service/ReservationStockInOrderDomainService.java @@ -652,7 +652,7 @@ public class ReservationStockInOrderDomainService { Long warehouseId1 = reservationStockInOrder.getWarehouseId(); AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId1); if (!"200".equals(String.valueOf(ajaxResult.get("code")))) { - throw new ServiceException("获取仓库信息失败"); + throw new ServiceException("获取仓库信息失败,仓库ID:" + warehouseId1 + ",错误信息:" + ajaxResult.get("msg")); } //仓库信息 WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class); @@ -826,7 +826,11 @@ public class ReservationStockInOrderDomainService { .collect(Collectors.groupingBy(ReservationInMaterialDetail::getWarehouseId)); List executionInOrderNumbers = new ArrayList<>(); materialDetailGroupByWarehouse.forEach((warehouseId, details) -> { - if (warehouseId == null) warehouseId = warehouseId1; + // 明细行仓库无效(0)时兜底用主表仓库,与下发issueOrder的兜底逻辑对齐; + // 无效仓库ID传给system服务获取仓库信息会失败,导致"获取仓库信息失败" + if (warehouseId == null || warehouseId == 0L){ + warehouseId = warehouseId1; + } Long topOrgId = reservationStockInOrderMapper.getTopOrgId(warehouseId); Long orgId = reservationStockInOrderMapper.getOrgId(warehouseId); @@ -873,7 +877,7 @@ public class ReservationStockInOrderDomainService { // Long warehouseId1 = reservationStockInOrder.getWarehouseId(); AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId); if (!"200".equals(String.valueOf(ajaxResult.get("code")))) { - throw new ServiceException("获取仓库信息失败"); + throw new ServiceException("获取仓库信息失败,仓库ID:" + warehouseId + ",错误信息:" + ajaxResult.get("msg")); } //仓库信息 WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class); diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockInOrder/service/StockInOrderDomainService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockInOrder/service/StockInOrderDomainService.java index aeffc45f4..d18095675 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockInOrder/service/StockInOrderDomainService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockInOrder/service/StockInOrderDomainService.java @@ -676,6 +676,17 @@ public class StockInOrderDomainService { private static final String OMS_AUTO_AUDIT_REMARK = "OMS下发自动审核"; public void omsOrderAdd(StockInOrder stockInOrder) { + // 幂等防御:OMS重复下发(下发接口防重缺失/请求重放)会以相同入库单号再次推送主单, + // 同单号有效单已存在则跳过插入,防止WMS侧出现重复入库单、明细与收货单 + String inOrderNumber = stockInOrder.getInOrderNumber(); + if (!StringUtils.isEmpty(inOrderNumber)) { + long exists = stockInOrderService.count(new QueryWrapper().lambda() + .eq(StockInOrder::getInOrderNumber, inOrderNumber) + .eq(StockInOrder::getDelFlag, 1)); + if (exists > 0) { + return; + } + } stockInOrderService.save(stockInOrder); } @@ -684,7 +695,29 @@ public class StockInOrderDomainService { if (CollectionUtils.isEmpty(inMaterialDetails)) { return; } - materialDetailService.saveBatch(inMaterialDetails); + // 幂等防御:OMS重复推送同单号明细(重复下发/请求重放)时,按OMS明细ID(omsInMaterialDetail)判重, + // 已存在的行不再插入,防止WMS入库明细与下游收货明细翻倍; + // omsInMaterialDetail为空(调用方未传)时无从判重,保持原插入行为 + String inOrderNumber = inMaterialDetails.get(0).getInOrderNumber(); + List toInsert = inMaterialDetails; + if (!StringUtils.isEmpty(inOrderNumber)) { + List existingDetails = materialDetailService.list(new QueryWrapper().lambda() + .eq(InMaterialDetail::getInOrderNumber, inOrderNumber) + .eq(InMaterialDetail::getDelFlag, 1)); + Set existingOmsDetailIds = existingDetails.stream() + .map(InMaterialDetail::getOmsInMaterialDetail) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toSet()); + if (!existingOmsDetailIds.isEmpty()) { + toInsert = inMaterialDetails.stream() + .filter(d -> StringUtils.isEmpty(d.getOmsInMaterialDetail()) + || !existingOmsDetailIds.contains(d.getOmsInMaterialDetail())) + .collect(Collectors.toList()); + } + } + if (!toInsert.isEmpty()) { + materialDetailService.saveBatch(toInsert); + } autoAuditOmsInOrder(inMaterialDetails.get(0).getInOrderNumber()); } @@ -700,6 +733,9 @@ public class StockInOrderDomainService { StockInOrder stockInOrder = stockInOrderService.getOne(new QueryWrapper().lambda() .eq(StockInOrder::getInOrderNumber, inOrderNumber) .eq(StockInOrder::getDelFlag, 1) + // 历史数据可能存在同号重复主单,固定取最早一张(首次下发那张), + // 无排序时LIMIT 1返回行不确定,可能命中未审核的重复单导致重复生成同号收货单 + .orderByAsc(StockInOrder::getInOrderId) .last("LIMIT 1")); if (stockInOrder == null || AUDIT_STATUS_PASS == stockInOrder.getAuditStatus()) { return; @@ -1202,13 +1238,26 @@ public class StockInOrderDomainService { throw new ServiceException("出库单不存在或已审核通过"); } List inOrderNumberList = stockInOrderPOList.stream().map(StockInOrderPO::getInOrderNumber).collect(Collectors.toList()); + // 收货单防重:收货单号"RV-"+入库单号为确定性生成,该单号收货单已存在(历史重复下发/重复审核 + // 已生成过)时不再为对应入库单生成收货单,避免同号收货单叠加导致收货管理详情明细翻倍 + List receiptNumbersToCheck = inOrderNumberList.stream().map(no -> "RV-" + no).collect(Collectors.toList()); + Set existingReceiptNumbers = stockReceiptOrderService.list(new QueryWrapper().lambda() + .in(StockReceiptOrder::getReceiptOrderNumber, receiptNumbersToCheck) + .eq(StockReceiptOrder::getDelFlag, 1)) + .stream().map(StockReceiptOrder::getReceiptOrderNumber).collect(Collectors.toSet()); + List pendingAuditOrders = stockInOrderPOList.stream() + .filter(p -> !existingReceiptNumbers.contains("RV-" + p.getInOrderNumber())) + .collect(Collectors.toList()); + if (pendingAuditOrders.isEmpty()) { + return true; + } InMaterialDetailDO inMaterialDetailDO = new InMaterialDetailDO(); inMaterialDetailDO.setInOrderNumberList(inOrderNumberList); List inMaterialDetailPOList = materialDetailService.queryListChildren(inMaterialDetailDO); Map> inMaterialDetailListMap = inMaterialDetailPOList.stream().collect(Collectors.groupingBy(InMaterialDetailPO::getInOrderNumber)); List stockReceiptOrderDOList = new ArrayList<>(); - stockInOrderPOList.forEach(p -> { + pendingAuditOrders.forEach(p -> { StockReceiptOrderDO stockReceiptOrderDO = new StockReceiptOrderDO(); BeanUtils.copyProperties(p, stockReceiptOrderDO); stockReceiptOrderDO.setReceiptOrderNumber("RV-"+p.getInOrderNumber());