Merge branch 'dev-ty1.2' into test
# Conflicts: # mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java
This commit is contained in:
+36
-6
@@ -66,6 +66,7 @@ import com.mhd.wms.domain.stockOutOrder.repository.mapper.StockOutOrderMapper;
|
||||
import com.mhd.wms.domain.stockOutOrder.repository.po.StockOutOrderPO;
|
||||
import com.mhd.wms.domain.stockOutOrder.repository.todo.StockOutOrderDO;
|
||||
import com.mhd.wms.domain.stockOutOrder.service.StockOutOrderDomainService;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.service.OutOrderFreezeRecordDomainService;
|
||||
import com.mhd.wms.interfaces.dto.stockOutOrder.StockOutOrderDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
@@ -99,6 +100,8 @@ public class StockOutOrderApplicationService {
|
||||
@Autowired
|
||||
private StockOutOrderDomainService stockOutOrderDomainService;
|
||||
@Autowired
|
||||
private OutOrderFreezeRecordDomainService outOrderFreezeRecordDomainService;
|
||||
@Autowired
|
||||
private OutMaterialDetailDomainService outMaterialDetailDomainService;
|
||||
@Autowired
|
||||
private MaterialInventoryDomainService materialInventoryDomainService;
|
||||
@@ -1062,6 +1065,13 @@ public class StockOutOrderApplicationService {
|
||||
}
|
||||
// 当前物料的可用库存
|
||||
BigDecimal availableStock = materialInventory.getAllocationQuantity();
|
||||
// 下单冻结模式:加回本单该行的冻结记录量(下单时已冻结导致可用=0,冻结行会被跳过
|
||||
// 导致分配不到量;承接时数量不动,此处仅校验/选行口径)
|
||||
BigDecimal orderFrozen = outOrderFreezeRecordDomainService.sumOrderFreezeQuantity(
|
||||
stockOutOrderDb.getBusinessOrderNo(), materialInventory.getMaterialInventoryId());
|
||||
if (orderFrozen.compareTo(BigDecimal.ZERO) > 0) {
|
||||
availableStock = availableStock.add(orderFrozen);
|
||||
}
|
||||
// 如果当前物料没有库存,跳过
|
||||
if (availableStock == null || availableStock.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
@@ -1149,14 +1159,23 @@ public class StockOutOrderApplicationService {
|
||||
|
||||
// 当前物料的可用库存
|
||||
BigDecimal availableStock = materialInventory.getAllocationQuantity();
|
||||
// 下单冻结模式:加回本单该行的冻结记录量(下单时已冻结导致可用=0,纯可用口径会把冻结明细
|
||||
// 跳过/报错,永远到不了 batchAssignUpdate 的承接分支;承接时数量不动,此处仅校验口径)
|
||||
BigDecimal orderFrozen = outOrderFreezeRecordDomainService.sumOrderFreezeQuantity(
|
||||
stockOutOrderDb.getBusinessOrderNo(), materialInventoryId);
|
||||
if (orderFrozen.compareTo(BigDecimal.ZERO) > 0) {
|
||||
availableStock = availableStock.add(orderFrozen);
|
||||
}
|
||||
// 如果当前物料没有库存,跳过
|
||||
if (availableStock == null || availableStock.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (availableStock.compareTo(remainingQuantity) < 0) {
|
||||
throw new ServiceException(
|
||||
String.format("出库单[%s]物料[%s]可用库存不足,计划数量:%s,当前可用库存:%s",
|
||||
outOrderNumber, materialDesc, remainingQuantity, availableStock)
|
||||
String.format("出库单[%s]物料[%s]可用库存不足,计划数量:%s,当前可用库存:%s(含下单冻结%s)",
|
||||
outOrderNumber, materialDesc, remainingQuantity,
|
||||
availableStock.stripTrailingZeros().toPlainString(),
|
||||
orderFrozen.stripTrailingZeros().toPlainString())
|
||||
);
|
||||
}
|
||||
// detail.setMaterialInventoryId(materialInventory.getMaterialInventoryId());
|
||||
@@ -1187,9 +1206,12 @@ public class StockOutOrderApplicationService {
|
||||
/**
|
||||
* OMS下发前库存校验:按库存ID聚合各明细计划数量,逐一比对WMS可用库存,
|
||||
* 全部比对完成后汇总所有不足项一次性抛错。用于OMS在下发推送前拦截
|
||||
* (此前单据先落库、分配才失败,会在WMS残留"已审核"状态的垃圾单)
|
||||
* (此前单据先落库、分配才失败,会在WMS残留"已审核"状态的垃圾单)。
|
||||
* 下单冻结模式:businessOrderNumber 非空时行级可用量 = 库存可用 + 本单该行冻结记录量
|
||||
* (下单时已冻结导致可用=0,按纯可用校验必失败;冻结量加回后校验=防冻结漂移),
|
||||
* 冻结量<需求即报"下单冻结记录不足";重复下发保护原样保留。
|
||||
*/
|
||||
public void checkOmsAssignInventory(List<OutMaterialDetail> outMaterialDetails) {
|
||||
public void checkOmsAssignInventory(List<OutMaterialDetail> outMaterialDetails, String businessOrderNumber) {
|
||||
if (CollectionUtils.isEmpty(outMaterialDetails)) {
|
||||
return;
|
||||
}
|
||||
@@ -1232,9 +1254,17 @@ public class StockOutOrderApplicationService {
|
||||
}
|
||||
BigDecimal available = materialInventory.getAllocationQuantity() == null
|
||||
? BigDecimal.ZERO : materialInventory.getAllocationQuantity();
|
||||
// 下单冻结模式:可用量加回本单该行的冻结记录量(下单冻结导致可用=0,纯可用校验必失败);
|
||||
// 正常流冻结量≥需求(下单冻结/重锁保证),冻结量不足即报错=防冻结被异常释放的漂移
|
||||
BigDecimal frozen = BigDecimal.ZERO;
|
||||
if (businessOrderNumber != null && !businessOrderNumber.isEmpty()) {
|
||||
frozen = outOrderFreezeRecordDomainService.sumOrderFreezeQuantity(businessOrderNumber, materialInventoryId);
|
||||
available = available.add(frozen);
|
||||
}
|
||||
if (available.compareTo(need) < 0) {
|
||||
errors.add(String.format("物料[%s]可用库存不足:需要%s,当前可用%s",
|
||||
materialDesc, need.stripTrailingZeros().toPlainString(), available.stripTrailingZeros().toPlainString()));
|
||||
errors.add(String.format("物料[%s]可用库存不足:需要%s,当前可用%s(含下单冻结%s)",
|
||||
materialDesc, need.stripTrailingZeros().toPlainString(),
|
||||
available.stripTrailingZeros().toPlainString(), frozen.stripTrailingZeros().toPlainString()));
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
|
||||
+5
@@ -80,6 +80,8 @@ public class HandoverTaskOrderDomainService {
|
||||
@Autowired
|
||||
private IHandoverTaskOrderService handoverTaskOrderService;
|
||||
@Autowired
|
||||
private com.mhd.wms.domain.outOrderFreezeRecord.service.OutOrderFreezeRecordDomainService outOrderFreezeRecordDomainService;
|
||||
@Autowired
|
||||
private MaterialInventoryMapper materialInventoryMapper;
|
||||
@Autowired
|
||||
private OutMaterialDetailMapper outMaterialDetailMapper;
|
||||
@@ -591,6 +593,9 @@ public class HandoverTaskOrderDomainService {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 下单冻结模式:交接扣减完成(库存-、冻结-),执行单承接的冻结记录置已交接终态
|
||||
// (无冻结记录时幂等无操作;数量扣减本身两种模式同逻辑,零改动)
|
||||
outOrderFreezeRecordDomainService.markHandoverCompleted(orderNumber);
|
||||
return clearoutParamDOs;
|
||||
}
|
||||
|
||||
|
||||
+37
@@ -140,4 +140,41 @@ public interface MaterialInventoryMapper extends BaseMapper<MaterialInventory>
|
||||
public Map<String,Object> queryShipperParameters(@Param("shipperId") Long shipperId,@Param("parametersName") String parametersName);
|
||||
|
||||
public Map<String, Object> queryMaterialClassify(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* OMS下单冻结-条件扣减:可用数量-quantity、冻结数量+quantity(库存数量不变),
|
||||
* WHERE 带可用数量充足条件,返回影响行数(0=可用不足),由调用方判0抛异常整体回滚防止并发超卖
|
||||
* (不可用现有 GREATEST 截断版批量SQL——静默截为0会掩盖超卖)
|
||||
*
|
||||
* @param materialInventoryId 库存行ID
|
||||
* @param quantity 冻结数量
|
||||
* @return 影响行数:1-成功 0-可用数量不足
|
||||
*/
|
||||
public int omsFreezeDeductAvailable(@Param("materialInventoryId") Long materialInventoryId, @Param("quantity") BigDecimal quantity);
|
||||
|
||||
/**
|
||||
* OMS下单解冻-条件释放:可用数量+quantity、冻结数量-quantity(库存数量不变),
|
||||
* WHERE 带冻结数量充足条件,返回影响行数(0=冻结不足),由调用方判0抛异常防止重复解冻/负数
|
||||
*
|
||||
* @param materialInventoryId 库存行ID
|
||||
* @param quantity 释放数量
|
||||
* @return 影响行数:1-成功 0-冻结数量不足
|
||||
*/
|
||||
public int omsReleaseFreezeToAvailable(@Param("materialInventoryId") Long materialInventoryId, @Param("quantity") BigDecimal quantity);
|
||||
|
||||
/**
|
||||
* OMS下单冻结单出库反审-还原为冻结:实物库存+quantity、冻结数量+quantity(可用不变),
|
||||
* 并还原重量/体积/面积;将已出库扣减状态还原为"下单已冻结待出库"(与交接扣减 ckjj 库存-冻结- 互逆)
|
||||
*
|
||||
* @param materialInventoryId 库存行ID
|
||||
* @param quantity 还原数量
|
||||
* @param netWeight 还原净重
|
||||
* @param grossWeight 还原毛重
|
||||
* @param volume 还原体积
|
||||
* @param area 还原面积
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int omsRestoreInventoryToFrozen(@Param("materialInventoryId") Long materialInventoryId, @Param("quantity") BigDecimal quantity,
|
||||
@Param("netWeight") BigDecimal netWeight, @Param("grossWeight") BigDecimal grossWeight,
|
||||
@Param("volume") BigDecimal volume, @Param("area") BigDecimal area);
|
||||
}
|
||||
+39
-1
@@ -647,7 +647,7 @@ public class MaterialInventoryImpl extends ServiceImpl<MaterialInventoryMapper,
|
||||
}
|
||||
inventoryAdjustmentRecord.setMaterialBaseInfoId(materialInventory.getMaterialBaseInfoId());
|
||||
inventoryAdjustmentRecord.setAdjustType(1L);
|
||||
if ("库存冻结".equals(adjustAction)) {
|
||||
if ("库存冻结".equals(adjustAction) || "下单冻结".equals(adjustAction)) {
|
||||
inventoryAdjustmentRecord.setAdjustType(3L);
|
||||
}
|
||||
inventoryAdjustmentRecord.setOrganizationId(materialInventory.getOrganizationId());
|
||||
@@ -803,6 +803,44 @@ public class MaterialInventoryImpl extends ServiceImpl<MaterialInventoryMapper,
|
||||
inventoryAdjustmentRecord.setAdjustedGrossWeight(materialInventory.getGrossWeight());
|
||||
inventoryAdjustmentRecord.setAdjustedVolume(materialInventory.getVolume());
|
||||
inventoryAdjustmentRecord.setAdjustedArea(materialInventory.getArea());
|
||||
} else if ("下单冻结".equals(adjustAction)) {
|
||||
// OMS下单冻结(防超卖):总库存不变;可用减少、冻结增加(数量从可用转至冻结,与"库存冻结"同构)
|
||||
BigDecimal invBefore = nz(materialInventory.getInventoryQuantity());
|
||||
BigDecimal allocBefore = nz(materialInventory.getAllocationQuantity());
|
||||
BigDecimal freezeBefore = nz(materialInventory.getFreezeQuantity());
|
||||
inventoryAdjustmentRecord.setInventoryBeforeQuantity(invBefore);
|
||||
inventoryAdjustmentRecord.setInventoryAfterQuantity(invBefore);
|
||||
inventoryAdjustmentRecord.setAllocationBeforeQuantity(allocBefore);
|
||||
inventoryAdjustmentRecord.setAllocationAfterQuantity(allocBefore.subtract(qty));
|
||||
inventoryAdjustmentRecord.setFreezeBeforeQuantity(freezeBefore);
|
||||
inventoryAdjustmentRecord.setFreezeAfterQuantity(freezeBefore.add(qty));
|
||||
inventoryAdjustmentRecord.setNetWeight(nz(materialInventory.getNetWeight()));
|
||||
inventoryAdjustmentRecord.setGrossWeight(nz(materialInventory.getGrossWeight()));
|
||||
inventoryAdjustmentRecord.setVolume(nz(materialInventory.getVolume()));
|
||||
inventoryAdjustmentRecord.setArea(nz(materialInventory.getArea()));
|
||||
inventoryAdjustmentRecord.setAdjustedNetWeight(nz(materialInventory.getNetWeight()));
|
||||
inventoryAdjustmentRecord.setAdjustedGrossWeight(nz(materialInventory.getGrossWeight()));
|
||||
inventoryAdjustmentRecord.setAdjustedVolume(nz(materialInventory.getVolume()));
|
||||
inventoryAdjustmentRecord.setAdjustedArea(nz(materialInventory.getArea()));
|
||||
} else if ("下单解冻".equals(adjustAction)) {
|
||||
// OMS取消/编辑重锁释放:总库存不变;可用增加、冻结减少(与"取消分配"同构)
|
||||
BigDecimal invBefore = nz(materialInventory.getInventoryQuantity());
|
||||
BigDecimal allocBefore = nz(materialInventory.getAllocationQuantity());
|
||||
BigDecimal freezeBefore = nz(materialInventory.getFreezeQuantity());
|
||||
inventoryAdjustmentRecord.setInventoryBeforeQuantity(invBefore);
|
||||
inventoryAdjustmentRecord.setInventoryAfterQuantity(invBefore);
|
||||
inventoryAdjustmentRecord.setAllocationBeforeQuantity(allocBefore);
|
||||
inventoryAdjustmentRecord.setAllocationAfterQuantity(allocBefore.add(qty));
|
||||
inventoryAdjustmentRecord.setFreezeBeforeQuantity(freezeBefore);
|
||||
inventoryAdjustmentRecord.setFreezeAfterQuantity(freezeBefore.subtract(qty));
|
||||
inventoryAdjustmentRecord.setNetWeight(nz(materialInventory.getNetWeight()));
|
||||
inventoryAdjustmentRecord.setGrossWeight(nz(materialInventory.getGrossWeight()));
|
||||
inventoryAdjustmentRecord.setVolume(nz(materialInventory.getVolume()));
|
||||
inventoryAdjustmentRecord.setArea(nz(materialInventory.getArea()));
|
||||
inventoryAdjustmentRecord.setAdjustedNetWeight(nz(materialInventory.getNetWeight()));
|
||||
inventoryAdjustmentRecord.setAdjustedGrossWeight(nz(materialInventory.getGrossWeight()));
|
||||
inventoryAdjustmentRecord.setAdjustedVolume(nz(materialInventory.getVolume()));
|
||||
inventoryAdjustmentRecord.setAdjustedArea(nz(materialInventory.getArea()));
|
||||
}
|
||||
com.mhd.system.api.domain.InventoryAdjustmentRecord inventoryAdjustmentRecordOMS = new com.mhd.system.api.domain.InventoryAdjustmentRecord();
|
||||
BeanUtils.copyProperties(inventoryAdjustmentRecord, inventoryAdjustmentRecordOMS);
|
||||
|
||||
+61
-1
@@ -1,6 +1,7 @@
|
||||
package com.mhd.wms.domain.outMaterialDetail.repository.persistence;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
@@ -40,6 +41,7 @@ import com.mhd.wms.domain.outMaterialDetail.repository.po.OutMaterialDetailWithO
|
||||
import com.mhd.wms.domain.outMaterialDetail.repository.po.OutStockListMaterialAgg;
|
||||
import com.mhd.wms.domain.outMaterialDetail.repository.todo.OutMaterialDetailBaseDO;
|
||||
import com.mhd.wms.domain.outMaterialDetail.repository.todo.OutMaterialDetailDO;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.service.OutOrderFreezeRecordDomainService;
|
||||
import com.mhd.wms.domain.outMaterialDetailSerialNumber.entity.OutMaterialDetailSerialNumber;
|
||||
import com.mhd.wms.domain.outMaterialDetailSerialNumber.repository.facade.IOutMaterialDetailSerialNumberService;
|
||||
import com.mhd.wms.domain.overStock.repository.todo.QueryOrderOverStockDO;
|
||||
@@ -71,6 +73,8 @@ public class OutMaterialDetailImpl extends ServiceImpl<OutMaterialDetailMapper,
|
||||
@Autowired
|
||||
private OutMaterialDetailMapper outMaterialDetailMapper;
|
||||
@Autowired
|
||||
private OutOrderFreezeRecordDomainService outOrderFreezeRecordDomainService;
|
||||
@Autowired
|
||||
private IOutMaterialDetailSerialNumberService outMaterialDetailSerialNumberService;
|
||||
@Autowired
|
||||
private IdGenerator idGenerator;
|
||||
@@ -427,7 +431,9 @@ public class OutMaterialDetailImpl extends ServiceImpl<OutMaterialDetailMapper,
|
||||
.eq(StockOutOrder::getOutOrderNumber, outOrderNumber)
|
||||
.eq(StockOutOrder::getDelFlag, 1));
|
||||
if (!CollectionUtils.isEmpty(stockOutOrderList) && !"yang_pin_chu_ku".equals(stockOutOrderList.get(0).getBusinessType())) {
|
||||
xgkc(outMaterialDetailList,outOrderNumber,loginUser);
|
||||
// 下单冻结模式分流:本单冻结明细(其库存行存在本单活跃冻结记录)数量不动(下单时已冻结,
|
||||
// 再调 xgkc 会冻结翻倍/可用错扣),仅校验冻结量充足并做承接标记;其余明细走现有分配扣减
|
||||
assignByFreezeMode(outMaterialDetailList, outOrderNumber, loginUser, stockOutOrderList.get(0).getBusinessOrderNo());
|
||||
}
|
||||
//保存物料明细信息
|
||||
outMaterialDetailList.forEach(outMaterialDetail -> {
|
||||
@@ -1016,15 +1022,61 @@ public class OutMaterialDetailImpl extends ServiceImpl<OutMaterialDetailMapper,
|
||||
return stockOutOrderPO;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分配扣减按库存冻结模式分流:
|
||||
* 明细的库存行存在【本单】活跃冻结记录(OMS下单冻结模式:下单时已冻结)→ 库存数量不动
|
||||
* (再调 xgkc 会二次冻结导致冻结翻倍/可用错扣),仅校验本单冻结量充足并做执行单承接标记
|
||||
* (FIFO 按量承接,余量保留业务单冻结,支持部分分配),明细绑定由后续统一逻辑完成;
|
||||
* 其他明细(分配库存冻结模式=现有行为)→ 调 xgkc 分配扣减(可用-、冻结+)。
|
||||
* ★单据级隔离:冻结记录按 businessOrderNo + materialInventoryId 双条件过滤,
|
||||
* 双单冻结同一库存行时互不误消费;businessOrderNo 为空(历史单)→ 查不到记录安全走原逻辑。
|
||||
* @author gen
|
||||
* @date 2026/9/12
|
||||
*/
|
||||
private void assignByFreezeMode(List<OutMaterialDetail> outMaterialDetailList, String outOrderNumber,
|
||||
LoginUser loginUser, String businessOrderNo) {
|
||||
List<OutMaterialDetail> normalAssignList = new ArrayList<>();
|
||||
for (OutMaterialDetail detail : outMaterialDetailList) {
|
||||
if (detail.getNowAllocationQuantity() == null
|
||||
|| detail.getNowAllocationQuantity().compareTo(BigDecimal.ZERO) <= 0
|
||||
|| detail.getMaterialInventoryId() == null) {
|
||||
// 无效行交给现有逻辑自然跳过,保持行为一致
|
||||
normalAssignList.add(detail);
|
||||
continue;
|
||||
}
|
||||
BigDecimal frozenSum = outOrderFreezeRecordDomainService.sumOrderFreezeQuantity(businessOrderNo, detail.getMaterialInventoryId());
|
||||
if (frozenSum.compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (frozenSum.compareTo(detail.getNowAllocationQuantity()) < 0) {
|
||||
throw new ServiceException("物料[" + (detail.getMaterialCode() != null ? detail.getMaterialCode() : "")
|
||||
+ "]库存行[" + detail.getMaterialInventoryId() + "]本单下单冻结量[" + frozenSum
|
||||
+ "]小于本次分配数量[" + detail.getNowAllocationQuantity() + "],分配失败");
|
||||
}
|
||||
// 下单冻结模式明细:数量不动,冻结记录承接给执行单(本分配事务内,失败随事务回滚)
|
||||
outOrderFreezeRecordDomainService.undertakeFromFreezeRecords(
|
||||
businessOrderNo, detail.getMaterialInventoryId(), outOrderNumber, detail.getNowAllocationQuantity());
|
||||
} else {
|
||||
normalAssignList.add(detail);
|
||||
}
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(normalAssignList)) {
|
||||
xgkc(normalAssignList, outOrderNumber, loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void qxfp(List<OutMaterialDetailDO> outMaterialDetailList,String outOrderNumber,LoginUser loginUser) {
|
||||
StockOutOrder stockOutOrder = stockOutOrderMapper.selectOne(new QueryWrapper<StockOutOrder>().lambda()
|
||||
.eq(StockOutOrder::getDelFlag,1)
|
||||
.eq(StockOutOrder::getOutOrderNumber, outOrderNumber));
|
||||
// 下单冻结判据用业务单号(null 安全:查不到单/无单号时按无冻结记录处理,走原还原逻辑)
|
||||
String businessOrderNo = stockOutOrder == null ? null : stockOutOrder.getBusinessOrderNo();
|
||||
for (OutMaterialDetailDO outMaterialDetail : outMaterialDetailList) {
|
||||
Long materialInventoryId = outMaterialDetail.getMaterialInventoryId();
|
||||
BigDecimal allocationQuantity = outMaterialDetail.getActualQuantity();
|
||||
MaterialInventory materialInventoryPO = materialInventoryMapper.selectById(materialInventoryId);
|
||||
if (materialInventoryPO != null) {
|
||||
// 下单冻结模式明细(本单该库存行存在活跃冻结记录):分配时数量未动,取消分配不还原数量
|
||||
// (不可用 continue——会跳过循环尾部的子行 childrenqxfp 处理,须 if 包裹还原块)
|
||||
if (!outOrderFreezeRecordDomainService.hasOrderFreezeRecord(businessOrderNo, materialInventoryId)) {
|
||||
MaterialInventoryParamDO materialInventoryParamDO = new MaterialInventoryParamDO();
|
||||
materialInventoryParamDO.setMaterialInventoryId(materialInventoryId);
|
||||
materialInventoryParamDO.setQuantity(allocationQuantity);
|
||||
@@ -1046,6 +1098,7 @@ public class OutMaterialDetailImpl extends ServiceImpl<OutMaterialDetailMapper,
|
||||
materialInventoryOmsParamDO.setKctzType("取消分配");
|
||||
materialInventoryOmsParamDO.setLoginUser(loginUser);
|
||||
omsServiceFeign.omsEdit(materialInventoryOmsParamDO);
|
||||
}
|
||||
// //更新库存数量
|
||||
// //可用数量
|
||||
// BigDecimal oldAllocationQuantity = materialInventoryPO.getAllocationQuantity();
|
||||
@@ -1132,11 +1185,18 @@ public class OutMaterialDetailImpl extends ServiceImpl<OutMaterialDetailMapper,
|
||||
StockOutOrder stockOutOrder = stockOutOrderMapper.selectOne(new QueryWrapper<StockOutOrder>().lambda()
|
||||
.eq(StockOutOrder::getDelFlag,1)
|
||||
.eq(StockOutOrder::getOutOrderNumber, outOrderNumber));
|
||||
// 下单冻结判据用业务单号(null 安全:查不到单/无单号时按无冻结记录处理,走原还原逻辑)
|
||||
String businessOrderNo = stockOutOrder == null ? null : stockOutOrder.getBusinessOrderNo();
|
||||
for (OutMaterialDetailDO outMaterialDetail : outMaterialDetailList) {
|
||||
Long materialInventoryId = outMaterialDetail.getMaterialInventoryId();
|
||||
BigDecimal allocationQuantity = outMaterialDetail.getActualQuantity();
|
||||
MaterialInventory materialInventoryPO = materialInventoryMapper.selectById(materialInventoryId);
|
||||
if (materialInventoryPO != null) {
|
||||
// 下单冻结模式明细(本单该库存行存在活跃冻结记录):分配时数量未动,取消分配不还原数量
|
||||
// (叶子子行无嵌套子项,可安全 continue)
|
||||
if (outOrderFreezeRecordDomainService.hasOrderFreezeRecord(businessOrderNo, materialInventoryId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MaterialInventoryParamDO materialInventoryParamDO = new MaterialInventoryParamDO();
|
||||
materialInventoryParamDO.setMaterialInventoryId(materialInventoryId);
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.mhd.wms.domain.outOrderFreezeRecord.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.mhd.common.core.web.domain.BaseVOEntity;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* WMS出库单库存冻结记录对象 out_order_freeze_record
|
||||
*
|
||||
* @description OMS下单冻结库存模式(防超卖)的「业务单↔库存行↔冻结数量」占用关系:
|
||||
* OMS出库业务单下单即冻结WMS库存(可用-、冻结+,库存数量不变),
|
||||
* 编辑=原子释放旧冻结+按新明细重锁,取消=释放冻结,分配=承接(数量不动),
|
||||
* 交接=扣库存+释放冻结,反审=还原为冻结。
|
||||
* 本表是编辑重锁/取消解冻/分配承接/交接标记/反审还原的对账依据与单据级隔离判据。
|
||||
* @date 2026-09-12
|
||||
*/
|
||||
@Data
|
||||
@TableName("out_order_freeze_record")
|
||||
public class OutOrderFreezeRecord extends BaseVOEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("冻结记录id")
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long freezeRecordId;
|
||||
|
||||
@ApiModelProperty("OMS出库业务单号")
|
||||
private String businessOrderNumber;
|
||||
|
||||
@ApiModelProperty("执行出库单号(分配承接时回填,可空)")
|
||||
private String executionOrderNumber;
|
||||
|
||||
@ApiModelProperty("明细唯一标识(OMS明细uniqueId)")
|
||||
private String materialDetailUniqueId;
|
||||
|
||||
@ApiModelProperty("库存行ID(material_inventory.material_inventory_id)")
|
||||
private Long materialInventoryId;
|
||||
|
||||
@ApiModelProperty("仓库ID")
|
||||
private Long warehouseId;
|
||||
|
||||
@ApiModelProperty("物料基础信息ID")
|
||||
private Long materialBaseInfoId;
|
||||
|
||||
@ApiModelProperty("冻结数量")
|
||||
private BigDecimal freezeQuantity;
|
||||
|
||||
@ApiModelProperty("冻结状态: 1-业务单冻结 2-执行单承接 3-已交接 4-已释放")
|
||||
private Integer freezeStatus;
|
||||
|
||||
@ApiModelProperty("关联单号(业务单号#明细uniqueId,与库存调整记录relate_no对应)")
|
||||
private String relateNo;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 冻结状态: 业务单冻结(下单/编辑重锁后)
|
||||
*/
|
||||
public static final int STATUS_BUSINESS_FROZEN = 1;
|
||||
/**
|
||||
* 冻结状态: 执行单承接(WMS分配时数量不动,仅记录归属转移到执行单)
|
||||
*/
|
||||
public static final int STATUS_EXECUTION_UNDERTAKE = 2;
|
||||
/**
|
||||
* 冻结状态: 已交接(出库交接扣减库存后终态)
|
||||
*/
|
||||
public static final int STATUS_HANDOVER = 3;
|
||||
/**
|
||||
* 冻结状态: 已释放(取消/解冻/重锁时的旧记录)
|
||||
*/
|
||||
public static final int STATUS_RELEASED = 4;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.mhd.wms.domain.outOrderFreezeRecord.repository.facade;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.entity.OutOrderFreezeRecord;
|
||||
|
||||
/**
|
||||
* WMS出库单库存冻结记录Service接口
|
||||
*
|
||||
* @date 2026-09-12
|
||||
*/
|
||||
public interface IOutOrderFreezeRecordService extends IService<OutOrderFreezeRecord>
|
||||
{
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.mhd.wms.domain.outOrderFreezeRecord.repository.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.entity.OutOrderFreezeRecord;
|
||||
|
||||
/**
|
||||
* WMS出库单库存冻结记录Mapper接口
|
||||
*
|
||||
* @date 2026-09-12
|
||||
*/
|
||||
public interface OutOrderFreezeRecordMapper extends BaseMapper<OutOrderFreezeRecord>
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.mhd.wms.domain.outOrderFreezeRecord.repository.persistence;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.entity.OutOrderFreezeRecord;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.repository.facade.IOutOrderFreezeRecordService;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.repository.mapper.OutOrderFreezeRecordMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* WMS出库单库存冻结记录Service业务层处理
|
||||
*
|
||||
* @date 2026-09-12
|
||||
*/
|
||||
@Service
|
||||
public class OutOrderFreezeRecordImpl extends ServiceImpl<OutOrderFreezeRecordMapper, OutOrderFreezeRecord> implements IOutOrderFreezeRecordService {
|
||||
}
|
||||
+536
@@ -0,0 +1,536 @@
|
||||
package com.mhd.wms.domain.outOrderFreezeRecord.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.mhd.common.core.exception.ServiceException;
|
||||
import com.mhd.common.security.utils.SecurityUtils;
|
||||
import com.mhd.system.api.OmsServiceFeign;
|
||||
import com.mhd.system.api.domain.MaterialInventoryOmsParamDO;
|
||||
import com.mhd.system.api.domain.OmsFreezeInventoryParam;
|
||||
import com.mhd.system.api.domain.OutMaterialDetail;
|
||||
import com.mhd.system.api.model.LoginUser;
|
||||
import com.mhd.wms.domain.materialInventory.entity.MaterialInventory;
|
||||
import com.mhd.wms.domain.materialInventory.repository.facade.IMaterialInventoryService;
|
||||
import com.mhd.wms.domain.materialInventory.repository.mapper.MaterialInventoryMapper;
|
||||
import com.mhd.wms.domain.materialInventory.repository.todo.MaterialInventoryParamDO;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.entity.OutOrderFreezeRecord;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.repository.facade.IOutOrderFreezeRecordService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* WMS出库单库存冻结记录领域服务
|
||||
*
|
||||
* @description OMS下单冻结库存模式(防超卖):解决OMS下单与WMS分配库存时间差导致的出库库存不足问题。
|
||||
* 库存数量 = 可用数量 + 冻结数量,交接前全程不变(出库交接才扣减实物库存)。
|
||||
* 「下单」→ 冻结(可用-、冻结+,条件更新防并发超卖);
|
||||
* 「编辑」→ omsRelockInventory 同一事务先释放原锁定再按新明细重新锁定(原子操作);
|
||||
* 「取消」→ omsUnfreezeInventory 释放冻结(可用+、冻结-);
|
||||
* 「分配」→ 不动数量,冻结记录由业务单承接给执行单(undertakeFromFreezeRecords FIFO按量承接);
|
||||
* 「取消下发」→ releaseExecutionUndertake 承接归还业务单;
|
||||
* 「交接」→ markHandoverCompleted 置已交接终态(数量由现有 ckjj 扣减);
|
||||
* 「反审」→ 已出库 reactivateHandoverFreeze 重激活 + 实物/冻结还原。
|
||||
* @date 2026-09-12
|
||||
*/
|
||||
@Service
|
||||
public class OutOrderFreezeRecordDomainService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OutOrderFreezeRecordDomainService.class);
|
||||
|
||||
@Autowired
|
||||
private IOutOrderFreezeRecordService outOrderFreezeRecordService;
|
||||
|
||||
@Autowired
|
||||
private MaterialInventoryMapper materialInventoryMapper;
|
||||
|
||||
@Autowired
|
||||
private IMaterialInventoryService materialInventoryService;
|
||||
|
||||
@Autowired
|
||||
private OmsServiceFeign omsServiceFeign;
|
||||
|
||||
// ================================ OMS 冻结三接口 ================================
|
||||
|
||||
/**
|
||||
* OMS出库业务单下单冻结库存(幂等:同业务单号已有活跃冻结记录时直接返回成功)
|
||||
* 明细必须携带 materialInventoryId 精确定位库存行(下单冻结模式仓库明细强制必选,OMS侧已前置校验)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean omsFreezeInventory(OmsFreezeInventoryParam param) {
|
||||
validateParam(param);
|
||||
String businessOrderNumber = param.getOutOrderNumber();
|
||||
List<OutOrderFreezeRecord> activeRecords = listActiveRecords(businessOrderNumber);
|
||||
if (!CollectionUtils.isEmpty(activeRecords)) {
|
||||
// 幂等:Feign超时重试/重复提交场景,已有活跃冻结视为成功
|
||||
log.info("下单冻结幂等:出库业务单[{}]已存在{}条活跃冻结记录,跳过重复冻结", businessOrderNumber, activeRecords.size());
|
||||
return true;
|
||||
}
|
||||
freezeByDetails(param, businessOrderNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* OMS出库业务单取消/删除-释放冻结库存(可用+、冻结-,库存数量不变;幂等:无活跃冻结直接成功)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean omsUnfreezeInventory(String businessOrderNumber, Long operatorId, String operatorName) {
|
||||
if (!StringUtils.hasText(businessOrderNumber)) {
|
||||
throw new ServiceException("出库业务单号不能为空");
|
||||
}
|
||||
List<OutOrderFreezeRecord> activeRecords = listActiveRecords(businessOrderNumber);
|
||||
if (CollectionUtils.isEmpty(activeRecords)) {
|
||||
// 幂等:未冻结(模式切换前老单/重复取消)直接成功
|
||||
log.info("释放冻结幂等:出库业务单[{}]无活跃冻结记录", businessOrderNumber);
|
||||
return true;
|
||||
}
|
||||
// 已承接给执行单/已交接的冻结不可直接释放,必须先走取消下发/反审链路
|
||||
for (OutOrderFreezeRecord record : activeRecords) {
|
||||
if (record.getFreezeStatus() != null && record.getFreezeStatus() == OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]冻结已被执行单承接,请先取消下发");
|
||||
}
|
||||
if (record.getFreezeStatus() != null && record.getFreezeStatus() == OutOrderFreezeRecord.STATUS_HANDOVER) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]冻结已交接(已出库),请走反审流程");
|
||||
}
|
||||
}
|
||||
LoginUserHolder holder = resolveLoginUser(operatorId, operatorName);
|
||||
for (OutOrderFreezeRecord record : activeRecords) {
|
||||
releaseOneRecord(record, businessOrderNumber, holder);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* OMS出库业务单编辑-原子重锁:同一事务内先释放原冻结,再按新明细重新冻结。
|
||||
* 任一新明细可用不足→抛异常整体回滚(旧冻结同样还原,满足"先释放再重新锁定"的原子性要求)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean omsRelockInventory(OmsFreezeInventoryParam param) {
|
||||
validateParam(param);
|
||||
String businessOrderNumber = param.getOutOrderNumber();
|
||||
List<OutOrderFreezeRecord> activeRecords = listActiveRecords(businessOrderNumber);
|
||||
for (OutOrderFreezeRecord record : activeRecords) {
|
||||
if (record.getFreezeStatus() != null && record.getFreezeStatus() != OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]已下发WMS(冻结已被承接),不可编辑,请先取消下发");
|
||||
}
|
||||
}
|
||||
LoginUserHolder holder = resolveLoginUser(param.getOperatorId(), param.getOperatorName());
|
||||
// 1. 先按原冻结记录精确释放(数量以冻结记录为准,防止与明细数量漂移)
|
||||
for (OutOrderFreezeRecord record : activeRecords) {
|
||||
releaseOneRecord(record, businessOrderNumber, holder);
|
||||
}
|
||||
// 2. 再按新明细重新冻结(任一行失败抛异常,整个事务回滚=旧冻结也还原)
|
||||
freezeByDetails(param, businessOrderNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================================ 核心链路状态机方法 ================================
|
||||
|
||||
/**
|
||||
* WMS分配-冻结承接(FIFO按量):校验本单本行冻结量≥本次分配量后,从本单该行 status=1 记录
|
||||
* 按id升序逐条扣减承接量:记录余量承接完→status=2+执行单号;余量不足整条→只改freeze_quantity为承接量,
|
||||
* 剩余量另插一条status=1新记录(保证任一时刻记录数量之和=该单该行总冻结量,守恒)。
|
||||
* 数量字段不动(下单时已冻结)。在分配事务内调用,失败随分配整体回滚。
|
||||
*
|
||||
* @param businessOrderNumber 业务单号(单据级隔离判据,防止误消费别单冻结)
|
||||
* @param materialInventoryId 库存行ID
|
||||
* @param executionOrderNumber 执行出库单号(WMS出库单号)
|
||||
* @param undertakeQuantity 本次承接(分配)数量
|
||||
* @return true-承接成功(调用前需先校验冻结量充足)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean undertakeFromFreezeRecords(String businessOrderNumber, Long materialInventoryId,
|
||||
String executionOrderNumber, BigDecimal undertakeQuantity) {
|
||||
BigDecimal remaining = undertakeQuantity;
|
||||
List<OutOrderFreezeRecord> records = listOrderRowActiveRecords(businessOrderNumber, materialInventoryId);
|
||||
records.sort(Comparator.comparing(r -> r.getFreezeRecordId() == null ? Long.MAX_VALUE : r.getFreezeRecordId()));
|
||||
for (OutOrderFreezeRecord record : records) {
|
||||
if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
break;
|
||||
}
|
||||
BigDecimal recordQty = record.getFreezeQuantity() == null ? BigDecimal.ZERO : record.getFreezeQuantity();
|
||||
if (recordQty.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (recordQty.compareTo(remaining) <= 0) {
|
||||
// 整条记录承接完
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE);
|
||||
record.setExecutionOrderNumber(executionOrderNumber);
|
||||
record.setUpdateTime(new Date());
|
||||
outOrderFreezeRecordService.updateById(record);
|
||||
remaining = remaining.subtract(recordQty);
|
||||
} else {
|
||||
// 部分承接:本条记承接量,余量拆一条新记录保留 status=1
|
||||
BigDecimal keepQty = recordQty.subtract(remaining);
|
||||
record.setFreezeQuantity(remaining);
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE);
|
||||
record.setExecutionOrderNumber(executionOrderNumber);
|
||||
record.setUpdateTime(new Date());
|
||||
outOrderFreezeRecordService.updateById(record);
|
||||
OutOrderFreezeRecord keepRecord = new OutOrderFreezeRecord();
|
||||
keepRecord.setBusinessOrderNumber(record.getBusinessOrderNumber());
|
||||
keepRecord.setMaterialDetailUniqueId(record.getMaterialDetailUniqueId());
|
||||
keepRecord.setMaterialInventoryId(record.getMaterialInventoryId());
|
||||
keepRecord.setWarehouseId(record.getWarehouseId());
|
||||
keepRecord.setMaterialBaseInfoId(record.getMaterialBaseInfoId());
|
||||
keepRecord.setFreezeQuantity(keepQty);
|
||||
keepRecord.setFreezeStatus(OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN);
|
||||
keepRecord.setRelateNo(record.getRelateNo());
|
||||
keepRecord.setCreateBy(record.getCreateBy());
|
||||
keepRecord.setCreateByName(record.getCreateByName());
|
||||
keepRecord.setCreateTime(new Date());
|
||||
keepRecord.setDelFlag(1);
|
||||
outOrderFreezeRecordService.save(keepRecord);
|
||||
remaining = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消下发/反审(未出库)-解除执行单承接:冻结归还业务单(数量不动,仅状态 2→1)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean releaseExecutionUndertake(String executionOrderNumber) {
|
||||
if (!StringUtils.hasText(executionOrderNumber)) {
|
||||
return true;
|
||||
}
|
||||
List<OutOrderFreezeRecord> records = outOrderFreezeRecordService.list(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getExecutionOrderNumber, executionOrderNumber)
|
||||
.eq(OutOrderFreezeRecord::getFreezeStatus, OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE)
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
for (OutOrderFreezeRecord record : records) {
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN);
|
||||
record.setExecutionOrderNumber(null);
|
||||
record.setUpdateTime(new Date());
|
||||
outOrderFreezeRecordService.updateById(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 出库交接完成-冻结记录置已交接终态(数量已在交接时由现有 ckjj 扣减:库存-、冻结-)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean markHandoverCompleted(String executionOrderNumber) {
|
||||
if (!StringUtils.hasText(executionOrderNumber)) {
|
||||
return true;
|
||||
}
|
||||
List<OutOrderFreezeRecord> records = outOrderFreezeRecordService.list(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getExecutionOrderNumber, executionOrderNumber)
|
||||
.eq(OutOrderFreezeRecord::getFreezeStatus, OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE)
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
for (OutOrderFreezeRecord record : records) {
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_HANDOVER);
|
||||
record.setUpdateTime(new Date());
|
||||
outOrderFreezeRecordService.updateById(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反审(已出库)-重新激活:已交接冻结记录(status=3)归还业务单(status=1,数量不动),
|
||||
* 业务单回到未下发可编辑态,冻结继续由业务单持有(实物/冻结数量由调用方还原)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean reactivateHandoverFreeze(String executionOrderNumber) {
|
||||
if (!StringUtils.hasText(executionOrderNumber)) {
|
||||
return true;
|
||||
}
|
||||
List<OutOrderFreezeRecord> records = outOrderFreezeRecordService.list(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getExecutionOrderNumber, executionOrderNumber)
|
||||
.eq(OutOrderFreezeRecord::getFreezeStatus, OutOrderFreezeRecord.STATUS_HANDOVER)
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
for (OutOrderFreezeRecord record : records) {
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN);
|
||||
record.setExecutionOrderNumber(null);
|
||||
record.setUpdateTime(new Date());
|
||||
outOrderFreezeRecordService.updateById(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================================ 查询判据 ================================
|
||||
|
||||
/**
|
||||
* 执行单是否存在活跃冻结记录(status in 2-承接 3-已交接):判断该执行单是否下单冻结模式单据
|
||||
*/
|
||||
public boolean hasFreezeForExecutionOrder(String executionOrderNumber) {
|
||||
if (!StringUtils.hasText(executionOrderNumber)) {
|
||||
return false;
|
||||
}
|
||||
int count = outOrderFreezeRecordService.count(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getExecutionOrderNumber, executionOrderNumber)
|
||||
.in(OutOrderFreezeRecord::getFreezeStatus, Arrays.asList(
|
||||
OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE,
|
||||
OutOrderFreezeRecord.STATUS_HANDOVER))
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本单该库存行的活跃冻结量合计(status in 1-业务单冻结 2-执行单承接)。
|
||||
* 单据级隔离判据:businessOrderNumber 必传(双单冻结同行时防止误消费别单冻结)
|
||||
*/
|
||||
public BigDecimal sumOrderFreezeQuantity(String businessOrderNumber, Long materialInventoryId) {
|
||||
if (!StringUtils.hasText(businessOrderNumber) || materialInventoryId == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
List<OutOrderFreezeRecord> records = listOrderRowActiveRecords(businessOrderNumber, materialInventoryId);
|
||||
BigDecimal sum = BigDecimal.ZERO;
|
||||
for (OutOrderFreezeRecord record : records) {
|
||||
if (record.getFreezeQuantity() != null) {
|
||||
sum = sum.add(record.getFreezeQuantity());
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本单该库存行是否存在活跃冻结记录(取消分配分支判据)
|
||||
*/
|
||||
public boolean hasOrderFreezeRecord(String businessOrderNumber, Long materialInventoryId) {
|
||||
return sumOrderFreezeQuantity(businessOrderNumber, materialInventoryId).compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仓库是否存在活跃冻结记录(status in 1,2;仓库"库存锁定模式"切换守卫用:
|
||||
* 存在在途冻结单时切换模式会导致在途单解冻/承接逻辑错乱)
|
||||
*/
|
||||
public boolean hasActiveFreezeByWarehouse(Long warehouseId) {
|
||||
if (warehouseId == null) {
|
||||
return false;
|
||||
}
|
||||
int count = outOrderFreezeRecordService.count(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getWarehouseId, warehouseId)
|
||||
.in(OutOrderFreezeRecord::getFreezeStatus, Arrays.asList(
|
||||
OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN,
|
||||
OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE))
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
// ================================ 私有方法 ================================
|
||||
|
||||
private void validateParam(OmsFreezeInventoryParam param) {
|
||||
if (param == null || !StringUtils.hasText(param.getOutOrderNumber())) {
|
||||
throw new ServiceException("出库业务单号不能为空");
|
||||
}
|
||||
if (CollectionUtils.isEmpty(param.getDetails())) {
|
||||
throw new ServiceException("冻结明细不能为空");
|
||||
}
|
||||
for (OutMaterialDetail detail : param.getDetails()) {
|
||||
if (detail.getQuantity() == null || detail.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new ServiceException("出库业务单[" + param.getOutOrderNumber() + "]明细["
|
||||
+ (detail.getMaterialCode() != null ? detail.getMaterialCode() : "未知物料") + "]冻结数量必须大于0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按明细冻结:明细必须携带 materialInventoryId(下单冻结模式强制必选),
|
||||
* 逐明细单行冻结——先写台账(调整前快照)再条件更新(0行=可用不足抛异常整体回滚),
|
||||
* 全部扣减成功后统一写冻结记录+推OMS镜像(推镜像放事务末尾,缩小漂移窗口)
|
||||
*/
|
||||
private void freezeByDetails(OmsFreezeInventoryParam param, String businessOrderNumber) {
|
||||
LoginUserHolder holder = resolveLoginUser(param.getOperatorId(), param.getOperatorName());
|
||||
List<PendingFreeze> pendingFreezes = new ArrayList<>();
|
||||
for (OutMaterialDetail detail : param.getDetails()) {
|
||||
if (detail.getMaterialInventoryId() == null || detail.getMaterialInventoryId() <= 0) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]物料["
|
||||
+ (detail.getMaterialCode() != null ? detail.getMaterialCode() : detail.getMaterialBaseInfoId())
|
||||
+ "]未关联库存行,无法冻结(下单冻结模式仓库明细必须选择库存批次)");
|
||||
}
|
||||
MaterialInventory row = materialInventoryMapper.selectById(detail.getMaterialInventoryId());
|
||||
if (row == null || (row.getDelFlag() != null && row.getDelFlag() != 1)) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]物料["
|
||||
+ (detail.getMaterialCode() != null ? detail.getMaterialCode() : "") + "]关联的库存行["
|
||||
+ detail.getMaterialInventoryId() + "]不存在,冻结失败");
|
||||
}
|
||||
BigDecimal freezeQuantity = detail.getQuantity();
|
||||
BigDecimal available = row.getAllocationQuantity() == null ? BigDecimal.ZERO : row.getAllocationQuantity();
|
||||
// 先行级预检给出友好错误(并发下仍由条件更新兜底判定)
|
||||
if (available.compareTo(freezeQuantity) < 0) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]物料["
|
||||
+ (detail.getMaterialCode() != null ? detail.getMaterialCode() : "") + "]库存行["
|
||||
+ row.getMaterialInventoryId() + "]可用数量不足,需要:" + freezeQuantity + ",当前可用:" + available + ",下单冻结失败");
|
||||
}
|
||||
String relateNo = buildRelateNo(businessOrderNumber, detail.getUniqueId());
|
||||
// 1.先写库存调整记录(调整前快照)2.条件更新扣可用加冻结(0行=并发下可用不足),失败抛异常整体回滚
|
||||
MaterialInventoryParamDO paramDO = buildParamDO(row, freezeQuantity, "7", relateNo);
|
||||
materialInventoryService.kctzjl(paramDO, "下单冻结", holder.loginUser);
|
||||
int affected = materialInventoryMapper.omsFreezeDeductAvailable(row.getMaterialInventoryId(), freezeQuantity);
|
||||
if (affected != 1) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]物料["
|
||||
+ (detail.getMaterialCode() != null ? detail.getMaterialCode() : "") + "]库存行["
|
||||
+ row.getMaterialInventoryId() + "]可用数量不足,冻结失败(需求:" + freezeQuantity + ")");
|
||||
}
|
||||
pendingFreezes.add(new PendingFreeze(row, freezeQuantity, detail.getUniqueId(), relateNo));
|
||||
}
|
||||
// 全部扣减成功后:写冻结记录 + 推送OMS镜像库存
|
||||
for (PendingFreeze pending : pendingFreezes) {
|
||||
insertFreezeRecord(pending.row, pending.quantity, businessOrderNumber, pending.uniqueId, pending.relateNo, holder);
|
||||
MaterialInventoryParamDO paramDO = buildParamDO(pending.row, pending.quantity, "7", pending.relateNo);
|
||||
pushOmsMirror(paramDO, "下单冻结");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放单条冻结记录:库存调整记录(下单解冻)+ 条件更新(可用+、冻结-)+ 记录置已释放 + 推OMS镜像
|
||||
*/
|
||||
private void releaseOneRecord(OutOrderFreezeRecord record, String businessOrderNumber, LoginUserHolder holder) {
|
||||
MaterialInventory row = materialInventoryMapper.selectById(record.getMaterialInventoryId());
|
||||
if (row == null || (row.getDelFlag() != null && row.getDelFlag() != 1)) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]冻结记录对应库存行["
|
||||
+ record.getMaterialInventoryId() + "]不存在,解冻失败");
|
||||
}
|
||||
MaterialInventoryParamDO paramDO = buildParamDO(row, record.getFreezeQuantity(), "3", record.getRelateNo());
|
||||
materialInventoryService.kctzjl(paramDO, "下单解冻", holder.loginUser);
|
||||
int affected = materialInventoryMapper.omsReleaseFreezeToAvailable(record.getMaterialInventoryId(), record.getFreezeQuantity());
|
||||
if (affected != 1) {
|
||||
throw new ServiceException("出库业务单[" + businessOrderNumber + "]库存行["
|
||||
+ record.getMaterialInventoryId() + "]冻结数量不足,解冻失败");
|
||||
}
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_RELEASED);
|
||||
record.setUpdateTime(new Date());
|
||||
outOrderFreezeRecordService.updateById(record);
|
||||
pushOmsMirror(buildParamDO(row, record.getFreezeQuantity(), "3", record.getRelateNo()), "下单解冻");
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装库存变更参数:匹配键全部从【库存行】取值(OMS镜像按
|
||||
* materialBaseInfoId+warehouseId+inOrderNumber+lotNumber+batchNumber+purchaseDate 精确匹配,
|
||||
* 从明细取会因缺 lotNo/purchaseDate 匹配到错误镜像行)
|
||||
*/
|
||||
private MaterialInventoryParamDO buildParamDO(MaterialInventory row, BigDecimal quantity, String type, String relateNo) {
|
||||
MaterialInventoryParamDO paramDO = new MaterialInventoryParamDO();
|
||||
BigDecimal zero = BigDecimal.ZERO;
|
||||
paramDO.setArea(zero);
|
||||
paramDO.setVolume(zero);
|
||||
paramDO.setNetWeight(zero);
|
||||
paramDO.setGrossWeight(zero);
|
||||
paramDO.setQuantity(quantity);
|
||||
paramDO.setType(type);
|
||||
paramDO.setMaterialInventoryId(row.getMaterialInventoryId());
|
||||
paramDO.setRelateNo(relateNo);
|
||||
paramDO.setMaterialBaseInfoId(row.getMaterialBaseInfoId());
|
||||
paramDO.setWarehouseId(row.getWarehouseId());
|
||||
paramDO.setLotNumber(row.getLotNumber());
|
||||
paramDO.setBatchNumber(row.getBatchNumber());
|
||||
paramDO.setInOrderNumber(row.getInOrderNumber());
|
||||
paramDO.setProductionDate(row.getProductionDate());
|
||||
paramDO.setExpiryDate(row.getExpiryDate());
|
||||
paramDO.setPurchaseDate(row.getPurchaseDate());
|
||||
return paramDO;
|
||||
}
|
||||
|
||||
private void insertFreezeRecord(MaterialInventory row, BigDecimal quantity, String businessOrderNumber,
|
||||
Long uniqueId, String relateNo, LoginUserHolder holder) {
|
||||
OutOrderFreezeRecord record = new OutOrderFreezeRecord();
|
||||
record.setBusinessOrderNumber(businessOrderNumber);
|
||||
record.setMaterialDetailUniqueId(uniqueId == null ? null : String.valueOf(uniqueId));
|
||||
record.setMaterialInventoryId(row.getMaterialInventoryId());
|
||||
record.setWarehouseId(row.getWarehouseId());
|
||||
record.setMaterialBaseInfoId(row.getMaterialBaseInfoId());
|
||||
record.setFreezeQuantity(quantity);
|
||||
record.setFreezeStatus(OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN);
|
||||
record.setRelateNo(relateNo);
|
||||
record.setCreateBy(holder.loginUser.getUserid());
|
||||
record.setCreateByName(holder.operatorName);
|
||||
record.setCreateTime(new Date());
|
||||
record.setDelFlag(1);
|
||||
outOrderFreezeRecordService.save(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送OMS镜像库存(type=7下单冻结:可用-冻结+ / type=3下单解冻:可用+冻结-),与分配/交接推送同模式
|
||||
*/
|
||||
private void pushOmsMirror(MaterialInventoryParamDO paramDO, String kctzType) {
|
||||
try {
|
||||
MaterialInventoryOmsParamDO omsParamDO = new MaterialInventoryOmsParamDO();
|
||||
BeanUtils.copyProperties(paramDO, omsParamDO);
|
||||
omsParamDO.setKctzType(kctzType);
|
||||
omsServiceFeign.omsEdit(omsParamDO);
|
||||
} catch (Exception e) {
|
||||
// 镜像推送失败不阻断冻结主流程(与分配/交接链路同口径),由OMS侧定时对账兜底
|
||||
log.error("下单冻结推送OMS镜像库存失败:relateNo={}, kctzType={}", paramDO.getRelateNo(), kctzType, e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<OutOrderFreezeRecord> listActiveRecords(String businessOrderNumber) {
|
||||
return outOrderFreezeRecordService.list(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getBusinessOrderNumber, businessOrderNumber)
|
||||
.in(OutOrderFreezeRecord::getFreezeStatus, Arrays.asList(
|
||||
OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN,
|
||||
OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE,
|
||||
OutOrderFreezeRecord.STATUS_HANDOVER))
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 本单该行的活跃冻结记录(status 1/2,承接/校验/取消分配判据共用)
|
||||
*/
|
||||
private List<OutOrderFreezeRecord> listOrderRowActiveRecords(String businessOrderNumber, Long materialInventoryId) {
|
||||
return outOrderFreezeRecordService.list(new LambdaQueryWrapper<OutOrderFreezeRecord>()
|
||||
.eq(OutOrderFreezeRecord::getBusinessOrderNumber, businessOrderNumber)
|
||||
.eq(OutOrderFreezeRecord::getMaterialInventoryId, materialInventoryId)
|
||||
.in(OutOrderFreezeRecord::getFreezeStatus, Arrays.asList(
|
||||
OutOrderFreezeRecord.STATUS_BUSINESS_FROZEN,
|
||||
OutOrderFreezeRecord.STATUS_EXECUTION_UNDERTAKE))
|
||||
.eq(OutOrderFreezeRecord::getDelFlag, 1));
|
||||
}
|
||||
|
||||
private String buildRelateNo(String businessOrderNumber, Long uniqueId) {
|
||||
return uniqueId != null ? businessOrderNumber + "#" + uniqueId : businessOrderNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feign调用可能无登录上下文(SecurityUtils抛异常),容错获取并兜底(与 reverseExecutionOrder 同模式)
|
||||
*/
|
||||
private LoginUserHolder resolveLoginUser(Long operatorId, String operatorName) {
|
||||
LoginUserHolder holder = new LoginUserHolder();
|
||||
try {
|
||||
holder.loginUser = SecurityUtils.getLoginUser();
|
||||
} catch (Exception e) {
|
||||
holder.loginUser = new LoginUser();
|
||||
holder.loginUser.setUserid(operatorId != null ? operatorId : 0L);
|
||||
holder.loginUser.setUsername(StringUtils.hasText(operatorName) ? operatorName : "system");
|
||||
}
|
||||
holder.operatorName = StringUtils.hasText(operatorName) ? operatorName
|
||||
: (holder.loginUser.getUsername() != null ? holder.loginUser.getUsername() : "system");
|
||||
return holder;
|
||||
}
|
||||
|
||||
private static class LoginUserHolder {
|
||||
private LoginUser loginUser;
|
||||
private String operatorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 待落库的冻结占位(先全部扣减成功,再统一写冻结记录+推镜像)
|
||||
*/
|
||||
private static class PendingFreeze {
|
||||
private final MaterialInventory row;
|
||||
private final BigDecimal quantity;
|
||||
private final Long uniqueId;
|
||||
private final String relateNo;
|
||||
|
||||
private PendingFreeze(MaterialInventory row, BigDecimal quantity, Long uniqueId, String relateNo) {
|
||||
this.row = row;
|
||||
this.quantity = quantity;
|
||||
this.uniqueId = uniqueId;
|
||||
this.relateNo = relateNo;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-11
@@ -123,6 +123,8 @@ public class StockOutOrderDomainService {
|
||||
@Autowired
|
||||
private IStockOutOrderService stockOutOrderService;
|
||||
@Autowired
|
||||
private com.mhd.wms.domain.outOrderFreezeRecord.service.OutOrderFreezeRecordDomainService outOrderFreezeRecordDomainService;
|
||||
@Autowired
|
||||
private IOutMaterialDetailService outMaterialDetailService;
|
||||
@Autowired
|
||||
private IDeliveryDetailsLinkService deliveryDetailsLinkService;
|
||||
@@ -2611,6 +2613,13 @@ public class StockOutOrderDomainService {
|
||||
stockOutOrderService.updateById(stockOutOrderDB);
|
||||
return;
|
||||
}
|
||||
// 下单冻结模式判断:该执行单持有承接/已交接冻结记录时,分配时数量未动(下单时已冻结),
|
||||
// 取消下发不还原库存数量,仅解除承接(冻结归还业务单);无冻结记录走现有分配占用还原。
|
||||
// 两分支互斥:双走会虚增可用
|
||||
boolean freezeMode = outOrderFreezeRecordDomainService.hasFreezeForExecutionOrder(outOrderNumber);
|
||||
if (freezeMode) {
|
||||
outOrderFreezeRecordDomainService.releaseExecutionUndertake(outOrderNumber);
|
||||
}
|
||||
for (OutMaterialDetail materialDetail : materialDetails) {
|
||||
//还原库存
|
||||
BigDecimal allocationQuantity = materialDetail.getAllocationQuantity();
|
||||
@@ -2619,7 +2628,7 @@ public class StockOutOrderDomainService {
|
||||
//以下两种情况都无可还原的冻结量,跳过还原直接删单,保证此类单据也能被取消/补偿清理:
|
||||
//1.库存不存在(如OMS漏传库存ID的历史脏单);
|
||||
//2.未分配的明细(allocationQuantity为空或0,如下发失败后的补偿清理)
|
||||
if (materialInventory != null && allocationQuantity != null && allocationQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (!freezeMode && materialInventory != null && allocationQuantity != null && allocationQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
materialInventory.setAllocationQuantity((materialInventory.getAllocationQuantity() == null ? BigDecimal.ZERO : materialInventory.getAllocationQuantity()).add(allocationQuantity));
|
||||
materialInventory.setFreezeQuantity((materialInventory.getFreezeQuantity() == null ? BigDecimal.ZERO : materialInventory.getFreezeQuantity()).subtract(allocationQuantity));
|
||||
materialInventoryMapper.updateById(materialInventory);
|
||||
@@ -2702,12 +2711,17 @@ public class StockOutOrderDomainService {
|
||||
// 出库/未出库分支: 未出库单据(WMS状态非9)实物库存未被扣减, 只还原分配占用(可用+冻结-),
|
||||
// 不做实物还原也不推OMS——否则会虚增库存; 已出库(9)分支还原实物并推OMS(防御保留, 业务单反审已拦截已出库单)
|
||||
boolean shipped = stockOutOrderDB.getStatus() != null && stockOutOrderDB.getStatus() == 9;
|
||||
// 下单冻结模式判断:该执行单持有承接/已交接冻结记录时按下单冻结语义还原
|
||||
// (数量从"可用"口径转为"冻结"口径——分配时数量未动、交接扣的是下单冻结量,还原必须回到冻结待出库态)
|
||||
boolean freezeMode = outOrderFreezeRecordDomainService.hasFreezeForExecutionOrder(outOrderNumber);
|
||||
for (OutMaterialDetail materialDetail : materialDetails) {
|
||||
MaterialInventory materialInventory = materialInventoryMapper.selectById(materialDetail.getMaterialInventoryId());
|
||||
if (!shipped) {
|
||||
// ===== 未出库: 还原分配占用(与 cancelExecutionOrder 的取消分配同口径) =====
|
||||
// 下单冻结模式:分配时数量未动(下单时已冻结),跳过数量还原,
|
||||
// 冻结归还业务单由循环后统一处理(2→1)
|
||||
BigDecimal allocationQuantity = materialDetail.getAllocationQuantity();
|
||||
if (materialInventory != null && allocationQuantity != null && allocationQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (!freezeMode && materialInventory != null && allocationQuantity != null && allocationQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
materialInventory.setAllocationQuantity((materialInventory.getAllocationQuantity() == null ? BigDecimal.ZERO : materialInventory.getAllocationQuantity()).add(allocationQuantity));
|
||||
materialInventory.setFreezeQuantity((materialInventory.getFreezeQuantity() == null ? BigDecimal.ZERO : materialInventory.getFreezeQuantity()).subtract(allocationQuantity));
|
||||
materialInventory.setUpdateTime(new Date());
|
||||
@@ -2761,15 +2775,31 @@ public class StockOutOrderDomainService {
|
||||
//库存调整记录【出库反审】(kctzjl 内部取调整前快照, 与交接扣减先记录后改库存的顺序一致)
|
||||
restoreParam.setRelateNo(outOrderNumber + "#" + materialDetail.getUniqueId());
|
||||
materialInventoryService.kctzjl(restoreParam, "出库反审", loginUser);
|
||||
//type=1 上架还原: 实物库存/可用数量加回, 冻结数量不动
|
||||
restoreParam.setType("1");
|
||||
materialInventoryService.xgkc(restoreParam);
|
||||
//推送OMS还原预约库存(type=1: OMS侧实物/可用加回, 与交接 omsEdit 扣减互逆)
|
||||
MaterialInventoryOmsParamDO omsParam = new MaterialInventoryOmsParamDO();
|
||||
BeanUtils.copyProperties(restoreParam, omsParam);
|
||||
omsParam.setKctzType("出库反审");
|
||||
omsParam.setLoginUser(loginUser);
|
||||
omsServiceFeign.omsEdit(omsParam);
|
||||
if (freezeMode) {
|
||||
//下单冻结模式:还原为"下单已冻结待出库"——实物+、冻结+(可用不变),与交接扣减(库存-冻结-)互逆
|
||||
materialInventoryMapper.omsRestoreInventoryToFrozen(materialInventoryId,
|
||||
effectiveQuantity, netWeight, grossWeight, volume, area);
|
||||
//推送OMS镜像(两步合成"实物+冻结+",缺一不可):type=1 实物/可用加回 → type=7 可用转冻结
|
||||
MaterialInventoryOmsParamDO omsFrozen = new MaterialInventoryOmsParamDO();
|
||||
BeanUtils.copyProperties(restoreParam, omsFrozen);
|
||||
omsFrozen.setKctzType("出库反审");
|
||||
omsFrozen.setLoginUser(loginUser);
|
||||
omsFrozen.setType("1");
|
||||
omsServiceFeign.omsEdit(omsFrozen);
|
||||
omsFrozen.setType("7");
|
||||
omsFrozen.setKctzType("出库反审-还原冻结");
|
||||
omsServiceFeign.omsEdit(omsFrozen);
|
||||
} else {
|
||||
//type=1 上架还原: 实物库存/可用数量加回, 冻结数量不动
|
||||
restoreParam.setType("1");
|
||||
materialInventoryService.xgkc(restoreParam);
|
||||
//推送OMS还原预约库存(type=1: OMS侧实物/可用加回, 与交接 omsEdit 扣减互逆)
|
||||
MaterialInventoryOmsParamDO omsParam = new MaterialInventoryOmsParamDO();
|
||||
BeanUtils.copyProperties(restoreParam, omsParam);
|
||||
omsParam.setKctzType("出库反审");
|
||||
omsParam.setLoginUser(loginUser);
|
||||
omsServiceFeign.omsEdit(omsParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
//逻辑删除出库单明细
|
||||
@@ -2779,6 +2809,15 @@ public class StockOutOrderDomainService {
|
||||
materialDetail.setReverseFlag(1);
|
||||
outMaterialDetailService.updateById(materialDetail);
|
||||
}
|
||||
// 下单冻结模式:冻结记录归还业务单(未出库承接2→1;已出库已交接3→1重激活),
|
||||
// 业务单回 status=1 可编辑/再下发/取消,冻结语义闭环
|
||||
if (freezeMode) {
|
||||
if (shipped) {
|
||||
outOrderFreezeRecordDomainService.reactivateHandoverFreeze(outOrderNumber);
|
||||
} else {
|
||||
outOrderFreezeRecordDomainService.releaseExecutionUndertake(outOrderNumber);
|
||||
}
|
||||
}
|
||||
//逻辑删除出库单(用按单号查出的DB对象更新: 传入对象无主键, 直接updateById不生效, 与 cancelExecutionOrder 一致)
|
||||
stockOutOrderDB.setDelFlag(2);
|
||||
stockOutOrderDB.setReverseFlag(1);
|
||||
|
||||
+47
-2
@@ -6,6 +6,7 @@ import com.mhd.common.core.web.controller.BaseController;
|
||||
import com.mhd.common.core.web.domain.AjaxResult;
|
||||
import com.mhd.common.core.web.page.TableDataInfo;
|
||||
import com.mhd.system.api.domain.DeliveryDetailsLink;
|
||||
import com.mhd.system.api.domain.OmsFreezeInventoryParam;
|
||||
import com.mhd.wms.application.service.handoverTaskOrder.HandoverTaskOrderApplicationService;
|
||||
import com.mhd.wms.application.service.stockOutOrder.StockOutOrderApplicationService;
|
||||
import com.mhd.wms.domain.copyCodeReference.entity.CopyCodeReference;
|
||||
@@ -25,6 +26,7 @@ import com.mhd.wms.domain.stockOutOrder.repository.facade.IStockOutOrderService;
|
||||
import com.mhd.wms.domain.stockOutOrder.repository.listener.ExcelParseService;
|
||||
import com.mhd.wms.domain.stockOutOrder.repository.po.StockOutOrderPO;
|
||||
import com.mhd.wms.domain.stockOutOrder.repository.todo.StockOutOrderDO;
|
||||
import com.mhd.wms.domain.outOrderFreezeRecord.service.OutOrderFreezeRecordDomainService;
|
||||
import com.mhd.wms.interfaces.assember.stockOutOrder.StockOutOrderAssembler;
|
||||
import com.mhd.wms.interfaces.dto.outMaterialDetail.OutMaterialDetailDTO;
|
||||
import com.mhd.wms.interfaces.dto.stockOutOrder.StockOutOrderDTO;
|
||||
@@ -58,6 +60,8 @@ import java.util.Map;
|
||||
public class StockOutOrderApi extends BaseController {
|
||||
@Autowired
|
||||
private StockOutOrderApplicationService stockOutOrderApplicationService;
|
||||
@Autowired
|
||||
private OutOrderFreezeRecordDomainService outOrderFreezeRecordDomainService;
|
||||
|
||||
@Resource
|
||||
private StockOutOrderAssembler stockOutOrderAssembler;
|
||||
@@ -561,8 +565,9 @@ public class StockOutOrderApi extends BaseController {
|
||||
*/
|
||||
@ApiOperation("oms下发前库存校验")
|
||||
@PostMapping(value = "/checkOmsAssignInventory")
|
||||
public AjaxResult checkOmsAssignInventory(@RequestBody List<OutMaterialDetail> outMaterialDetails) {
|
||||
stockOutOrderApplicationService.checkOmsAssignInventory(outMaterialDetails);
|
||||
public AjaxResult checkOmsAssignInventory(@RequestBody List<OutMaterialDetail> outMaterialDetails,
|
||||
@RequestParam(value = "businessOrderNumber", required = false) String businessOrderNumber) {
|
||||
stockOutOrderApplicationService.checkOmsAssignInventory(outMaterialDetails, businessOrderNumber);
|
||||
return AjaxResult.success("库存校验通过");
|
||||
}
|
||||
|
||||
@@ -584,6 +589,46 @@ public class StockOutOrderApi extends BaseController {
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* oms出库业务单下单冻结库存(防超卖): 按明细冻结WMS库存(可用-、冻结+,库存数量不变),
|
||||
* 条件更新防并发超卖,任一行可用不足整体失败;同业务单号幂等
|
||||
*/
|
||||
@ApiOperation("oms出库业务单下单冻结库存")
|
||||
@PostMapping(value = "/omsFreezeInventory")
|
||||
public AjaxResult omsFreezeInventory(@RequestBody OmsFreezeInventoryParam param) {
|
||||
return AjaxResult.success(outOrderFreezeRecordDomainService.omsFreezeInventory(param));
|
||||
}
|
||||
|
||||
/**
|
||||
* oms出库业务单取消/删除-释放冻结库存: 可用+、冻结-(库存数量不变);按业务单号幂等;
|
||||
* 冻结已被执行单承接时拒绝(须先取消下发)
|
||||
*/
|
||||
@ApiOperation("oms出库业务单释放冻结库存")
|
||||
@PostMapping(value = "/omsUnfreezeInventory")
|
||||
public AjaxResult omsUnfreezeInventory(@RequestBody OmsFreezeInventoryParam param) {
|
||||
return AjaxResult.success(outOrderFreezeRecordDomainService.omsUnfreezeInventory(
|
||||
param.getOutOrderNumber(), param.getOperatorId(), param.getOperatorName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* oms出库业务单编辑原子重锁: 同一事务先释放原冻结再按新明细重新冻结,
|
||||
* 任一新明细可用不足整体回滚(旧冻结同样还原)
|
||||
*/
|
||||
@ApiOperation("oms出库业务单编辑重锁库存")
|
||||
@PostMapping(value = "/omsRelockInventory")
|
||||
public AjaxResult omsRelockInventory(@RequestBody OmsFreezeInventoryParam param) {
|
||||
return AjaxResult.success(outOrderFreezeRecordDomainService.omsRelockInventory(param));
|
||||
}
|
||||
|
||||
/**
|
||||
* 仓库是否存在活跃冻结记录(仓库"库存锁定模式"切换守卫:在途冻结单存在时禁止切换)
|
||||
*/
|
||||
@ApiOperation("仓库是否存在活跃冻结记录")
|
||||
@GetMapping(value = "/hasActiveFreezeByWarehouse/{warehouseId}")
|
||||
public AjaxResult hasActiveFreezeByWarehouse(@PathVariable("warehouseId") Long warehouseId) {
|
||||
return AjaxResult.success(outOrderFreezeRecordDomainService.hasActiveFreezeByWarehouse(warehouseId));
|
||||
}
|
||||
|
||||
/**
|
||||
* oms下发单配送明细保存
|
||||
*/
|
||||
|
||||
@@ -1894,4 +1894,43 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
1 = 1
|
||||
AND MATERIAL_CLASSIFY_ID = #{id} AND DEL_FLAG = 1
|
||||
</select>
|
||||
|
||||
<!-- OMS下单冻结-条件扣减:可用-、冻结+,库存数量不变;可用不足时影响行数为0,
|
||||
由调用方抛异常回滚(防并发超卖,绝不能用 GREATEST 截断版) -->
|
||||
<update id="omsFreezeDeductAvailable">
|
||||
UPDATE material_inventory
|
||||
SET allocation_quantity = allocation_quantity - #{quantity},
|
||||
freeze_quantity = freeze_quantity + #{quantity},
|
||||
update_time = SYSDATE
|
||||
WHERE material_inventory_id = #{materialInventoryId}
|
||||
AND del_flag = 1
|
||||
AND allocation_quantity >= #{quantity}
|
||||
</update>
|
||||
|
||||
<!-- OMS下单解冻-条件释放:可用+、冻结-,库存数量不变;冻结不足时影响行数为0,
|
||||
由调用方抛异常回滚(防重复解冻/负数) -->
|
||||
<update id="omsReleaseFreezeToAvailable">
|
||||
UPDATE material_inventory
|
||||
SET allocation_quantity = allocation_quantity + #{quantity},
|
||||
freeze_quantity = freeze_quantity - #{quantity},
|
||||
update_time = SYSDATE
|
||||
WHERE material_inventory_id = #{materialInventoryId}
|
||||
AND del_flag = 1
|
||||
AND freeze_quantity >= #{quantity}
|
||||
</update>
|
||||
|
||||
<!-- OMS下单冻结单出库反审-还原为冻结:实物+、冻结+(可用不变)+重量体积面积还原,
|
||||
与交接扣减 ckjj(库存-冻结-)互逆;整行加回无条件(还原语义明确) -->
|
||||
<update id="omsRestoreInventoryToFrozen">
|
||||
UPDATE material_inventory
|
||||
SET inventory_quantity = inventory_quantity + #{quantity},
|
||||
freeze_quantity = freeze_quantity + #{quantity},
|
||||
net_weight = net_weight + #{netWeight},
|
||||
gross_weight = gross_weight + #{grossWeight},
|
||||
volume = volume + #{volume},
|
||||
area = area + #{area},
|
||||
update_time = SYSDATE
|
||||
WHERE material_inventory_id = #{materialInventoryId}
|
||||
AND del_flag = 1
|
||||
</update>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user