diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ExcelParseService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ExcelParseService.java index 16f20b0d8..a933251e5 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ExcelParseService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ExcelParseService.java @@ -167,6 +167,10 @@ public class ExcelParseService { stockOutOrder.setCheckTaskDistribution(2); //校验必填字段 StringBuilder errorMsg = check(stockOutOrder); + // 日期单元格有值但解析失败:与物料一致性错误同等对待,阻断导入并明确提示,杜绝静默丢日期 + for (String dateError : headerListener.getDateParseErrors()) { + errorMsg.append(dateError).append("; "); + } // 物料一致性校验错误与必填校验错误合并,任一有错都不落库 if (materialConsistencyErrors.length() > 0) { errorMsg.insert(0, materialConsistencyErrors); @@ -453,8 +457,6 @@ public class ExcelParseService { if (materialList != null && !materialList.isEmpty()) { materialBaseInfoPO = materialList.get(0); materialBaseInfoId = materialBaseInfoPO.getMaterialBaseInfoId(); - BigDecimal unitPrice = materialBaseInfoPO.getUnitPrice(); - outMaterialDetailDO.setUnitPrice(unitPrice); // ==== 新增:料号与规格型号/SKU码一致性校验(档案有值就必须一致,Excel留空视为不一致;档案为空跳过)==== String excelSpec = StringUtils.trimToEmpty(outMaterialDetailDO.getSpecificationModel()); if (StringUtils.isNotBlank(materialBaseInfoPO.getSpecificationModel()) @@ -516,6 +518,8 @@ public class ExcelParseService { // 设置物料基础信息ID,并从物料基础信息中回填料号、物料编码、物料名称等信息 if (materialBaseInfoId != null && materialBaseInfoPO != null) { outMaterialDetailDO.setMaterialBaseInfoId(materialBaseInfoId); + // 单价统一从物料档案回填(原逻辑仅料号匹配时回填,SKU/商品名称匹配时漏填,导致编辑页部分行单价为空) + outMaterialDetailDO.setUnitPrice(materialBaseInfoPO.getUnitPrice()); // 如果Excel中没有料号,从物料基础信息中回填 if (StringUtils.isBlank(outMaterialDetailDO.getMaterialNo())) { outMaterialDetailDO.setMaterialNo(materialBaseInfoPO.getMaterialCode()); diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/HeaderListener.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/HeaderListener.java index 3c8f19dba..e2286e6e3 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/HeaderListener.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/HeaderListener.java @@ -17,6 +17,9 @@ import java.util.List; import java.util.Map; import java.util.Collections; import java.util.ArrayList; +import org.apache.poi.ss.usermodel.DateUtil; // Excel序列号转日期,POI已随EasyExcel引入 +import java.text.ParseException; +import java.text.SimpleDateFormat; /** * 解析表头信息的监听器(处理前11行) @@ -25,6 +28,8 @@ import java.util.ArrayList; public class HeaderListener extends AnalysisEventListener> { private final StockOutOrderDO stockOutOrder = new StockOutOrderDO(); private int rowNum = 0; + /** 日期单元格有值但格式无法识别的收集器(类上已有@Getter,自动生成getDateParseErrors()) */ + private final List dateParseErrors = new ArrayList<>(); private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @Override @@ -55,10 +60,9 @@ public class HeaderListener extends AnalysisEventListener> stockOutOrder.setDriverSign(getStringValue(rowData[16])); break; case 6: // 第7行:出仓日期、fax、运输方式、申报地关区 - LocalDate localDate = parseDate(getStringValue(rowData[2])); - if (localDate != null) { - Date date = Date.from(localDate.atStartOfDay(ZoneId.of("UTC")).toInstant()); - stockOutOrder.setOutboundDate(date); + Date outboundDate = parseDateSmart(getStringValue(rowData[2]), "出仓日期"); + if (outboundDate != null) { + stockOutOrder.setOutboundDate(outboundDate); } stockOutOrder.setFax(getStringValue(rowData[5])); stockOutOrder.setTransportModeName(getStringValue(rowData[12])); @@ -68,10 +72,9 @@ public class HeaderListener extends AnalysisEventListener> stockOutOrder.setManufacturer(getStringValue(rowData[2])); stockOutOrder.setCustomerName(getStringValue(rowData[5])); stockOutOrder.setCustomsDeclarerInfo(getStringValue(rowData[12])); - LocalDate CompleteTime = parseDate(getStringValue(rowData[16])); - if (CompleteTime != null) { - Date date = Date.from(CompleteTime.atStartOfDay(ZoneId.of("UTC")).toInstant()); - stockOutOrder.setCompleteTime(date); + Date completeTime = parseDateSmart(getStringValue(rowData[16]), "完成时间"); + if (completeTime != null) { + stockOutOrder.setCompleteTime(completeTime); } break; case 8: // 第9行:监管方式、送货地址、海关是否查验货物、进出境关别 @@ -82,10 +85,9 @@ public class HeaderListener extends AnalysisEventListener> stockOutOrder.setEntryExitCustoms(getStringValue(rowData[16])); break; case 9: // 第10行:下单日期、起运国 - LocalDate orderDate = parseDate(getStringValue(rowData[2])); + Date orderDate = parseDateSmart(getStringValue(rowData[2]), "下单日期"); if (orderDate != null) { - Date date = Date.from(orderDate.atStartOfDay(ZoneId.of("UTC")).toInstant()); - stockOutOrder.setOrderDate(date); + stockOutOrder.setOrderDate(orderDate); } stockOutOrder.setDepartureArrivalCountry(getStringValue(rowData[5])); break; @@ -160,19 +162,41 @@ public class HeaderListener extends AnalysisEventListener> } /** - * 解析日期 + * 智能解析日期:兼容 Excel 日期单元格序列号 + 常见文本格式。 + * 解析失败记入 dateParseErrors,由 ExcelParseService 汇总返回给用户,不再静默丢失。 + * @param raw 单元格原始字符串 + * @param fieldLabel 字段中文名(用于错误提示) */ - private LocalDate parseDate(String dateStr) { - if (dateStr == null || dateStr.isEmpty()) { - return null; + private Date parseDateSmart(String raw, String fieldLabel) { + String s = raw == null ? "" : raw.trim(); + if (s.isEmpty()) { + return null; // 单元格为空:保持原行为,不算错误 } - try { - if (dateStr.contains(" ")) { - dateStr = dateStr.split(" ")[0]; + // 1) Excel 日期序列号:NoConvertConverter 会把真日期单元格转成纯数字字符串(如 46234 = 2026-08-20) + // 区间 20000~60000 对应 1954~2064 年,避免把普通数字误判成日期 + if (s.matches("^\\d{1,5}(\\.\\d+)?$")) { + try { + double serial = Double.parseDouble(s); + if (serial >= 20000 && serial <= 60000) { + return DateUtil.getJavaDate(serial); + } + } catch (NumberFormatException ignore) { } - return LocalDate.parse(dateStr, dateFormatter); - } catch (Exception e) { - return null; } + // 2) 常见文本格式逐个尝试 + String[] patterns = { + "yyyy-MM-dd", "yyyy/M/d", "yyyy.M.d", "yyyy年M月d日", + "yyyy-MM-dd HH:mm:ss", "yyyy/M/d H:mm:ss", "yyyy-MM-dd H:mm" + }; + for (String p : patterns) { + try { + SimpleDateFormat sdf = new SimpleDateFormat(p); + sdf.setLenient(false); + return sdf.parse(s); + } catch (ParseException ignore) { + } + } + dateParseErrors.add(fieldLabel + "「" + s + "」格式无法识别,请改为日期格式或 2026-08-20 / 2026/8/20 / 2026年8月20日 等格式"); + return null; } } \ No newline at end of file diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ItemListener.java b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ItemListener.java index 8a800bcb4..3cbaeb323 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ItemListener.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/stockOutOrder/repository/listener/ItemListener.java @@ -57,7 +57,7 @@ public class ItemListener extends AnalysisEventListener> { item.setInvoiceNo(getStringValue(rowData[5])); item.setDeclareQuantity(parseBigDecimal(rowData[6])); item.setDeclareUnit(getStringValue(rowData[7])); - item.setLiter(getStringValue(rowData[8])); + item.setLiter(normalizeNumeric(getStringValue(rowData[8]))); item.setCaseCount(parseBigDecimal(rowData[9])); item.setPieceCount(parseBigDecimal(rowData[10])); item.setSize(getStringValue(rowData[11])); @@ -285,6 +285,25 @@ public class ItemListener extends AnalysisEventListener> { } } + /** + * 升列归一化:去千分位逗号、去空白;空值统一存 null(避免前端合计时对空串求和); + * 纯数字归一为无多余零的字符串;非数字内容原样保留。 + */ + private String normalizeNumeric(String raw) { + if (raw == null) { + return null; + } + String s = raw.trim().replace(",", ""); + if (s.isEmpty()) { + return null; + } + try { + return new BigDecimal(s).stripTrailingZeros().toPlainString(); + } catch (NumberFormatException e) { + return s; + } + } + public static boolean isSequenceNumber(String str) { if (str == null || str.trim().isEmpty()) {