fix(oms):出库明细materialBaseInfoId自动纠正,杜绝脏物料ID落库

问题:客户端推单把materialInventoryId传进materialBaseInfoId字段(或漏传为0),
出库明细存脏ID,编辑/保存报"物料基础信息不存在"(如DCCK034存了库存ID477/444,
真实物料ID应为233/231)。预约端校验原先静默跳过,脏数据持续入库(7-15起200+行)。

一、新增MaterialBaseInfoResolveService三级解析:
    baseId直查 → 库存ID反查纠正 → 物料编码+组织兜底,全部失败返回null
二、三处setMaterialDetailInfo改为纠正+回填(预约/业务/执行出库单):
    查不到抛错带定位信息(baseId/库存ID/物料编码/单号);
    纠正后的ID随保存落库,旧脏单重新保存即自愈;
    预约端由静默跳过改为抛错
三、两处pushOutOrderDetail推送入口(预约/业务)加纠正,
    反查不到拒绝保存,脏数据不再入库

注:存量脏数据修复SQL另行手动执行;下发复制链(预约→业务→执行)不走校验,
This commit is contained in:
rcx
2026-08-21 18:20:22 +08:00
parent 06aae196c7
commit b209d8cb6d
4 changed files with 137 additions and 6 deletions
@@ -31,6 +31,7 @@ import com.mhd.oms.domain.executionStockOutOrder.repository.facade.IExecutionSto
import com.mhd.oms.domain.executionStockOutOrder.repository.mapper.ExecutionStockOutOrderMapper;
import com.mhd.oms.domain.executionStockOutOrder.repository.po.ExecutionStockOutOrderPO;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import com.mhd.oms.domain.materialBaseInfo.service.MaterialBaseInfoResolveService;
import com.mhd.oms.domain.reservationDeliveryDetailsLink.entity.ReservationDeliveryDetailsLink;
import com.mhd.oms.domain.reservationDeliveryDetailsLink.repository.facade.IReservationDeliveryDetailsLinkService;
//import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO;
@@ -102,6 +103,8 @@ public class ExecutionStockOutOrderDomainService {
private ReservationStockInOrderMapper stockInOrderMapper;
@Autowired
private ReserveStockOutOrderMapper reserveStockOutOrderMapper;
@Autowired
private MaterialBaseInfoResolveService materialBaseInfoResolveService;
/**
* 分页查询出库单列表
*/
@@ -902,10 +905,22 @@ public class ExecutionStockOutOrderDomainService {
List<ExecutionOutMaterialDetailDO> outMaterialDetailDOList = stockOutOrderDO.getMaterialDetailList();
outMaterialDetailDOList.forEach(outMaterialDetailDO -> {
// Long materialBaseInfoId = outMaterialDetailDO.getMaterialBaseInfoId();
MaterialBaseInfoPO materialBaseInfoPO = reservationStockInOrderMapper.getinfo(outMaterialDetailDO.getMaterialBaseInfoId());
// 物料ID自动纠正:baseId直查 → 库存ID反查 → 编码+组织兜底(修复客户端把库存ID传进baseId或漏传)
Long orgId = outMaterialDetailDO.getOrganizationId() != null
? outMaterialDetailDO.getOrganizationId() : stockOutOrderDO.getOrganizationId();
// 注意:解析器返回的是 reservationStockInOrder 包下的 MaterialBaseInfoPO,与本类 import 的同名PO不是同一类型,故用全限定名
com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO materialBaseInfoPO = materialBaseInfoResolveService.resolve(
outMaterialDetailDO.getMaterialBaseInfoId(),
outMaterialDetailDO.getMaterialInventoryId(),
outMaterialDetailDO.getMaterialCode(), orgId);
if (materialBaseInfoPO == null){
throw new ServiceException("物料基础信息不存在");
throw new ServiceException("物料基础信息不存在materialBaseInfoId=" + outMaterialDetailDO.getMaterialBaseInfoId()
+ "materialInventoryId=" + outMaterialDetailDO.getMaterialInventoryId()
+ "materialCode=" + outMaterialDetailDO.getMaterialCode()
+ ",出库单号=" + stockOutOrderDO.getOutOrderNumber());
}
// 回填纠正后的物料ID,脏数据随保存落库自愈
outMaterialDetailDO.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
outMaterialDetailDO.setMaterialCode(materialBaseInfoPO.getMaterialCode());
outMaterialDetailDO.setMaterialName(materialBaseInfoPO.getMaterialName());
outMaterialDetailDO.setBarCode(materialBaseInfoPO.getBarCode());
@@ -0,0 +1,57 @@
package com.mhd.oms.domain.materialBaseInfo.service;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.oms.domain.reservationMaterialInventory.entity.ReservationMaterialInventory;
import com.mhd.oms.domain.reservationMaterialInventory.repository.mapper.ReservationMaterialInventoryMapper;
import com.mhd.oms.domain.reservationStockInOrder.repository.mapper.ReservationStockInOrderMapper;
import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 物料基础信息解析器(带自动纠正)
* 解决客户端推单把 materialInventoryId 传进 materialBaseInfoId 字段、或漏传导致明细存脏ID的问题。
* 三级解析:1.按baseId直查 → 2.按库存ID反查纠正 → 3.按物料编码+组织兜底
*/
@Service
public class MaterialBaseInfoResolveService {
@Autowired
private ReservationStockInOrderMapper reservationStockInOrderMapper;
@Autowired
private ReservationMaterialInventoryMapper reservationMaterialInventoryMapper;
/**
* 解析物料基础信息,优先按 materialBaseInfoId 直查;
* 无效时用 materialInventoryId 反查库存表取正确 baseInfoId
* 仍无效时按 物料编码+组织ID 兜底。
*
* @return 解析成功返回物料信息(调用方需用 po.getMaterialBaseInfoId() 回填纠正后的ID);全部失败返回 null
*/
public MaterialBaseInfoPO resolve(Long materialBaseInfoId, Long materialInventoryId, String materialCode, Long organizationId) {
// 1. 优先按 baseId 直查(有效ID不受影响)
if (materialBaseInfoId != null && materialBaseInfoId > 0) {
MaterialBaseInfoPO po = reservationStockInOrderMapper.getinfo(materialBaseInfoId);
if (po != null) {
return po;
}
}
// 2. baseId 无效但带了库存ID → 反查库存表拿正确 baseInfoId(修复"库存ID传进baseId"场景)
if (materialInventoryId != null && materialInventoryId > 0) {
ReservationMaterialInventory inventory = reservationMaterialInventoryMapper.selectById(materialInventoryId);
if (inventory != null && inventory.getMaterialBaseInfoId() != null
&& (inventory.getDelFlag() == null || inventory.getDelFlag() == 1)) {
MaterialBaseInfoPO po = reservationStockInOrderMapper.getinfo(inventory.getMaterialBaseInfoId());
if (po != null) {
return po;
}
}
}
// 3. 兜底:按物料编码+组织查(修复 baseId=0 漏传场景)
if (StringUtils.isNotBlank(materialCode) && organizationId != null) {
return reservationStockInOrderMapper.getByCode(materialCode.trim(), organizationId);
}
return null;
}
}
@@ -30,6 +30,7 @@ import com.mhd.oms.domain.executionStockOutOrder.repository.facade.IExecutionSto
import com.mhd.oms.domain.executionStockOutOrder.repository.mapper.ExecutionStockOutOrderMapper;
import com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO;
import com.mhd.oms.domain.executionStockOutOrder.service.ExecutionStockOutOrderApplicationService;
import com.mhd.oms.domain.materialBaseInfo.service.MaterialBaseInfoResolveService;
import com.mhd.oms.domain.reservationDeliveryDetailsLink.entity.ReservationDeliveryDetailsLink;
import com.mhd.oms.domain.reservationDeliveryDetailsLink.repository.facade.IReservationDeliveryDetailsLinkService;
import com.mhd.oms.domain.reservationInMaterialDetail.entity.ReservationInMaterialDetail;
@@ -115,6 +116,8 @@ public class ReservationStockOutOrderDomainService {
private ReservationStockOutOrderMapper reservationStockOutOrderMapper;
@Autowired
private ReserveStockOutOrderMapper reserveStockOutOrderMapper;
@Autowired
private MaterialBaseInfoResolveService materialBaseInfoResolveService;
/**
* 分页查询出库单列表
@@ -370,6 +373,21 @@ public class ReservationStockOutOrderDomainService {
public Boolean pushOutOrderDetail(OutMaterialDetailTZPD outMaterialDetail) {
ReservationOutMaterialDetail reservationOutMaterialDetail = new ReservationOutMaterialDetail();
BeanUtils.copyProperties(outMaterialDetail, reservationOutMaterialDetail);
// 推送数据物料ID纠正:客户端曾把库存ID传进 materialBaseInfoId 或漏传,纠正后再入库,查不到拒绝保存
MaterialBaseInfoPO materialBaseInfoPO = materialBaseInfoResolveService.resolve(
reservationOutMaterialDetail.getMaterialBaseInfoId(),
reservationOutMaterialDetail.getMaterialInventoryId(),
reservationOutMaterialDetail.getMaterialCode(),
reservationOutMaterialDetail.getOrganizationId());
if (materialBaseInfoPO == null){
throw new ServiceException("推送出库明细物料无法识别,拒绝保存:materialBaseInfoId=" + reservationOutMaterialDetail.getMaterialBaseInfoId()
+ "materialInventoryId=" + reservationOutMaterialDetail.getMaterialInventoryId()
+ "materialCode=" + reservationOutMaterialDetail.getMaterialCode()
+ ",出库单号=" + reservationOutMaterialDetail.getOutOrderNumber());
}
reservationOutMaterialDetail.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
reservationOutMaterialDetail.setMaterialCode(materialBaseInfoPO.getMaterialCode());
reservationOutMaterialDetail.setMaterialName(materialBaseInfoPO.getMaterialName());
return outMaterialDetailService.save(reservationOutMaterialDetail);
}
@@ -1064,10 +1082,21 @@ public class ReservationStockOutOrderDomainService {
List<ReservationOutMaterialDetailDO> outMaterialDetailDOList = stockOutOrderDO.getMaterialDetailList();
outMaterialDetailDOList.forEach(outMaterialDetailDO -> {
// Long materialBaseInfoId = outMaterialDetailDO.getMaterialBaseInfoId();
MaterialBaseInfoPO materialBaseInfoPO = reservationStockInOrderMapper.getinfo(outMaterialDetailDO.getMaterialBaseInfoId());
// 物料ID自动纠正:baseId直查 → 库存ID反查 → 编码+组织兜底(修复客户端把库存ID传进baseId或漏传)
Long orgId = outMaterialDetailDO.getOrganizationId() != null
? outMaterialDetailDO.getOrganizationId() : stockOutOrderDO.getOrganizationId();
MaterialBaseInfoPO materialBaseInfoPO = materialBaseInfoResolveService.resolve(
outMaterialDetailDO.getMaterialBaseInfoId(),
outMaterialDetailDO.getMaterialInventoryId(),
outMaterialDetailDO.getMaterialCode(), orgId);
if (materialBaseInfoPO == null){
throw new ServiceException("物料基础信息不存在");
throw new ServiceException("物料基础信息不存在materialBaseInfoId=" + outMaterialDetailDO.getMaterialBaseInfoId()
+ "materialInventoryId=" + outMaterialDetailDO.getMaterialInventoryId()
+ "materialCode=" + outMaterialDetailDO.getMaterialCode()
+ ",出库单号=" + stockOutOrderDO.getOutOrderNumber());
}
// 回填纠正后的物料ID,脏数据随保存落库自愈
outMaterialDetailDO.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
outMaterialDetailDO.setMaterialCode(materialBaseInfoPO.getMaterialCode());
outMaterialDetailDO.setMaterialName(materialBaseInfoPO.getMaterialName());
outMaterialDetailDO.setBarCode(materialBaseInfoPO.getBarCode());
@@ -10,6 +10,7 @@ import com.mhd.common.core.utils.bean.BeanUtils;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper;
import com.mhd.oms.domain.materialBaseInfo.service.MaterialBaseInfoResolveService;
import com.mhd.oms.domain.reservationInMaterialDetail.entity.ReservationInMaterialDetail;
import com.mhd.oms.domain.reservationOutMaterialDetail.entity.ReservationOutMaterialDetail;
import com.mhd.oms.domain.reservationOutMaterialDetail.repository.facade.IReservationOutMaterialDetailService;
@@ -80,6 +81,8 @@ public class ReserveStockOutOrderDomainService {
private ReservationStockOutOrderDomainService reservationStockOutOrderDomainService;
@Autowired
private SystemServiceFeign systemServiceFeign;
@Autowired
private MaterialBaseInfoResolveService materialBaseInfoResolveService;
/**
* 分页查询出库单列表
@@ -324,6 +327,21 @@ public class ReserveStockOutOrderDomainService {
public Boolean pushOutOrderDetail(OutMaterialDetailTZPD outMaterialDetail) {
ReserveOutMaterialDetail reservationOutMaterialDetail = new ReserveOutMaterialDetail();
BeanUtils.copyProperties(outMaterialDetail, reservationOutMaterialDetail);
// 推送数据物料ID纠正:客户端曾把库存ID传进 materialBaseInfoId 或漏传,纠正后再入库,查不到拒绝保存
MaterialBaseInfoPO materialBaseInfoPO = materialBaseInfoResolveService.resolve(
reservationOutMaterialDetail.getMaterialBaseInfoId(),
reservationOutMaterialDetail.getMaterialInventoryId(),
reservationOutMaterialDetail.getMaterialCode(),
reservationOutMaterialDetail.getOrganizationId());
if (materialBaseInfoPO == null){
throw new ServiceException("推送出库明细物料无法识别,拒绝保存:materialBaseInfoId=" + reservationOutMaterialDetail.getMaterialBaseInfoId()
+ "materialInventoryId=" + reservationOutMaterialDetail.getMaterialInventoryId()
+ "materialCode=" + reservationOutMaterialDetail.getMaterialCode()
+ ",出库单号=" + reservationOutMaterialDetail.getOutOrderNumber());
}
reservationOutMaterialDetail.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
reservationOutMaterialDetail.setMaterialCode(materialBaseInfoPO.getMaterialCode());
reservationOutMaterialDetail.setMaterialName(materialBaseInfoPO.getMaterialName());
return outMaterialDetailService.save(reservationOutMaterialDetail);
}
@@ -833,10 +851,22 @@ public class ReserveStockOutOrderDomainService {
List<ReserveOutMaterialDetailDO> outMaterialDetailDOList = stockOutOrderDO.getMaterialDetailList();
outMaterialDetailDOList.forEach(outMaterialDetailDO -> {
// Long materialBaseInfoId = outMaterialDetailDO.getMaterialBaseInfoId();
MaterialBaseInfoPO materialBaseInfoPO = reservationStockInOrderMapper.getinfo(outMaterialDetailDO.getMaterialBaseInfoId());
// 物料ID自动纠正:baseId直查 → 库存ID反查 → 编码+组织兜底(修复客户端把库存ID传进baseId或漏传)
// 原逻辑查不到物料时静默跳过,导致物料信息缺失且脏ID落库,现改为纠正失败即抛错
Long orgId = outMaterialDetailDO.getOrganizationId() != null
? outMaterialDetailDO.getOrganizationId() : stockOutOrderDO.getOrganizationId();
MaterialBaseInfoPO materialBaseInfoPO = materialBaseInfoResolveService.resolve(
outMaterialDetailDO.getMaterialBaseInfoId(),
outMaterialDetailDO.getMaterialInventoryId(),
outMaterialDetailDO.getMaterialCode(), orgId);
if (materialBaseInfoPO == null){
return;
throw new ServiceException("物料基础信息不存在:materialBaseInfoId=" + outMaterialDetailDO.getMaterialBaseInfoId()
+ "materialInventoryId=" + outMaterialDetailDO.getMaterialInventoryId()
+ "materialCode=" + outMaterialDetailDO.getMaterialCode()
+ ",出库单号=" + stockOutOrderDO.getOutOrderNumber());
}
// 回填纠正后的物料ID,脏数据随保存落库自愈
outMaterialDetailDO.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
outMaterialDetailDO.setMaterialCode(materialBaseInfoPO.getMaterialCode());
outMaterialDetailDO.setMaterialName(materialBaseInfoPO.getMaterialName());
outMaterialDetailDO.setBarCode(materialBaseInfoPO.getBarCode());