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 87ef47f9d..d35c93c7f 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 @@ -378,6 +378,90 @@ public class ReservationStockOutOrderDomainService { return JSON.toJSONString(warehouseJsonMap.values()); } + /** + * 行级库存锁定模式判定(公开供老预约单下发路径复用):1-下单冻结 2-分配库存冻结(默认) + */ + public Integer resolveStockLockMode(Long warehouseId) { + return isOrderFreezeWarehouse(warehouseId) ? 1 : 2; + } + + /** + * 实体版仓级库存锁定模式JSON(公开供老预约单下发路径复用): + * 按明细行仓库去重组装(LinkedHashMap 保序),行仓库为空取默认仓库; + * 明细行须已完成 stockLockMode stamp + */ + public String buildStockLockModeJsonByEntities(List details, Long defaultWarehouseId) { + if (CollectionUtils.isEmpty(details)) { + return null; + } + Map> warehouseJsonMap = new LinkedHashMap<>(); + for (ReservationOutMaterialDetail detail : details) { + Long warehouseId = detail.getWarehouseId() != null ? detail.getWarehouseId() : defaultWarehouseId; + warehouseJsonMap.computeIfAbsent(warehouseId, k -> { + Map item = new LinkedHashMap<>(); + item.put("warehouseId", k); + item.put("warehouseCode", detail.getWarehouseCode()); + item.put("warehouseName", detail.getWarehouseName()); + item.put("stockLockMode", detail.getStockLockMode() != null ? detail.getStockLockMode() : 2); + return item; + }); + } + return JSON.toJSONString(warehouseJsonMap.values()); + } + + /** + * 老预约单下发路径的下单冻结钩子(与 insert 的 tryFreezeOnCreate 同语义): + * 过滤模式 1 仓库明细 → R2 强制批次校验 → 调 WMS 冻结(可用-、冻结+,失败阻断下发)。 + * 幂等由 WMS 保证(同业务单号活跃冻结记录直接成功)。 + * + * @param businessOrderNumber 业务单号(老预约单路径=保留的老预约单号) + * @return true-已冻结(本地落库失败需补偿解冻 compensateUnfreeze);false-无模式 1 明细未冻结 + */ + public boolean tryFreezeOnLegacyIssue(String businessOrderNumber, List details, Long defaultWarehouseId) { + if (CollectionUtils.isEmpty(details)) { + return false; + } + List freezeDetails = new ArrayList<>(); + for (ReservationOutMaterialDetail detail : details) { + Long warehouseId = detail.getWarehouseId() != null ? detail.getWarehouseId() : defaultWarehouseId; + if (!isOrderFreezeWarehouse(warehouseId)) { + continue; + } + // ★R2:下单冻结必须知道冻结哪条库存行 + if (detail.getMaterialInventoryId() == null || detail.getMaterialInventoryId() <= 0) { + throw new ServiceException("出库单[" + (businessOrderNumber != null ? businessOrderNumber : "当前单据") + "]物料[" + + (detail.getMaterialCode() != null ? detail.getMaterialCode() : detail.getMaterialName()) + + "]未选择库存批次,下单冻结模式仓库必须先选择库存批次"); + } + OutMaterialDetail out = new OutMaterialDetail(); + out.setMaterialInventoryId(detail.getMaterialInventoryId()); + out.setMaterialBaseInfoId(detail.getMaterialBaseInfoId()); + out.setMaterialCode(detail.getMaterialCode()); + out.setMaterialName(detail.getMaterialName()); + out.setWarehouseId(warehouseId); + out.setQuantity(detail.getQuantity()); + out.setBatchNumber(detail.getBatchNumber()); + out.setBatchRefNo(detail.getBatchRefNo()); + out.setSheetRefNo(detail.getSheetRefNo()); + out.setBoxPalletNo(detail.getBoxPalletNo()); + out.setUniqueId(detail.getUniqueId()); + freezeDetails.add(out); + } + if (freezeDetails.isEmpty()) { + return false; + } + OmsFreezeInventoryParam param = new OmsFreezeInventoryParam(); + param.setOutOrderNumber(businessOrderNumber); + param.setDetails(freezeDetails); + fillOperator(param); + AjaxResult result = wmsServiceFeign.omsFreezeInventory(param); + if (result == null || !"200".equals(String.valueOf(result.get("code")))) { + throw new ServiceException("下单冻结库存失败,无法下发出库单:" + + (result == null ? "WMS服务不可用" : result.get("msg"))); + } + return true; + } + /** * 出库单保存时下单冻结:筛选下单冻结模式仓库的明细,先冻结WMS库存再落库(失败抛异常阻断保存)。 * ★下单冻结模式仓库明细强制必选库存批次:未关联 materialInventoryId 无法确定冻结哪条库存行,直接阻断。 @@ -464,7 +548,7 @@ public class ReservationStockOutOrderDomainService { * 本地落库失败的补偿解冻(对齐执行并下发的补偿模式):补偿失败仅告警, * 凭业务单号在WMS冻结记录(out_order_freeze_record,relate_no 前缀=业务单号)中人工处理 */ - private void compensateUnfreeze(String outOrderNumber) { + public void compensateUnfreeze(String outOrderNumber) { try { OmsFreezeInventoryParam param = new OmsFreezeInventoryParam(); param.setOutOrderNumber(outOrderNumber); diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderDomainService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderDomainService.java index acb65e0dc..0e853b52d 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderDomainService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderDomainService.java @@ -431,20 +431,14 @@ public class ReserveStockOutOrderDomainService { Long shipperId = reservationStockOutOrder.getShipperId(); String jsbz = reservationStockInOrderMapper.getJsbz(shipperId); reservationStockOutOrder.setSettlementCurrency(jsbz); - // 单头库存锁定模式JSON:老预约单下发的业务单不走下单冻结钩子(WMS分配时才冻结),固定 2-分配库存冻结(单仓单元素) - Map stockLockModeItem = new LinkedHashMap<>(); - stockLockModeItem.put("warehouseId", reservationStockOutOrder.getWarehouseId()); - stockLockModeItem.put("warehouseCode", reservationStockOutOrder.getWarehouseCode()); - stockLockModeItem.put("warehouseName", reservationStockOutOrder.getWarehouseName()); - stockLockModeItem.put("stockLockMode", 2); - reservationStockOutOrder.setStockLockModeJson(JSON.toJSONString(java.util.Collections.singletonList(stockLockModeItem))); - // 保留旧单号,用于查询源明细数据 + // 单头库存锁定模式JSON:延迟到明细构建后按真实判定组装(见下方 buildStockLockModeJsonByEntities) + // 保留旧单号,用于查询源明细数据(业务单沿用老预约单号,冻结/解冻/取消下发/反审均以此单号为键) String oldOutOrderNumber = reservationStockOutOrder.getOutOrderNumber(); // 生成出库业务单号,防止与已生成的出库业务单号重复 String newOrderNumber = reservationStockOutOrderDomainService.generateReserveOrderNumber(); // reservationStockOutOrder.setOutOrderNumber(newOrderNumber); // reservationStockOutOrder.setBusinessOrderNo(newOrderNumber); - reservationStockOutOrderService.save(reservationStockOutOrder); + // 查询老预约单明细并构建业务单明细(stamp 真实判定的库存锁定模式)——须先于冻结钩子与落库 List materialDetailList = outMaterialDetailService.list(new QueryWrapper().lambda().eq(ReserveOutMaterialDetail::getOutOrderNumber, oldOutOrderNumber)); List inMaterialDetailList = new ArrayList<>(); int index = 1; @@ -452,15 +446,29 @@ public class ReserveStockOutOrderDomainService { ReservationOutMaterialDetail reservationOutMaterialDetail = new ReservationOutMaterialDetail(); BeanUtils.copyProperties(reserveOutMaterialDetail, reservationOutMaterialDetail,"materialDetailId"); // reservationOutMaterialDetail.setOutOrderNumber(newOrderNumber); // 使用新单号保持与主表关联 - // 老预约单下发生成的业务单不走下单冻结钩子(客户预约单在WMS分配时才冻结),明细模式固定为 2-分配库存冻结 - reservationOutMaterialDetail.setStockLockMode(2); + // 明细库存锁定模式:按行仓库(空取单头)实时判定,与下单冻结钩子同口径 + reservationOutMaterialDetail.setStockLockMode(reservationStockOutOrderDomainService.resolveStockLockMode( + reservationOutMaterialDetail.getWarehouseId() != null ? reservationOutMaterialDetail.getWarehouseId() : reservationStockOutOrder.getWarehouseId())); String lot = generateDateSerialNumber(); lot = lot+"-"+index; index++; reservationOutMaterialDetail.setLotNo(lot); inMaterialDetailList.add(reservationOutMaterialDetail); } - reservationOutMaterialDetailService.saveBatch(inMaterialDetailList); + // 单头库存锁定模式JSON(仓级,真实判定值,元素不含执行单号) + reservationStockOutOrder.setStockLockModeJson(reservationStockOutOrderDomainService.buildStockLockModeJsonByEntities(inMaterialDetailList, reservationStockOutOrder.getWarehouseId())); + // ★下单冻结钩子(与 insert 的 tryFreezeOnCreate 同语义):模式 1 仓库明细先冻结 WMS 库存,失败阻断下发 + boolean frozen = reservationStockOutOrderDomainService.tryFreezeOnLegacyIssue(oldOutOrderNumber, inMaterialDetailList, reservationStockOutOrder.getWarehouseId()); + try { + reservationStockOutOrderService.save(reservationStockOutOrder); + reservationOutMaterialDetailService.saveBatch(inMaterialDetailList); + } catch (Exception e) { + if (frozen) { + // WMS 已冻结但本地落库失败 → 补偿解冻(老预约单保持待下发状态可重试,WMS 侧幂等) + reservationStockOutOrderDomainService.compensateUnfreeze(oldOutOrderNumber); + } + throw e; + } return update; }