fix(oms): 老预约单(客户预约)下发生成的业务单补下单冻结钩子, 消除最后一个不冻结入口
- issueOrder 路径原直存业务单绕过 insert 冻结钩子, 模式1仓库下不冻结; 排查确认入口活跃在用(App端/下发链路近期均有功能提交), 予以补齐 - 新增 resolveStockLockMode/buildStockLockModeJsonByEntities/tryFreezeOnLegacyIssue 公开复用方法, compensateUnfreeze 改 public - 下发顺序重排: 明细真实判定stamp → 单头JSON真实值 → 冻结(失败阻断下发, R2无批次同insert语义) → try-catch落库(失败补偿解冻, 与insert"冻结先行+补偿"模式一致) - 业务单号沿用老预约单号, 冻结幂等/取消下发/删除/反审解冻均以此单号闭环 - 至此模式1仓库下所有业务单入口(新增/批量/Excel单工作簿/多工作簿导入/编辑/老预约单下发)全部冻结
This commit is contained in:
+85
-1
@@ -378,6 +378,90 @@ public class ReservationStockOutOrderDomainService {
|
|||||||
return JSON.toJSONString(warehouseJsonMap.values());
|
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<ReservationOutMaterialDetail> details, Long defaultWarehouseId) {
|
||||||
|
if (CollectionUtils.isEmpty(details)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<Long, Map<String, Object>> warehouseJsonMap = new LinkedHashMap<>();
|
||||||
|
for (ReservationOutMaterialDetail detail : details) {
|
||||||
|
Long warehouseId = detail.getWarehouseId() != null ? detail.getWarehouseId() : defaultWarehouseId;
|
||||||
|
warehouseJsonMap.computeIfAbsent(warehouseId, k -> {
|
||||||
|
Map<String, Object> 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<ReservationOutMaterialDetail> details, Long defaultWarehouseId) {
|
||||||
|
if (CollectionUtils.isEmpty(details)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<OutMaterialDetail> 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库存再落库(失败抛异常阻断保存)。
|
* 出库单保存时下单冻结:筛选下单冻结模式仓库的明细,先冻结WMS库存再落库(失败抛异常阻断保存)。
|
||||||
* ★下单冻结模式仓库明细强制必选库存批次:未关联 materialInventoryId 无法确定冻结哪条库存行,直接阻断。
|
* ★下单冻结模式仓库明细强制必选库存批次:未关联 materialInventoryId 无法确定冻结哪条库存行,直接阻断。
|
||||||
@@ -464,7 +548,7 @@ public class ReservationStockOutOrderDomainService {
|
|||||||
* 本地落库失败的补偿解冻(对齐执行并下发的补偿模式):补偿失败仅告警,
|
* 本地落库失败的补偿解冻(对齐执行并下发的补偿模式):补偿失败仅告警,
|
||||||
* 凭业务单号在WMS冻结记录(out_order_freeze_record,relate_no 前缀=业务单号)中人工处理
|
* 凭业务单号在WMS冻结记录(out_order_freeze_record,relate_no 前缀=业务单号)中人工处理
|
||||||
*/
|
*/
|
||||||
private void compensateUnfreeze(String outOrderNumber) {
|
public void compensateUnfreeze(String outOrderNumber) {
|
||||||
try {
|
try {
|
||||||
OmsFreezeInventoryParam param = new OmsFreezeInventoryParam();
|
OmsFreezeInventoryParam param = new OmsFreezeInventoryParam();
|
||||||
param.setOutOrderNumber(outOrderNumber);
|
param.setOutOrderNumber(outOrderNumber);
|
||||||
|
|||||||
+19
-11
@@ -431,20 +431,14 @@ public class ReserveStockOutOrderDomainService {
|
|||||||
Long shipperId = reservationStockOutOrder.getShipperId();
|
Long shipperId = reservationStockOutOrder.getShipperId();
|
||||||
String jsbz = reservationStockInOrderMapper.getJsbz(shipperId);
|
String jsbz = reservationStockInOrderMapper.getJsbz(shipperId);
|
||||||
reservationStockOutOrder.setSettlementCurrency(jsbz);
|
reservationStockOutOrder.setSettlementCurrency(jsbz);
|
||||||
// 单头库存锁定模式JSON:老预约单下发的业务单不走下单冻结钩子(WMS分配时才冻结),固定 2-分配库存冻结(单仓单元素)
|
// 单头库存锁定模式JSON:延迟到明细构建后按真实判定组装(见下方 buildStockLockModeJsonByEntities)
|
||||||
Map<String, Object> 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)));
|
|
||||||
// 保留旧单号,用于查询源明细数据
|
|
||||||
String oldOutOrderNumber = reservationStockOutOrder.getOutOrderNumber();
|
String oldOutOrderNumber = reservationStockOutOrder.getOutOrderNumber();
|
||||||
// 生成出库业务单号,防止与已生成的出库业务单号重复
|
// 生成出库业务单号,防止与已生成的出库业务单号重复
|
||||||
String newOrderNumber = reservationStockOutOrderDomainService.generateReserveOrderNumber();
|
String newOrderNumber = reservationStockOutOrderDomainService.generateReserveOrderNumber();
|
||||||
// reservationStockOutOrder.setOutOrderNumber(newOrderNumber);
|
// reservationStockOutOrder.setOutOrderNumber(newOrderNumber);
|
||||||
// reservationStockOutOrder.setBusinessOrderNo(newOrderNumber);
|
// reservationStockOutOrder.setBusinessOrderNo(newOrderNumber);
|
||||||
reservationStockOutOrderService.save(reservationStockOutOrder);
|
// 查询老预约单明细并构建业务单明细(stamp 真实判定的库存锁定模式)——须先于冻结钩子与落库
|
||||||
List<ReserveOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReserveOutMaterialDetail>().lambda().eq(ReserveOutMaterialDetail::getOutOrderNumber, oldOutOrderNumber));
|
List<ReserveOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReserveOutMaterialDetail>().lambda().eq(ReserveOutMaterialDetail::getOutOrderNumber, oldOutOrderNumber));
|
||||||
List<ReservationOutMaterialDetail> inMaterialDetailList = new ArrayList<>();
|
List<ReservationOutMaterialDetail> inMaterialDetailList = new ArrayList<>();
|
||||||
int index = 1;
|
int index = 1;
|
||||||
@@ -452,15 +446,29 @@ public class ReserveStockOutOrderDomainService {
|
|||||||
ReservationOutMaterialDetail reservationOutMaterialDetail = new ReservationOutMaterialDetail();
|
ReservationOutMaterialDetail reservationOutMaterialDetail = new ReservationOutMaterialDetail();
|
||||||
BeanUtils.copyProperties(reserveOutMaterialDetail, reservationOutMaterialDetail,"materialDetailId");
|
BeanUtils.copyProperties(reserveOutMaterialDetail, reservationOutMaterialDetail,"materialDetailId");
|
||||||
// reservationOutMaterialDetail.setOutOrderNumber(newOrderNumber); // 使用新单号保持与主表关联
|
// reservationOutMaterialDetail.setOutOrderNumber(newOrderNumber); // 使用新单号保持与主表关联
|
||||||
// 老预约单下发生成的业务单不走下单冻结钩子(客户预约单在WMS分配时才冻结),明细模式固定为 2-分配库存冻结
|
// 明细库存锁定模式:按行仓库(空取单头)实时判定,与下单冻结钩子同口径
|
||||||
reservationOutMaterialDetail.setStockLockMode(2);
|
reservationOutMaterialDetail.setStockLockMode(reservationStockOutOrderDomainService.resolveStockLockMode(
|
||||||
|
reservationOutMaterialDetail.getWarehouseId() != null ? reservationOutMaterialDetail.getWarehouseId() : reservationStockOutOrder.getWarehouseId()));
|
||||||
String lot = generateDateSerialNumber();
|
String lot = generateDateSerialNumber();
|
||||||
lot = lot+"-"+index;
|
lot = lot+"-"+index;
|
||||||
index++;
|
index++;
|
||||||
reservationOutMaterialDetail.setLotNo(lot);
|
reservationOutMaterialDetail.setLotNo(lot);
|
||||||
inMaterialDetailList.add(reservationOutMaterialDetail);
|
inMaterialDetailList.add(reservationOutMaterialDetail);
|
||||||
}
|
}
|
||||||
|
// 单头库存锁定模式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);
|
reservationOutMaterialDetailService.saveBatch(inMaterialDetailList);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (frozen) {
|
||||||
|
// WMS 已冻结但本地落库失败 → 补偿解冻(老预约单保持待下发状态可重试,WMS 侧幂等)
|
||||||
|
reservationStockOutOrderDomainService.compensateUnfreeze(oldOutOrderNumber);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user