feat(wms):出入库导入增加料号与规格型号/SKU码一致性校验

- 料号命中物料档案后:规格型号/SKU码必须与档案一致,Excel留空且档案有值同样报错(提示Excel:空);档案值为空则跳过该项
- SKU以档案条码(bar_code)为基准;错误行一次性汇总提示,拒绝导入
- 出库料号已填写但货主下查不到时直接报错,不再被SKU码/商品名称静默兜底(与入库行为对齐)
This commit is contained in:
rcx
2026-08-21 14:42:25 +08:00
parent 8fedb6d6ed
commit 27f3fceb6e
2 changed files with 49 additions and 3 deletions
@@ -1827,6 +1827,8 @@ public class StockInOrderApplicationService {
.collect(Collectors.groupingBy(r -> StringUtils.isBlank(r.getConsignmentNo()) ? "__EMPTY_CONSIGNMENT__" : r.getConsignmentNo()));
List<StockInOrderDO> orderList = new ArrayList<>();
// 收集"料号与规格型号/SKU码不一致"的错误行,最后统一抛出,避免用户修一行导一次
List<String> consistencyErrors = new ArrayList<>();
for (Map.Entry<String, List<StockInOrderImportRowDTO>> entry : groupMap.entrySet()) {
List<StockInOrderImportRowDTO> groupRows = entry.getValue();
StockInOrderImportRowDTO h = groupRows.get(0);
@@ -2045,6 +2047,21 @@ public class StockInOrderApplicationService {
log.error(errorMsg);
throw new ServiceException(errorMsg);
}
// 校验料号与规格型号/SKU码的一致性:档案有值就必须一致(Excel留空视为不一致);档案为空则跳过该项
String excelSpec = StringUtils.trimToEmpty(r.getSpecificationModel());
if (StringUtils.isNotBlank(materialBaseInfoPO.getSpecificationModel())
&& !excelSpec.equals(materialBaseInfoPO.getSpecificationModel().trim())) {
consistencyErrors.add(String.format("料号[%s](商品名称:%s)规格型号与物料档案不一致(Excel:%s / 档案:%s",
r.getMaterialCode(), r.getMaterialName(),
excelSpec.isEmpty() ? "" : excelSpec, materialBaseInfoPO.getSpecificationModel().trim()));
}
String excelSku = StringUtils.trimToEmpty(r.getSkuCode());
if (StringUtils.isNotBlank(materialBaseInfoPO.getBarCode())
&& !excelSku.equals(materialBaseInfoPO.getBarCode().trim())) {
consistencyErrors.add(String.format("料号[%s](商品名称:%s)SKU码与物料档案不一致(Excel:%s / 档案:%s",
r.getMaterialCode(), r.getMaterialName(),
excelSku.isEmpty() ? "" : excelSku, materialBaseInfoPO.getBarCode().trim()));
}
// 数量已在前面检查过,这里直接使用
log.info("数量字段检查 - 仓库数量: [{}], 入库数量: [{}], 使用数量: [{}]",
@@ -2186,8 +2203,14 @@ public class StockInOrderApplicationService {
if (orderList.isEmpty()) {
throw new ServiceException("导入失败:所有入库单的明细行都因数据不完整(数量为空或物料基础信息缺失)被跳过,请检查Excel数据");
}
// 存在料号与规格型号/SKU码不一致的明细行时,拒绝导入并一次性提示全部问题行
if (!consistencyErrors.isEmpty()) {
throw new ServiceException("导入失败,以下明细行的规格型号/SKU码与物料档案不一致,请修正后重新导入:\n"
+ String.join("\n", consistencyErrors));
}
return stockInOrderDomainService.batchInsert(orderList);
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
@@ -112,7 +112,8 @@ public class ExcelParseService {
LoginUser loginUser = SecurityUtils.getLoginUser();
Long topOrganizationId = loginUser != null && loginUser.getUserPo() != null
? loginUser.getUserPo().getTopOrganizationId() : null;
initList(itemList, warehouseId, warehouseCode, warehouseName, shipperId, topOrganizationId);
StringBuilder materialConsistencyErrors = new StringBuilder();
initList(itemList, warehouseId, warehouseCode, warehouseName, shipperId, topOrganizationId, materialConsistencyErrors);
stockOutOrder.setMaterialDetailList(itemList);
stockOutOrder.setWarehouseName(warehouseName);
stockOutOrder.setWarehouseCode(warehouseCode);
@@ -166,6 +167,10 @@ public class ExcelParseService {
stockOutOrder.setCheckTaskDistribution(2);
//校验必填字段
StringBuilder errorMsg = check(stockOutOrder);
// 物料一致性校验错误与必填校验错误合并任一有错都不落库
if (materialConsistencyErrors.length() > 0) {
errorMsg.insert(0, materialConsistencyErrors);
}
if (errorMsg.length() > 0) {
return errorMsg;
} else {
@@ -416,8 +421,7 @@ public class ExcelParseService {
* @param shipperId 货主ID用于过滤物料基础信息确保查询到的是该货主绑定的物料
* @param topOrganizationId 一级组织ID用于过滤物料基础信息
*/
private void initList(List<OutMaterialDetailDO> outMaterialDetailDOList, Long warehouseId, String warehouseCode, String warehouseName, Long shipperId, Long topOrganizationId) {
for (OutMaterialDetailDO outMaterialDetailDO : outMaterialDetailDOList) {
private void initList(List<OutMaterialDetailDO> outMaterialDetailDOList, Long warehouseId, String warehouseCode, String warehouseName, Long shipperId, Long topOrganizationId, StringBuilder materialConsistencyErrors) { for (OutMaterialDetailDO outMaterialDetailDO : outMaterialDetailDOList) {
outMaterialDetailDO.setWarehouseId(warehouseId);
outMaterialDetailDO.setWarehouseCode(warehouseCode);
outMaterialDetailDO.setWarehouseName(warehouseName);
@@ -451,6 +455,25 @@ public class ExcelParseService {
materialBaseInfoId = materialBaseInfoPO.getMaterialBaseInfoId();
BigDecimal unitPrice = materialBaseInfoPO.getUnitPrice();
outMaterialDetailDO.setUnitPrice(unitPrice);
// ==== 新增料号与规格型号/SKU码一致性校验档案有值就必须一致Excel留空视为不一致档案为空跳过====
String excelSpec = StringUtils.trimToEmpty(outMaterialDetailDO.getSpecificationModel());
if (StringUtils.isNotBlank(materialBaseInfoPO.getSpecificationModel())
&& !excelSpec.equals(materialBaseInfoPO.getSpecificationModel().trim())) {
materialConsistencyErrors.append("料号「").append(outMaterialDetailDO.getMaterialNo())
.append("」规格型号与物料档案不一致(Excel:").append(excelSpec.isEmpty() ? "" : excelSpec)
.append(" / 档案:").append(materialBaseInfoPO.getSpecificationModel().trim()).append("; ");
}
String excelSku = StringUtils.trimToEmpty(outMaterialDetailDO.getSkucode());
if (StringUtils.isNotBlank(materialBaseInfoPO.getBarCode())
&& !excelSku.equals(materialBaseInfoPO.getBarCode().trim())) {
materialConsistencyErrors.append("料号「").append(outMaterialDetailDO.getMaterialNo())
.append("」SKU码与物料档案不一致(Excel:").append(excelSku.isEmpty() ? "" : excelSku)
.append(" / 档案:").append(materialBaseInfoPO.getBarCode().trim()).append("; ");
}
}else {
// 料号已填写但在该货主下查不到 直接报错与入库导入一致不再被SKU码/商品名称静默兜底
materialConsistencyErrors.append("料号「").append(materialNo)
.append("」在当前客户(货主)下不存在,请核对料号或在物料管理中维护;");
}
}