From 4ad31a870a3bc2a3587378cd568d38d29a4a9348 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Thu, 3 Sep 2026 15:52:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(oms):=20=E7=89=A9=E6=96=99=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E9=98=B2=E5=BC=A0=E5=86=A0=E6=9D=8E=E6=88=B4+?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E4=B8=89=E7=B1=BB=E6=A0=A1=E9=AA=8C=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E6=8A=A5=E9=94=99=EF=BC=8C=E5=AE=A2=E6=88=B7=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E5=8F=AA=E8=AE=A4USER=5FMEMBER=5FCODE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、出库业务单导入(ReservationStockInOrderImpl) - 物料不存在/客户不存在/批次库存不足三类校验全部收集完成后统一报错,不再分阶段中断 - materialCache 取用统一 trim,防 Excel 编号带空格导致校验被跳过 二、物料三级解析防张冠李戴(MaterialBaseInfoResolveService) - baseId 按主键查到的档案与明细编码不一致时不再信任 baseId,转库存ID反查/编码+组织兜底,两级落空才退回按ID档案,命中打WARN日志 - 背景:前端把库存ID传进 materialBaseInfoId,撞上编码=名称的脏档案主键后整行物料被覆盖成错误物料 三、客户编码口径(reservationStockOutOrder/ReservationStockOutOrderMapper.xml) - queryCustomerByCode 只认 USER.USER_MEMBER_CODE,不再放行 USER_SHIPPER.CUSTOMER_NC_CODE 四、明细查询补字段(reserveStockOutOrder/ReserveStockOutOrderMapper.xml) - resultMap 补 status/issue_status 映射,detailsList 查询补查 a.status, a.issue_status --- .../MaterialBaseInfoResolveService.java | 26 ++++++-- .../ReservationStockInOrderImpl.java | 66 ++++++++++--------- .../ReservationStockOutOrderMapper.xml | 8 +-- .../ReserveStockOutOrderMapper.xml | 4 +- 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/materialBaseInfo/service/MaterialBaseInfoResolveService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/materialBaseInfo/service/MaterialBaseInfoResolveService.java index 510ca34b6..02b37e095 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/materialBaseInfo/service/MaterialBaseInfoResolveService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/materialBaseInfo/service/MaterialBaseInfoResolveService.java @@ -5,6 +5,7 @@ import com.mhd.oms.domain.reservationMaterialInventory.entity.ReservationMateria 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 lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -13,6 +14,7 @@ import org.springframework.stereotype.Service; * 解决客户端推单把 materialInventoryId 传进 materialBaseInfoId 字段、或漏传导致明细存脏ID的问题。 * 三级解析:1.按baseId直查 → 2.按库存ID反查纠正 → 3.按物料编码+组织兜底 */ +@Slf4j @Service public class MaterialBaseInfoResolveService { @@ -30,11 +32,23 @@ public class MaterialBaseInfoResolveService { * @return 解析成功返回物料信息(调用方需用 po.getMaterialBaseInfoId() 回填纠正后的ID);全部失败返回 null */ public MaterialBaseInfoPO resolve(Long materialBaseInfoId, Long materialInventoryId, String materialCode, Long organizationId) { - // 1. 优先按 baseId 直查(有效ID不受影响) + // 1. 优先按 baseId 直查(有效ID不受影响)。 + // 防张冠李戴:客户端曾把库存ID传进 materialBaseInfoId,若该库存ID恰好撞上另一条有效档案的主键 + // (如库存1201撞上编码=名称的脏档案"水箱"),按ID查到的档案编码会与明细编码对不上。 + // 此时 baseId 不可信,继续走库存反查/编码兜底;两级都落空时才退回按ID查到的档案 + MaterialBaseInfoPO byId = null; if (materialBaseInfoId != null && materialBaseInfoId > 0) { MaterialBaseInfoPO po = reservationStockInOrderMapper.getinfo(materialBaseInfoId); if (po != null) { - return po; + if (StringUtils.isNotBlank(materialCode) && po.getMaterialCode() != null + && !po.getMaterialCode().trim().equals(materialCode.trim())) { + log.warn("物料ID疑似误传:baseInfoId={} 按主键查到档案[{}({})],与明细编码[{}]不一致," + + "转库存反查/编码兜底(客户端可能把库存ID传进了materialBaseInfoId)", + materialBaseInfoId, po.getMaterialCode(), po.getMaterialName(), materialCode); + byId = po; + } else { + return po; + } } } // 2. baseId 无效但带了库存ID → 反查库存表拿正确 baseInfoId(修复"库存ID传进baseId"场景) @@ -50,8 +64,12 @@ public class MaterialBaseInfoResolveService { } // 3. 兜底:按物料编码+组织查(修复 baseId=0 漏传场景) if (StringUtils.isNotBlank(materialCode) && organizationId != null) { - return reservationStockInOrderMapper.getByCode(materialCode.trim(), organizationId); + MaterialBaseInfoPO po = reservationStockInOrderMapper.getByCode(materialCode.trim(), organizationId); + if (po != null) { + return po; + } } - return null; + // 编码不一致已证明 baseId 不可信,但库存反查/编码兜底都落空时,退回按ID查到的档案,保持原有兜底行为 + return byId; } } diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java index 9d7774ba5..21ed397d6 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java @@ -297,16 +297,39 @@ public class ReservationStockInOrderImpl extends ServiceImpl problems = new ArrayList<>(); - if (!missingMaterials.isEmpty()) { - problems.add("物料[" + String.join("、", missingMaterials) + "]不存在,请先维护物料信息"); + // ====== 第三步:物料/客户/库存三类校验全部完成后统一报错(不再分阶段各自中断) ====== + List problems = new ArrayList<>(); + if (!missingMaterials.isEmpty()) { + problems.add("物料[" + String.join("、", missingMaterials) + "]不存在,请先维护物料信息"); + } + if (!missingCustomers.isEmpty()) { + problems.add("客户[" + String.join("、", missingCustomers) + "]不存在,请先维护客户信息"); + } + + // 库存预检:只对已存在的物料比较批次库存(不存在的物料问题已收集),不足的同样收集 + if (!productDetailList.isEmpty()) { + Set insufficientMaterials = new HashSet<>(); + for (ProductDetail productDetail : productDetailList) { + // materialCache 的 key 是 trim 过的编码,取用时同样 trim,避免 Excel 编号带空格导致校验被静默跳过 + String productCode = productDetail.getProductCode() == null ? "" : productDetail.getProductCode().trim(); + MaterialBaseInfoPO materialBaseInfoPO = materialCache.get(productCode); + if (materialBaseInfoPO == null) { + continue; // 物料不存在的问题已收集 + } + BigDecimal requireQty = new BigDecimal(productDetail.getPieceCount()).setScale(2, BigDecimal.ROUND_HALF_UP); + List inventoryList = stockInOrderMapper.getkcId( + materialBaseInfoPO.getMaterialBaseInfoId(), requireQty, warehouseId); + if (inventoryList == null || inventoryList.isEmpty()) { + insufficientMaterials.add(productCode); + } } - if (!missingCustomers.isEmpty()) { - problems.add("客户[" + String.join("、", missingCustomers) + "]不存在,请先维护客户信息"); + if (!insufficientMaterials.isEmpty()) { + problems.add("物料[" + String.join("、", insufficientMaterials) + "]" + + (warehouseId != null && warehouseId > 0 ? "在当前仓库" : "") + "批次库存数量少于出库数量"); } - throw new Exception(String.join(";", problems) + ",维护完成后重新导入"); + } + if (!problems.isEmpty()) { + throw new Exception(String.join(";", problems) + ",请处理后重新导入"); } // ====== 后续处理(编号均已存在):货主归属校验、建单落库 ====== @@ -332,28 +355,6 @@ public class ReservationStockInOrderImpl extends ServiceImpl insufficientMaterials = new HashSet<>(); - for (ProductDetail productDetail : productDetailList) { - MaterialBaseInfoPO materialBaseInfoPO = materialCache.get(productDetail.getProductCode()); - if (materialBaseInfoPO == null) { - continue; // 存在性已在前面统一校验 - } - BigDecimal requireQty = new BigDecimal(productDetail.getPieceCount()).setScale(2, BigDecimal.ROUND_HALF_UP); - List inventoryList = stockInOrderMapper.getkcId( - materialBaseInfoPO.getMaterialBaseInfoId(), requireQty, warehouseId); - if (inventoryList == null || inventoryList.isEmpty()) { - insufficientMaterials.add(productDetail.getProductCode()); - } - } - if (!insufficientMaterials.isEmpty()) { - throw new Exception("物料[" + String.join("、", insufficientMaterials) + "]" - + (warehouseId != null && warehouseId > 0 ? "在当前仓库" : "") + "批次库存数量少于出库数量!"); - } - } - ReservationStockOutOrderDO reservationStockOutOrderDO = new ReservationStockOutOrderDO(); // 生成出库业务单号 String orderNumber = reservationStockOutOrderApplicationService.generateReserveOrderNumber(); @@ -407,8 +408,9 @@ public class ReservationStockInOrderImpl extends ServiceImpl + 客户编码只认 USER.USER_MEMBER_CODE(客户编码); + 2026-09-03 起不再放行 USER_SHIPPER.CUSTOMER_NC_CODE(NC客户代码),避免会员/NC编码撞车导致不存在的客户编号误通过 --> select - b.* + b.*, a.status, a.issue_status from reserve_stock_out_order a left join reserve_out_material_detail b on a.OUT_ORDER_NUMBER = b.OUT_ORDER_NUMBER