fix(oms,wms):执行单重复下发致WMS收货明细翻倍修复,executeAndIssue获取仓库信息失败修复

一、收货明细翻倍(executionStockInOrderApi/issueOrder下发后,WMS收货管理详情明细×2):
根因:执行单下发接口无防重 + WMS接收端无幂等。executeAndIssue已自动下发过的执行单
仍可被手动再次下发(issueTime已置但无校验),重复推送使WMS插入同号入库主单+第二份
in_material_detail,再生成第二张同号收货单(收货单号"RV-"+入库单号确定性生成),
收货管理详情按单号查明细即双倍。四层防御:
1. OMS执行单issueOrder补防重:issueTime非空拒绝下发(与executeAndIssue对齐),
   inOrderIds去重防同请求重复推送
2. WMS omsOrderAdd主单幂等:同单号有效单(del_flag=1)已存在则跳过插入
3. WMS omsOrderAddDetail明细幂等:按oms_in_material_detail判重,已存在行不再插入;
   未传ID的行保持原插入行为
4. WMS autoAuditOmsInOrder取单补orderByAsc(in_order_id)消除同号取单随机性;
   auditApprove生成收货单前按收货单号查重过滤,已生成过的单不再重复生成
二、executeAndIssue报"获取仓库信息失败":
1. 明细行warehouseId=0时兜底改用主表仓库(与下发issueOrder兜底逻辑对齐),
   不再拿无效仓库ID调system服务
2. executeAndIssue/issueOrder两处报错增强为带仓库ID与错误信息,便于定位
存量已翻倍数据(重复入库单/明细/收货单)需另行软删清理,本次仅防新增
This commit is contained in:
rcx
2026-08-21 18:21:03 +08:00
parent b209d8cb6d
commit 5fc491f612
3 changed files with 79 additions and 8 deletions
@@ -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<Long> inOrderIds = stockInOrderDO.getInOrderIds();
if (inOrderIds == null || inOrderIds.isEmpty()) {
throw new ServiceException("下发失败:未选择单据");
}
// 入参去重:同一请求内重复ID会导致同一执行单被多次推送WMS
List<Long> distinctInOrderIds = inOrderIds.stream().distinct().collect(Collectors.toList());
List<ExecutionStockInOrder> checkOrders = stockInOrderService.list(
new QueryWrapper<ExecutionStockInOrder>().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<Long> 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");
@@ -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<String> 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);
@@ -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<StockInOrder>().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<InMaterialDetail> toInsert = inMaterialDetails;
if (!StringUtils.isEmpty(inOrderNumber)) {
List<InMaterialDetail> existingDetails = materialDetailService.list(new QueryWrapper<InMaterialDetail>().lambda()
.eq(InMaterialDetail::getInOrderNumber, inOrderNumber)
.eq(InMaterialDetail::getDelFlag, 1));
Set<String> 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<StockInOrder>().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<String> inOrderNumberList = stockInOrderPOList.stream().map(StockInOrderPO::getInOrderNumber).collect(Collectors.toList());
// 收货单防重收货单号"RV-"+入库单号为确定性生成该单号收货单已存在历史重复下发/重复审核
// 已生成过时不再为对应入库单生成收货单避免同号收货单叠加导致收货管理详情明细翻倍
List<String> receiptNumbersToCheck = inOrderNumberList.stream().map(no -> "RV-" + no).collect(Collectors.toList());
Set<String> existingReceiptNumbers = stockReceiptOrderService.list(new QueryWrapper<StockReceiptOrder>().lambda()
.in(StockReceiptOrder::getReceiptOrderNumber, receiptNumbersToCheck)
.eq(StockReceiptOrder::getDelFlag, 1))
.stream().map(StockReceiptOrder::getReceiptOrderNumber).collect(Collectors.toSet());
List<StockInOrderPO> 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<InMaterialDetailPO> inMaterialDetailPOList = materialDetailService.queryListChildren(inMaterialDetailDO);
Map<String, List<InMaterialDetailPO>> inMaterialDetailListMap = inMaterialDetailPOList.stream().collect(Collectors.groupingBy(InMaterialDetailPO::getInOrderNumber));
List<StockReceiptOrderDO> stockReceiptOrderDOList = new ArrayList<>();
stockInOrderPOList.forEach(p -> {
pendingAuditOrders.forEach(p -> {
StockReceiptOrderDO stockReceiptOrderDO = new StockReceiptOrderDO();
BeanUtils.copyProperties(p, stockReceiptOrderDO);
stockReceiptOrderDO.setReceiptOrderNumber("RV-"+p.getInOrderNumber());