fix(oms): 出库反审核心改动(下发自愈/运输链路软删/issueStatus已驳回/uniqueId置空/仓库兜底切换)

This commit is contained in:
rcx
2026-09-11 14:17:17 +08:00
parent 2094ea31cd
commit f9a50ae353
@@ -18,6 +18,10 @@ import com.mhd.oms.domain.businessDeliveryDetailsLink.repository.facade.IBusines
import com.mhd.oms.domain.businessDeliveryDetailsLink.repository.mapper.BusinessDeliveryDetailsLinkMapper;
import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder;
import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper;
import com.mhd.oms.domain.businessDocumentCargoMulti.entity.BusinessDocumentCargoMulti;
import com.mhd.oms.domain.businessDocumentCargoMulti.repository.mapper.BusinessDocumentCargoMultiMapper;
import com.mhd.oms.domain.businessDocumentAddressMulti.entity.BusinessDocumentAddressMulti;
import com.mhd.oms.domain.businessDocumentAddressMulti.repository.mapper.BusinessDocumentAddressMultiMapper;
import com.mhd.oms.domain.executeOrder.entity.ExecuteOrder;
import com.mhd.oms.domain.executeOrder.repository.mapper.ExecuteOrderMapper;
import com.mhd.oms.domain.executionInMaterialDetail.entity.ExecutionInMaterialDetail;
@@ -57,6 +61,7 @@ import com.mhd.oms.domain.reservationStockOutOrder.repository.todo.ReservationSt
import com.mhd.oms.domain.reserveStockOutOrder.repository.mapper.ReserveStockOutOrderMapper;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.WlhyServiceFeign;
import com.mhd.system.api.domain.*;
import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
@@ -122,6 +127,14 @@ public class ReservationStockOutOrderDomainService {
private MaterialBaseInfoResolveService materialBaseInfoResolveService;
@Resource
private ReservationInventoryAdjustmentRecordMapper inventoryAdjustmentRecordMapper;
@Resource
private WlhyServiceFeign wlhyServiceFeign;
@Autowired
private BusinessDocumentCargoMultiMapper businessDocumentCargoMultiMapper;
@Autowired
private BusinessDocumentAddressMultiMapper businessDocumentAddressMultiMapper;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
/**
* 分页查询出库单列表
@@ -491,6 +504,122 @@ public class ReservationStockOutOrderDomainService {
}
}
/**
* 重新下发前自愈清理: 历史版本反审/取消下发软删的执行明细(del_flag=2)仍占用unique_id
* (EXECUTION_OUT_MATERIAL_DETAIL 唯一约束不含del_flag), 重新下发从预留明细复制同一unique_id会撞键;
* 归档行的unique_id无功能用途(仅活动明细展示反查批号用到), 统一置空释放键位, 幂等可重复执行
*/
private void releaseArchivedDetailUniqueId(String businessOutOrderNumber) {
List<String> executionOrderNumbers = executionStockOutOrderService.list(new QueryWrapper<ExecutionStockOutOrder>().lambda()
.eq(ExecutionStockOutOrder::getBusinessOutOrderNumber, businessOutOrderNumber))
.stream()
.map(ExecutionStockOutOrder::getOutOrderNumber)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (executionOrderNumbers.isEmpty()) {
return;
}
executionOutMaterialDetailService.update(null, new UpdateWrapper<ExecutionOutMaterialDetail>().lambda()
.set(ExecutionOutMaterialDetail::getUniqueId, null)
.eq(ExecutionOutMaterialDetail::getDelFlag, 2)
.in(ExecutionOutMaterialDetail::getOutOrderNumber, executionOrderNumbers));
}
/**
* 反审联动软删运输链路(编辑"是否运输"=是 且审核通过后生成的链):
* 运输业务单(business_document_order) -> 运输执行单(execute_order) -> TMS货源单/运单/运输作业单(wlhy外部服务)。
* 定位: 出库单.businessNumber(货源单号) + business_delivery_details_link.invoiceNumber=出库单号(orderId/拆单subOrderId);
* 执行单逐个调 TMS deleteChainByExecuteOrderNumber 软删TMS侧整链(未下发过则TMS无数据, 幂等空操作), 失败抛异常整批回滚;
* 本地软删执行单/运输业务单及其货品/地址明细/配送明细链; 无关联运输单时直接跳过。
*/
private void reverseTransportChain(ReservationStockOutOrder byId) {
String outOrderNumber = byId.getOutOrderNumber();
Set<Long> transportOrderIds = new HashSet<>();
if (StringUtils.isNotBlank(byId.getBusinessNumber())) {
businessDocumentOrderMapper.selectList(new LambdaQueryWrapper<BusinessDocumentOrder>()
.eq(BusinessDocumentOrder::getGoodsNumber, byId.getBusinessNumber())
.eq(BusinessDocumentOrder::getDelFlag, 1))
.forEach(order -> transportOrderIds.add(order.getId()));
}
for (BusinessDeliveryDetailsLink link : businessDeliveryDetailsLinkService.list(new LambdaQueryWrapper<BusinessDeliveryDetailsLink>()
.eq(BusinessDeliveryDetailsLink::getInvoiceNumber, outOrderNumber)
.eq(BusinessDeliveryDetailsLink::getDelFlag, 1))) {
if (link.getOrderId() != null) {
transportOrderIds.add(link.getOrderId());
}
if (StringUtils.isNotBlank(link.getSubOrderId())) {
try {
transportOrderIds.add(Long.valueOf(link.getSubOrderId()));
} catch (NumberFormatException ignore) {
//拆单子单id非纯数字的历史脏数据, 忽略
}
}
}
if (transportOrderIds.isEmpty()) {
return;
}
for (Long transportOrderId : transportOrderIds) {
BusinessDocumentOrder transportOrder = businessDocumentOrderMapper.selectById(transportOrderId);
if (transportOrder == null) {
continue;
}
// 1. 执行单: 先软删TMS侧整链(货源单->运单->作业单), 再软删本地执行单
// (运输业务单已被手工删除时其执行单/TMS链可能仍存活, 同样需要清理)
List<ExecuteOrder> executeOrders = executeOrderMapper.selectList(new LambdaQueryWrapper<ExecuteOrder>()
.eq(ExecuteOrder::getBusinessDocumentId, transportOrderId)
.eq(ExecuteOrder::getDelFlag, 1));
for (ExecuteOrder executeOrder : executeOrders) {
if (StringUtils.isNotBlank(executeOrder.getGoodsNumber())) {
AjaxResult tmsResult = wlhyServiceFeign.deleteChainByExecuteOrderNumber(executeOrder.getGoodsNumber());
if (tmsResult == null || !"200".equals(String.valueOf(tmsResult.get("code")))) {
throw new ServiceException("出库单[" + outOrderNumber + "]反审失败: 运输执行单货源单号["
+ executeOrder.getGoodsNumber() + "]TMS链路软删失败: "
+ (tmsResult == null ? "TMS服务不可用" : tmsResult.get("msg")));
}
}
executeOrder.setDelFlag(2);
executeOrderMapper.updateById(executeOrder);
}
// 2. 软删运输业务单及其货品/地址明细(已软删的跳过更新)
if (!Integer.valueOf(2).equals(transportOrder.getDelFlag())) {
transportOrder.setDelFlag(2);
businessDocumentOrderMapper.updateById(transportOrder);
businessDocumentCargoMultiMapper.update(null, new LambdaUpdateWrapper<BusinessDocumentCargoMulti>()
.set(BusinessDocumentCargoMulti::getDelFlag, 2)
.eq(BusinessDocumentCargoMulti::getOrderId, String.valueOf(transportOrderId))
.eq(BusinessDocumentCargoMulti::getDelFlag, 1));
businessDocumentAddressMultiMapper.update(null, new LambdaUpdateWrapper<BusinessDocumentAddressMulti>()
.set(BusinessDocumentAddressMulti::getDelFlag, 2)
.eq(BusinessDocumentAddressMulti::getOrderId, String.valueOf(transportOrderId))
.eq(BusinessDocumentAddressMulti::getDelFlag, 1));
}
}
// 3. 配送明细链软删(仅限指向本次删除的运输业务单, 保留指向其他运输单的链路)
List<String> transportOrderIdStrings = transportOrderIds.stream().map(String::valueOf).collect(Collectors.toList());
businessDeliveryDetailsLinkService.update(null, new LambdaUpdateWrapper<BusinessDeliveryDetailsLink>()
.set(BusinessDeliveryDetailsLink::getDelFlag, 2)
.eq(BusinessDeliveryDetailsLink::getInvoiceNumber, outOrderNumber)
.and(wrapper -> wrapper.in(BusinessDeliveryDetailsLink::getOrderId, transportOrderIds)
.or().in(BusinessDeliveryDetailsLink::getSubOrderId, transportOrderIdStrings))
.eq(BusinessDeliveryDetailsLink::getDelFlag, 1));
}
/**
* 获取仓库信息(Feign优先, system服务失败/降级时本地跨库兜底), 统一委托共享组件
*/
private WarehouseFeignPO getWarehouseInfoWithFallback(Long warehouseId) {
return warehouseInfoApplicationService.getWarehouseInfo(warehouseId);
}
/**
* 获取仓库单号生成规则(下发生成执行单号用): 优先 Feign 调 system 服务;
* Feign 异常/降级/返回非200时回退本地跨库直查 NGWL_TEST_SYSTEM.WAREHOUSE(同库同表, 与 getWarehouseCode 等查询同数据源),
* 避免 system 服务抖动阻断下发; 两侧都取不到才报错并带出 Feign 真实返回原因。
*/
private WarehouseFeignPO getWarehouseOrderNoRule(Long warehouseId) {
return getWarehouseInfoWithFallback(warehouseId);
}
/**
* @description 审核出库单
* @author ZhouGY
@@ -531,6 +660,8 @@ public class ReservationStockOutOrderDomainService {
if (activeExecutionCount > 0) {
throw new ServiceException("出库单[" + inOrderNumber + "]已下发,请勿重复下发;如需重新下发请先取消下发");
}
// 自愈: 历史版本反审/取消下发的归档明细可能仍占用unique_id, 先置空释放键位再重新下发
releaseArchivedDetailUniqueId(inOrderNumber);
List<String> executionInOrderNumbers = new ArrayList<>();
List<ReservationOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReservationOutMaterialDetail>().lambda().eq(ReservationOutMaterialDetail::getOutOrderNumber, inOrderNumber));
@@ -566,14 +697,8 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrder.setBusinessOutOrderNumber(inOrderNumber);
// reservationStockInOrder.setOutOrderNumber(generateExecutionOrderNumber(warehouseCode));
//设置单号
Long warehouseId1 = reservationStockInOrder.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId1);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
//设置单号(仓库单号规则: Feign优先, system服务失败/降级时本地跨库兜底)
WarehouseFeignPO warehouseFeignPO = getWarehouseOrderNoRule(reservationStockInOrder.getWarehouseId());
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -662,6 +787,8 @@ public class ReservationStockOutOrderDomainService {
materialDetails.forEach(executionOutMaterialDetail -> {
executionOutMaterialDetailService.update(null, new UpdateWrapper<ExecutionOutMaterialDetail>().lambda()
.set(ExecutionOutMaterialDetail::getDelFlag, 2)
// 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发时从预留明细复制的同一uniqueId撞键
.set(ExecutionOutMaterialDetail::getUniqueId, null)
.eq(ExecutionOutMaterialDetail::getMaterialDetailId, executionOutMaterialDetail.getMaterialDetailId()));
});
executionStockOutOrderService.update(null, new UpdateWrapper<ExecutionStockOutOrder>().lambda()
@@ -676,7 +803,8 @@ public class ReservationStockOutOrderDomainService {
* @description 反审出库业务单(按前端展示字段 issueStatus 判断可反审范围: 2-已审核(未出库) / 4-已出库 均可反审;
* 0-已创建无需反审、3-已驳回可直接编辑; status 字段在出库完成后不一定同步为9, 故不作为判断依据,
* 仅兜底排除 10-已取消/11-已关闭)。
* 反审将单据回退到最原始状态(1-已创建, issueStatus=0)并打上反审标记:
* 反审将单据回退到已创建(status=1)且审核状态置为已驳回(issueStatus=3, 与入库侧反审同口径, 可编辑→重新审核→重新下发)
* 并打上反审标记:
* WMS 侧按出库单实际状态分支还原库存(已出库: 实物库存还原+推OMS; 未出库: 仅还原分配/冻结占用)、
* 清理执行单据(出库单/明细/拣货/复核/交接/配送, 软删+reverseFlag=1 划归【已反审】tab)、
* 每个执行出库单生成1条出库异常记录【反审记录】(out_order_abnormal, abnormalType=3);
@@ -738,6 +866,8 @@ public class ReservationStockOutOrderDomainService {
executionOutMaterialDetailService.update(null, new UpdateWrapper<ExecutionOutMaterialDetail>().lambda()
.set(ExecutionOutMaterialDetail::getDelFlag, 2)
.set(ExecutionOutMaterialDetail::getReverseFlag, 1)
// 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发时从预留明细复制的同一uniqueId撞键
.set(ExecutionOutMaterialDetail::getUniqueId, null)
.eq(ExecutionOutMaterialDetail::getMaterialDetailId, executionOutMaterialDetail.getMaterialDetailId()));
});
executionStockOutOrderService.update(null, new UpdateWrapper<ExecutionStockOutOrder>().lambda()
@@ -750,15 +880,20 @@ public class ReservationStockOutOrderDomainService {
.eq(ReservationInventoryAdjustmentRecord::getExecutionOrderNumber, executionOutOrderNumber)
.eq(ReservationInventoryAdjustmentRecord::getDelFlag, 1));
}
// 反审联动: 软删关联运输链路(运输业务单/运输执行单/TMS货源单-运单-作业单), 无关联运输单时跳过
reverseTransportChain(byId);
}
// 主单回退到已创建并打反审标记; 更新条件带 issueStatus in (2,4) + 排除已取消/已关闭 兜底防并发重复反审
// issueStatus 回退到 0-已创建(前端展示映射: 0-已创建 2-已审核 3-已驳回 4-已出库);
// issueStatus 回退到 3-已驳回(前端展示映射: 0-已创建 2-已审核 3-已驳回 4-已出库; 与入库侧反审同口径,
// 已驳回态可编辑→重新审核→重新下发, 重新审核通过时 reverse_status 自动复位 0 离开已反审tab);
// 清空执行出库单号——执行单已随反审软删, 前端"是否执行"列由该字段有无值推导, 不清空会仍显示已执行
return stockOutOrderService.update(null, new UpdateWrapper<ReservationStockOutOrder>().lambda()
.set(ReservationStockOutOrder::getStatus, 1)
.set(ReservationStockOutOrder::getIssueStatus, 0)
.set(ReservationStockOutOrder::getIssueStatus, 3)
.set(ReservationStockOutOrder::getExecutionOutOrderNumber, null)
// 运输业务单已随反审软删, 货源单号一并清空, 重新审核后重建运输链路时自动回填
.set(ReservationStockOutOrder::getBusinessNumber, null)
.set(ReservationStockOutOrder::getIssueId, null)
.set(ReservationStockOutOrder::getIssueName, null)
.set(ReservationStockOutOrder::getIssueTime, null)
@@ -816,6 +951,8 @@ public class ReservationStockOutOrderDomainService {
String warehouseCode1 = byId.getWarehouseCode();
String warehouseName1 = byId.getWarehouseName();
String inOrderNumber = byId.getOutOrderNumber();
// 自愈: 历史版本反审/取消下发的归档明细可能仍占用unique_id, 先置空释放键位再重新下发
releaseArchivedDetailUniqueId(inOrderNumber);
List<ReservationOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReservationOutMaterialDetail>().lambda().eq(ReservationOutMaterialDetail::getOutOrderNumber, inOrderNumber));
// 按照 warehouseId 分组
@@ -890,14 +1027,8 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrder.setBusinessOutOrderNumber(inOrderNumber);
//设置单号
// Long warehouseId1 = reservationStockInOrder.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
//设置单号(仓库单号规则: Feign优先, system服务失败/降级时本地跨库兜底)
WarehouseFeignPO warehouseFeignPO = getWarehouseOrderNoRule(warehouseId);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {