Browse Source

fix(qms): 解决质检模块Excel导入和数据验证问题

- 添加检验类型编码验证,避免未知类型导致ItemNo为空
- 修复Excel导入时空行和空单元格处理逻辑
- 改进getCellStringValue方法,正确读取Excel单元格文本值
- 添加多个数据验证检查,包括项目描述、值类型等必填项验证
- 修复inspectTypeNo映射缺失的GX和***类型处理逻辑
- 优化数值类型字段的最大最小值解析逻辑
- 统一错误消息格式,提供更清晰的错误提示信息
master
qiankanghui 1 month ago
parent
commit
9b9ef49811
  1. 64
      src/main/java/com/xujie/modules/inspection/service/impl/InspectionRequestServiceImpl.java
  2. 99
      src/main/java/com/xujie/modules/qms/service/impl/QcServiceImpl.java

64
src/main/java/com/xujie/modules/inspection/service/impl/InspectionRequestServiceImpl.java

@ -340,22 +340,22 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
// 3. 【关键】查询现有明细,构建已存在的产品映射(key = orderNo + "|" + itemNo)
List<InspectionRequestDetailSub> existingSubList =
inspectionRequestDetailSubMapper.selectByRequestNo(site, requestNo);
Map<String, InspectionRequestDetailSub> existingSubMap = new HashMap<>();
Map<String, InspectionRequestDetail> existingDetailMap = new HashMap<>();
for (InspectionRequestDetailSub sub : existingSubList) {
String key = sub.getPoNo() + "|" + sub.getPoItemNo();
existingSubMap.put(key, sub);
}
List<InspectionRequestDetail> existingDetailList =
inspectionRequestDetailMapper.selectByRequestNo(site, requestNo);
for (InspectionRequestDetail detail : existingDetailList) {
// Detail没有直接存储PO信息,需要通过itemNo关联
// 这里先构建 itemNo -> Detail 的映射
}
// 构建 itemNo -> Detail 的映射(用于后续更新)
Map<Integer, InspectionRequestDetail> itemNoToDetailMap = new HashMap<>();
for (InspectionRequestDetail detail : existingDetailList) {
@ -381,23 +381,23 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
for (Map.Entry<String, List<InspectionRequestItemVO>> entry : newItemsGroupMap.entrySet()) {
String productKey = entry.getKey();
List<InspectionRequestItemVO> newItemsList = entry.getValue();
// 计算本次新增的总数量
BigDecimal newTotalQty = BigDecimal.ZERO;
for (InspectionRequestItemVO item : newItemsList) {
newTotalQty = newTotalQty.add(item.getQty());
}
InspectionRequestItemVO firstItem = newItemsList.get(0);
// 6. 判断该产品是否已存在于明细中
InspectionRequestDetailSub existingSub = existingSubMap.get(productKey);
if (existingSub != null) {
// 【情况A】产品已存在,需要累加数量到已有明细
log.info("产品已存在,累加数量: productKey={}, 原有数量={}, 新增数量={}",
productKey, existingSub.getQty(), newTotalQty);
// 6.1 锁定PO的验货数量(只锁定本次新增的数量)
int updateCount = poDetailMapper.lockInspectQty(
site,
@ -405,7 +405,7 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
firstItem.getItemNo(),
newTotalQty
);
if (updateCount == 0) {
throw new RuntimeException(
"PO:" + firstItem.getOrderRef1()
@ -413,21 +413,21 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
+ " 可申请验货数量不足"
);
}
// 6.2 更新Sub表的数量(累加)
BigDecimal updatedQty = existingSub.getQty().add(newTotalQty);
UpdateWrapper<InspectionRequestDetailSub> subUpdateWrapper = new UpdateWrapper<>();
subUpdateWrapper.eq("site", site)
.eq("request_no", requestNo)
.eq("item_no", existingSub.getItemNo())
.eq("po_no", firstItem.getOrderNo())
.eq("po_item_no", firstItem.getItemNo());
InspectionRequestDetailSub updateSub = new InspectionRequestDetailSub();
updateSub.setQty(updatedQty);
inspectionRequestDetailSubMapper.update(updateSub, subUpdateWrapper);
// 6.3 更新Detail表的数量(累加)
InspectionRequestDetail existingDetail = itemNoToDetailMap.get(existingSub.getItemNo());
if (existingDetail != null) {
@ -435,26 +435,26 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
detailUpdateWrapper.eq("site", site)
.eq("request_no", requestNo)
.eq("item_no", existingSub.getItemNo());
InspectionRequestDetail updateDetail = new InspectionRequestDetail();
updateDetail.setQty(existingDetail.getQty().add(newTotalQty));
inspectionRequestDetailMapper.update(updateDetail, detailUpdateWrapper);
}
log.info("累加完成: productKey={}, 新总数={}", productKey, updatedQty);
} else {
// 【情况B】产品不存在,需要新增明细行
log.info("产品不存在,新增明细: productKey={}, 数量={}", productKey, newTotalQty);
// 获取当前最大的itemNo
Integer maxItemNo = inspectionRequestDetailMapper.selectMaxItemNo(site, requestNo);
int newItemNo = (maxItemNo == null ? 0 : maxItemNo) + 1;
// 查询PO信息(用于isChanged判断)
java.util.Map<String, Object> poInfo = poDetailMapper.getPoDetailInspectInfo(
site, firstItem.getOrderNo(), firstItem.getItemNo());
// 锁定验货数量
int updateCount = poDetailMapper.lockInspectQty(
site,
@ -462,7 +462,7 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
firstItem.getItemNo(),
newTotalQty
);
if (updateCount == 0) {
throw new RuntimeException(
"PO:" + firstItem.getOrderRef1()
@ -470,7 +470,7 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
+ " 可申请验货数量不足"
);
}
// 保存Sub
InspectionRequestDetailSub sub = new InspectionRequestDetailSub();
sub.setSite(site);
@ -484,15 +484,15 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
sub.setOrderRef2(firstItem.getOrderRef2());
sub.setShipMothod(firstItem.getShipMethod());
sub.setCrd(firstItem.getCrd());
// 计算数量是否变更(新增的产品,原始数量为0)
Object qtyObj = poInfo.get("waitInspectQty");
BigDecimal originQty = qtyObj == null ? BigDecimal.ZERO : new BigDecimal(qtyObj.toString());
boolean isChanged = newTotalQty.compareTo(originQty) != 0;
sub.setIsChanged(isChanged ? "Y" : "N");
inspectionRequestDetailSubMapper.insert(sub);
// 保存Detail
InspectionRequestDetail detail = new InspectionRequestDetail();
detail.setSite(site);
@ -504,7 +504,7 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
detail.setStatus("草稿");
detail.setStatusDb("Draft");
inspectionRequestDetailMapper.insert(detail);
log.info("新增明细完成: productKey={}, itemNo={}", productKey, newItemNo);
}
}
@ -546,7 +546,7 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
// 3. 【关键】在回滚之前,先保存旧明细的原始数量信息(用于后续 isChanged 判断)
List<InspectionRequestDetailSub> oldSubList =
inspectionRequestDetailSubMapper.selectByRequestNo(site, requestNo);
// 构建旧明细的数量映射:key = orderNo + "|" + itemNo, value = qty
Map<String, BigDecimal> oldQtyMap = new HashMap<>();
for (InspectionRequestDetailSub sub : oldSubList) {
@ -600,23 +600,23 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
for (Map.Entry<String, List<InspectionRequestItemVO>> entry : itemsGroupMap.entrySet()) {
String productKey = entry.getKey();
List<InspectionRequestItemVO> itemsList = entry.getValue();
// 计算该产品的总数量(累加)
BigDecimal totalQty = BigDecimal.ZERO;
for (InspectionRequestItemVO item : itemsList) {
totalQty = totalQty.add(item.getQty());
}
// 使用第一条数据的其他字段
InspectionRequestItemVO firstItem = itemsList.get(0);
// 数据校验:数量必须为正数
if (totalQty.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException(
"PO:" + firstItem.getOrderNo() + " 行:" + firstItem.getItemNo() + " 的验货数量必须大于0"
);
}
// 锁定验货数量(使用合并后的总数量)
int updateCount = poDetailMapper.lockInspectQty(
site,
@ -1131,7 +1131,7 @@ public class InspectionRequestServiceImpl extends ServiceImpl<InspectionRequestH
Map<String, Object> poInfo = poDetailMapper.getPoDetailByOrderRef(site, dto.getPoNo(), dto.getPoItemNo());
if (poInfo == null) {
throw new RuntimeException(String.format(
"第%d行:PO号 %s 行号 %s 不存在,请检查外部订单号(order_ref1)和外部行号(order_ref2)",
"第%d行:PO号 %s 行号 %s 不存在,请检查外部订单号",
rowNum, dto.getPoNo(), dto.getPoItemNo()));
}

99
src/main/java/com/xujie/modules/qms/service/impl/QcServiceImpl.java

@ -228,6 +228,10 @@ public class QcServiceImpl implements QcService {
}else if ("***".equals(data.getInspectionTypeNo())) {
data.setItemNo("PF-" + itemNo);
}
// 检验类型不在编码规则内时直接报错,避免 ItemNo 为空导致 INSERT 失败
if (StringUtils.isBlank(data.getItemNo())) {
throw new RuntimeException("未知的检验类型编码:" + data.getInspectionTypeNo() + ",无法生成项目编码!");
}
List<QcItemData> checkItemOnlyOne = qcMapper.checkItemOnlyOne(data);
if (!checkItemOnlyOne.isEmpty()) {
throw new RuntimeException("该检验项目已存在!");
@ -315,13 +319,25 @@ public class QcServiceImpl implements QcService {
QcItemData task = null;
// 遍历每一行(从第二行开始)
for (int j = 1; j < rows; j++) {
// 实例化对象
task = new QcItemData();
//获得该行
XSSFRow row = sheet.getRow(j);
if (row == null) {
continue;
}
// 读取关键列(不能用 getRawValue,文本单元格它返回的是共享字符串表索引而非真实文本)
String itemDesc = getCellStringValue(row.getCell(0));
String inspectionTypeNo = getCellStringValue(row.getCell(3));
// 关键列均为空视为空行,跳过(避免模板残留空行导致插入失败)
if (StringUtils.isBlank(itemDesc) && StringUtils.isBlank(inspectionTypeNo)) {
continue;
}
// 实例化对象
task = new QcItemData();
// 为对象赋值
QcSiteBuHelper.resolveSiteParam(data.getSite(), task::setSite, task::setBuNo);
task.setInspectionTypeNo(row.getCell(3).getRawValue());
// itemType 必须在 getItemNo 之前设置,否则按类型查不到已有编码,每行都会生成相同编码
task.setItemType("D");
task.setInspectionTypeNo(inspectionTypeNo);
// 获取编码
String itemNo = qcMapper.getItemNo(task);
if ("101".equals(task.getInspectionTypeNo()) || "102".equals(task.getInspectionTypeNo()) || "103".equals(task.getInspectionTypeNo()) || "104".equals(task.getInspectionTypeNo())) {
@ -334,36 +350,40 @@ public class QcServiceImpl implements QcService {
task.setItemNo("FQC-" + itemNo);
} else if ("108".equals(task.getInspectionTypeNo())) {
task.setItemNo("PQC-" + itemNo);
} else if ("GX".equals(task.getInspectionTypeNo())) {
task.setItemNo("GX-" + itemNo);
} else if ("***".equals(task.getInspectionTypeNo())) {
task.setItemNo("PF-" + itemNo);
}
// 检验类型编码无效时明确报错,避免 ItemNo 为空导致 INSERT 失败
if (StringUtils.isBlank(task.getItemNo())) {
throw new RuntimeException("第" + (j + 1) + "行检验类型编码无效:" + task.getInspectionTypeNo());
}
if (StringUtils.isBlank(itemDesc)) {
throw new RuntimeException("第" + (j + 1) + "行检验项目描述不能为空!");
}
task.setItemDesc(row.getCell(0).getStringCellValue());
task.setValueType(row.getCell(1).getStringCellValue());
if (row.getCell(1).getStringCellValue().equals("文本")){
task.setItemDesc(itemDesc.trim());
String valueType = getCellStringValue(row.getCell(1));
if (StringUtils.isBlank(valueType)) {
throw new RuntimeException("第" + (j + 1) + "行值类型不能为空!");
}
task.setValueType(valueType);
if (valueType.equals("文本")){
task.setValueTypeDb("T");
task.setMaxValue(null);
task.setMinValue(null);
} else if (row.getCell(1).getStringCellValue().equals("数字")) {
} else if (valueType.equals("数字")) {
task.setValueTypeDb("N");
if (row.getCell(3) == null) {
task.setMaxValue(null);
} else {
task.setMaxValue(BigDecimal.valueOf(row.getCell(3).getNumericCellValue()));
}
if (row.getCell(4) == null) {
task.setMinValue(null);
} else {
task.setMinValue(BigDecimal.valueOf(row.getCell(4).getNumericCellValue()));
}
} else {
task.setValueTypeDb(null);
}
if (row.getCell(2) == null) {
task.setDefaultValue(null);
String maxValue = getCellStringValue(row.getCell(4));
task.setMaxValue(StringUtils.isBlank(maxValue) ? null : new BigDecimal(maxValue.trim()));
String minValue = getCellStringValue(row.getCell(5));
task.setMinValue(StringUtils.isBlank(minValue) ? null : new BigDecimal(minValue.trim()));
} else {
task.setDefaultValue(row.getCell(2).getRawValue());
throw new RuntimeException("第" + (j + 1) + "行值类型无效:" + valueType + "(只能填写 文本 或 数字)");
}
task.setItemType("D");
task.setDefaultValue(getCellStringValue(row.getCell(2)));
task.setCreatedBy(data.getCreateBy());
task.setMethodNo(row.getCell(5).getRawValue());
task.setMethodNo(getCellStringValue(row.getCell(6)));
// 新增
qcMapper.qcItemSave(task);
// 新增项目里的方法
@ -374,6 +394,35 @@ public class QcServiceImpl implements QcService {
}
}
/**
* 读取单元格的实际文本值。
* 不能直接使用 getRawValue():对文本单元格它返回的是共享字符串表的索引而不是真实文本;
* 这里按单元格类型分别取值,保证拿到用户在 Excel 中看到的内容。
**/
private String getCellStringValue(XSSFCell cell) {
if (cell == null) {
return null;
}
CellType cellType = cell.getCellType();
if (cellType == CellType.FORMULA) {
cellType = cell.getCachedFormulaResultType();
}
switch (cellType) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
}
// 按单元格显示格式取值,避免出现 105.0、科学计数法等格式偏差
return new DataFormatter().formatCellValue(cell).trim();
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return null;
}
}
// ======================= 质检员信息维护 =======================
/**

Loading…
Cancel
Save