diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockOutOrder/service/ReservationStockOutOrderDomainService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockOutOrder/service/ReservationStockOutOrderDomainService.java index 5bcdaafbd..25a541758 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockOutOrder/service/ReservationStockOutOrderDomainService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockOutOrder/service/ReservationStockOutOrderDomainService.java @@ -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 executionOrderNumbers = executionStockOutOrderService.list(new QueryWrapper().lambda() + .eq(ExecutionStockOutOrder::getBusinessOutOrderNumber, businessOutOrderNumber)) + .stream() + .map(ExecutionStockOutOrder::getOutOrderNumber) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (executionOrderNumbers.isEmpty()) { + return; + } + executionOutMaterialDetailService.update(null, new UpdateWrapper().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 transportOrderIds = new HashSet<>(); + if (StringUtils.isNotBlank(byId.getBusinessNumber())) { + businessDocumentOrderMapper.selectList(new LambdaQueryWrapper() + .eq(BusinessDocumentOrder::getGoodsNumber, byId.getBusinessNumber()) + .eq(BusinessDocumentOrder::getDelFlag, 1)) + .forEach(order -> transportOrderIds.add(order.getId())); + } + for (BusinessDeliveryDetailsLink link : businessDeliveryDetailsLinkService.list(new LambdaQueryWrapper() + .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 executeOrders = executeOrderMapper.selectList(new LambdaQueryWrapper() + .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() + .set(BusinessDocumentCargoMulti::getDelFlag, 2) + .eq(BusinessDocumentCargoMulti::getOrderId, String.valueOf(transportOrderId)) + .eq(BusinessDocumentCargoMulti::getDelFlag, 1)); + businessDocumentAddressMultiMapper.update(null, new LambdaUpdateWrapper() + .set(BusinessDocumentAddressMulti::getDelFlag, 2) + .eq(BusinessDocumentAddressMulti::getOrderId, String.valueOf(transportOrderId)) + .eq(BusinessDocumentAddressMulti::getDelFlag, 1)); + } + } + // 3. 配送明细链软删(仅限指向本次删除的运输业务单, 保留指向其他运输单的链路) + List transportOrderIdStrings = transportOrderIds.stream().map(String::valueOf).collect(Collectors.toList()); + businessDeliveryDetailsLinkService.update(null, new LambdaUpdateWrapper() + .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 executionInOrderNumbers = new ArrayList<>(); List materialDetailList = outMaterialDetailService.list(new QueryWrapper().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().lambda() .set(ExecutionOutMaterialDetail::getDelFlag, 2) + // 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发时从预留明细复制的同一uniqueId撞键 + .set(ExecutionOutMaterialDetail::getUniqueId, null) .eq(ExecutionOutMaterialDetail::getMaterialDetailId, executionOutMaterialDetail.getMaterialDetailId())); }); executionStockOutOrderService.update(null, new UpdateWrapper().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().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().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().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 materialDetailList = outMaterialDetailService.list(new QueryWrapper().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())) {