feat(oms,wms,system): OMS下单冻结库存(防超卖)——仓库级库存锁定模式

- 仓库新增stock_lock_mode开关(1-下单冻结 2-分配库存冻结默认),查询/独立切换接口+在途冻结单切换守卫
- WMS新增out_order_freeze_record冻结记录表:下单冻结/编辑原子重锁/取消解冻/分配承接/交接扣减/反审还原全状态机,条件更新防并发超卖
- OMS下单即冻结(失败阻断+落库失败补偿解冻)、已下发禁编辑、cancelIssueOrder补调WMS清理(修存量不同步)、cancelAuditState镜像还原修复
- 达梦SQL脚本3个(WAREHOUSE加列/冻结记录建表/主键列名修复),测试库已执行并全流程实测通过
This commit is contained in:
rcx
2026-09-14 15:41:32 +08:00
parent 888a293ebd
commit b1d92fdda8
31 changed files with 1587 additions and 47 deletions
@@ -12,7 +12,9 @@ import com.mhd.basic.domain.warehouse.repository.todo.WarehouseDO;
import com.mhd.basic.domain.warehouse.service.WarehouseDomainService;
import com.mhd.common.core.enums.DictCode;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.domain.SysDictData;
import com.mhd.system.api.model.LoginUser;
import com.mhd.system.service.ISysDictTypeService;
@@ -43,6 +45,8 @@ public class WarehouseApplicationService {
private ISysDictTypeService dictTypeService;
@Autowired
private IAssociationWarehouseService associationWarehouseService;
@Autowired
private WmsServiceFeign wmsServiceFeign;
/**
@@ -239,12 +243,78 @@ public class WarehouseApplicationService {
/**
* 修改仓库
*/
/**
* @description 查询仓库库存锁定模式(OMS下单冻结库存/防超卖功能的仓库级开关)
* 1-下单冻结:OMS出库业务单「下单」即冻结WMS库存(可用-、冻结+,库存数量不变)
* 2-分配库存冻结:现有模式,WMS「分配库存」时才冻结
* 仓库不存在或未配置(null)时返回默认值 2(与代码侧降级口径一致)
* @author gen
* @date 2026-09-12
*/
public Integer getStockLockMode(Long warehouseId) {
WarehousePO warehousePO = warehouseDomainService.getInfo(warehouseId);
return warehousePO == null ? 2
: cn.hutool.core.util.ObjectUtil.defaultIfNull(warehousePO.getStockLockMode(), 2);
}
public Boolean update(WarehouseDO warehouseDO) {
// 库存锁定模式切换守卫:模式发生变化时,存在在途冻结单(OMS下单冻结未释放)则禁止切换,
// 否则在途单的解冻/承接逻辑会按新模式错乱。fail-closed:校验服务不可用时拒绝切换
if (warehouseDO.getWarehouseId() != null && warehouseDO.getStockLockMode() != null) {
WarehousePO existingPO = warehouseDomainService.getInfo(warehouseDO.getWarehouseId());
if (existingPO != null) {
checkStockLockModeSwitchable(warehouseDO.getWarehouseId(), warehouseDO.getStockLockMode(), existingPO);
}
}
//设置数据字典键值
setDataDict(warehouseDO);
return warehouseDomainService.update(warehouseDO);
}
/**
* @description 单独切换仓库库存锁定模式(不改动仓库其他信息)
* 1-下单冻结:OMS出库业务单「下单」即冻结WMS库存
* 2-分配库存冻结(默认):WMS「分配库存」时才冻结
* 切换守卫与通用编辑一致:模式变化且存在在途冻结单时禁止切换,fail-closed
* @author gen
* @date 2026-09-14
*/
public Boolean switchStockLockMode(Long warehouseId, Integer stockLockMode) {
if (warehouseId == null) {
throw new ServiceException("仓库ID不能为空");
}
if (stockLockMode == null || (stockLockMode.intValue() != 1 && stockLockMode.intValue() != 2)) {
throw new ServiceException("库存锁定模式不合法,仅支持 1-下单冻结 2-分配库存冻结");
}
WarehousePO existingPO = warehouseDomainService.getInfo(warehouseId);
if (existingPO == null) {
throw new ServiceException("仓库不存在");
}
checkStockLockModeSwitchable(warehouseId, stockLockMode, existingPO);
return warehouseDomainService.switchStockLockMode(warehouseId, stockLockMode);
}
/**
* @description 库存锁定模式切换守卫:模式未变化直接放行;模式变化时查询WMS是否存在
* 在途下单冻结单(freeze_status 1/2),存在则禁止切换。
* fail-closed:WMS服务不可用/返回异常时一律拒绝切换,防止切换后冻结语义错乱
* @author gen
* @date 2026-09-14
*/
private void checkStockLockModeSwitchable(Long warehouseId, Integer newMode, WarehousePO existingPO) {
if (newMode.compareTo(cn.hutool.core.util.ObjectUtil.defaultIfNull(existingPO.getStockLockMode(), 2)) == 0) {
return;
}
AjaxResult freezeCheck = wmsServiceFeign.hasActiveFreezeByWarehouse(warehouseId);
if (freezeCheck == null || !"200".equals(String.valueOf(freezeCheck.get("code")))) {
throw new ServiceException("库存锁定模式切换校验失败,请稍后重试:"
+ (freezeCheck == null ? "WMS服务不可用" : freezeCheck.get("msg")));
}
if (Boolean.TRUE.equals(freezeCheck.get("data"))) {
throw new ServiceException("该仓库存在下单冻结中的出库单,不可切换库存锁定模式");
}
}
/**
* @description 设置数据字典键值
* @author ZhouGY
@@ -130,6 +130,10 @@ public class Warehouse extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则0日期累加1历史累加")
@Excel(name = "单号生成规则")
private String orderNoGenerateRule;
@@ -132,6 +132,10 @@ public class WarehousePO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则")
@Excel(name = "单号生成规则")
@@ -140,6 +140,10 @@ public class WarehouseDO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则")
@Excel(name = "单号生成规则")
@@ -142,6 +142,29 @@ public class WarehouseDomainService {
.eq(Warehouse::getDelFlag, 1));
}
/**
* @description 单独切换仓库库存锁定模式(仅更新 STOCK_LOCK_MODE 与审计字段,不触碰仓库其他数据)
* 1-下单冻结:OMS出库业务单「下单」即冻结WMS库存
* 2-分配库存冻结(默认):WMS「分配库存」时才冻结
* @author gen
* @date 2026-09-14
*/
public Boolean switchStockLockMode(Long warehouseId, Integer stockLockMode) {
Long updateBy = null;
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
updateBy = loginUser == null ? null : loginUser.getUserid();
} catch (Exception ignore) {
}
Long finalUpdateBy = updateBy;
return warehouseService.update(new UpdateWrapper<Warehouse>().lambda()
.set(Warehouse::getStockLockMode, stockLockMode)
.set(finalUpdateBy != null, Warehouse::getUpdateBy, finalUpdateBy)
.set(Warehouse::getUpdateTime, new Date())
.eq(Warehouse::getWarehouseId, warehouseId)
.eq(Warehouse::getDelFlag, 1));
}
/**
* @description 获取指定仓库下的所有库区及库区下所有库位
* @author ZhouGY
@@ -145,6 +145,10 @@ public class WarehouseDTO extends BaseVOEntity{
@Excel(name = "作业模式")
private String workMode;
@ApiModelProperty("库存锁定模式: 1-下单冻结 2-分配库存冻结")
@Excel(name = "库存锁定模式: 1-下单冻结 2-分配库存冻结")
private Integer stockLockMode;
@ApiModelProperty("订单号生成规则")
@Excel(name = "单号生成规则")
@@ -104,6 +104,33 @@ public class WarehouseApi extends BaseController{
return AjaxResult.success(warehouseApplicationService.getInfo(warehouseId));
}
/**
* 查询仓库库存锁定模式(OMS下单冻结库存/防超卖功能的仓库级开关):
* 1-下单冻结(OMS出库业务单「下单」即冻结WMS库存:可用-、冻结+,库存数量不变;
* 分配不动数量;编辑=原子重锁;取消=释放;交接=库存-冻结-)
* 2-分配库存冻结(默认,现有行为:WMS「分配库存」时才 可用-冻结+)
* 仓库不存在或未配置时返回 2(与 OMS/WMS 代码侧降级口径一致)
*/
@ApiOperation("查询仓库库存锁定模式: 1-下单冻结 2-分配库存冻结")
@GetMapping(value = "/getStockLockMode/{warehouseId}")
public AjaxResult getStockLockMode(@PathVariable("warehouseId") Long warehouseId)
{
return AjaxResult.success(warehouseApplicationService.getStockLockMode(warehouseId));
}
/**
* 单独切换仓库库存锁定模式(不改动仓库其他信息):
* 模式变化且该仓库存在在途下单冻结出库单(freeze_status 1/2)时拒绝切换;
* WMS校验服务不可用时同样拒绝(fail-closed
*/
@ApiOperation("切换仓库库存锁定模式: 1-下单冻结 2-分配库存冻结")
@PostMapping(value = "/switchStockLockMode/{warehouseId}/{stockLockMode}")
public AjaxResult switchStockLockMode(@PathVariable("warehouseId") Long warehouseId,
@PathVariable("stockLockMode") Integer stockLockMode)
{
return toAjax(warehouseApplicationService.switchStockLockMode(warehouseId, stockLockMode));
}
@ApiOperation("批量作废仓库")
@GetMapping("/nullifyByIds/{warehouseIds}")
@@ -43,6 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="orderNoGenerateRule" column="ORDER_NO_GENERATE_RULE" />
<result property="inOrderNoPrefix" column="IN_ORDER_NO_PREFIX" />
<result property="outOrderNoPrefix" column="OUT_ORDER_NO_PREFIX" />
<result property="stockLockMode" column="STOCK_LOCK_MODE" />
</resultMap>
@@ -51,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
warehouse_type, warehouse_type_name, warehouse_structure, warehouse_nature, warm_layer_code, warm_layer_name,
director_name, director_phone, region_code, region_name, detail_address, longitude, latitude, building_region,
usage_region, remark, opening_up, nullify, create_time, create_by, create_by_name, update_time, update_by,
update_by_name, del_flag, IS_ENABLE ,WORK_MODE,TASK_MODE,SAMPLE_OUT_ORDER_NO_PREFIX,ORDER_NO_GENERATE_RULE,IN_ORDER_NO_PREFIX,
update_by_name, del_flag, IS_ENABLE ,WORK_MODE,TASK_MODE,STOCK_LOCK_MODE,SAMPLE_OUT_ORDER_NO_PREFIX,ORDER_NO_GENERATE_RULE,IN_ORDER_NO_PREFIX,
OUT_ORDER_NO_PREFIX
from warehouse
</sql>