From 3aec384ba0ad0a62838a6ce92b1e07173570f4bf Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 21 Aug 2026 14:56:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(wms):=E5=87=BA=E5=BA=93=E5=A7=94=E6=89=98?= =?UTF-8?q?=E5=8D=95=E5=AF=BC=E5=85=A5=E5=87=BA=E4=BB=93=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E3=80=81=E5=8D=87=E5=88=97=E4=B8=8E=E5=8D=95?= =?UTF-8?q?=E4=BB=B7=E5=9B=9E=E5=A1=AB=E4=BF=AE=E5=A4=8D=20-=20HeaderListe?= =?UTF-8?q?ner=EF=BC=9AparseDate=E5=8D=87=E7=BA=A7=E4=B8=BAparseDateSmart?= =?UTF-8?q?=EF=BC=8C=E5=85=BC=E5=AE=B9Excel=E6=97=A5=E6=9C=9F=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8F=B7(20000~60000=E2=89=881954~2064=E5=B9=B4?= =?UTF-8?q?=EF=BC=8C=20=20=20POI=20DateUtil.getJavaDate=E8=BD=AC=E6=8D=A2)?= =?UTF-8?q?=E5=8F=8Ayyyy/M/d=E3=80=81yyyy.M.d=E3=80=81yyyy=E5=B9=B4M?= =?UTF-8?q?=E6=9C=88d=E6=97=A5=E7=AD=89=E5=A4=9A=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E6=96=87=E6=9C=AC=EF=BC=9B=20=20=20=E5=87=BA=E4=BB=93=E6=97=A5?= =?UTF-8?q?=E6=9C=9F/=E5=AE=8C=E6=88=90=E6=97=B6=E9=97=B4/=E4=B8=8B?= =?UTF-8?q?=E5=8D=95=E6=97=A5=E6=9C=9F=E4=B8=89=E5=A4=84=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E7=82=B9=E5=90=8C=E6=AD=A5=E5=88=87=E6=8D=A2=EF=BC=9B=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=A4=B1=E8=B4=A5=E8=AE=B0=E5=85=A5dateParseErrors?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E9=9D=99=E9=BB=98=20-=20ExcelParseService?= =?UTF-8?q?=EF=BC=9A=E5=AF=BC=E5=85=A5=E6=A0=A1=E9=AA=8C=E6=B1=87=E6=80=BB?= =?UTF-8?q?dateParseErrors=EF=BC=8C=E6=97=A5=E6=9C=9F=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=A0=BC=E6=9C=89=E5=80=BC=E4=BD=86=E6=A0=BC=E5=BC=8F=E4=B8=8D?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E6=97=B6=E9=98=BB=E6=96=AD=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=B9=B6=20=20=20=E6=98=8E=E7=A1=AE=E6=8F=90=E7=A4=BA=EF=BC=88?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E4=BB=8D=E6=94=BE=E8=A1=8C=EF=BC=8C=E4=B8=8E?= =?UTF-8?q?=E5=8E=9F=E8=A1=8C=E4=B8=BA=E4=B8=80=E8=87=B4=EF=BC=89=EF=BC=9B?= =?UTF-8?q?=E5=8D=95=E4=BB=B7=E5=9B=9E=E5=A1=AB=E4=BB=8E=E6=96=99=E5=8F=B7?= =?UTF-8?q?=E5=88=86=E6=94=AF=E7=A7=BB=E8=87=B3=E6=96=99=E5=8F=B7/SKU/?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E4=B8=89=E5=88=86=E6=94=AF=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=9B=9E=E5=A1=AB=E5=9D=97=EF=BC=8C=20=20=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?SKU=E3=80=81=E5=95=86=E5=93=81=E5=90=8D=E7=A7=B0=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E5=9C=BA=E6=99=AF=20-=20ItemListener=EF=BC=9A?= =?UTF-8?q?=E5=8D=87=E5=88=97=E6=96=B0=E5=A2=9EnormalizeNumeric=E5=BD=92?= =?UTF-8?q?=E4=B8=80=E5=8C=96=EF=BC=8C=E5=8E=BB=E5=8D=83=E5=88=86=E4=BD=8D?= =?UTF-8?q?=E9=80=97=E5=8F=B7=E3=80=81=E7=A9=BA=E5=80=BC=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=AD=98null=E3=80=81=E7=BA=AF=E6=95=B0=E5=AD=97=E5=8E=BB?= =?UTF-8?q?=E5=B0=BE=E9=9B=B6=EF=BC=8C=20=20=20=E8=90=BD=E5=BA=93=E5=80=BC?= =?UTF-8?q?=E6=81=92=E4=B8=BA"=E7=BA=AF=E6=95=B0=E5=AD=97=E6=88=96null"?= =?UTF-8?q?=EF=BC=8C=E5=89=8D=E7=AB=AF=E5=90=88=E8=AE=A1=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E4=BA=A7=E7=94=9FNaN/=E6=8B=BC=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/ExcelParseService.java | 8 ++- .../repository/listener/HeaderListener.java | 66 +++++++++++++------ .../repository/listener/ItemListener.java | 21 +++++- 3 files changed, 71 insertions(+), 24 deletions(-) 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()) {