Browse Source

价格表

master
常熟吴彦祖 2 weeks ago
parent
commit
c36976e4df
  1. 262
      src/main/java/com/xujie/modules/order/support/PoImportRowMapper.java

262
src/main/java/com/xujie/modules/order/support/PoImportRowMapper.java

@ -1,23 +1,118 @@
package com.xujie.modules.order.support;
import com.xujie.modules.order.vo.PODetailVo;
import org.apache.poi.ss.usermodel.DateUtil;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* 将 PO 导入预览行转换成 PODetailVo。
* 日期字段兼容 Excel 常见文本,不再只接受 yyyy-MM-dd。
*/
public final class PoImportRowMapper {
private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy-MM-dd");
/**
* 两位年份窗口起点:以当前年往前 80 年。
* 例如 2026 年导入时,22 解释为 2022,98 解释为 1998。
*/
private static final int TWO_DIGIT_YEAR_BASE = LocalDate.now().getYear() - 80;
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-M-d", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy/M/d", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy.M.d", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyyMMdd", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M/d/yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M-d-yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M.d.yyyy", Locale.ENGLISH),
reducedYearFormatter("M/d/"),
reducedYearFormatter("M-d-"),
reducedYearFormatter("M.d."),
DateTimeFormatter.ofPattern("d/M/yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("d-M-yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("d.M.yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("d-MMM-yyyy", Locale.ENGLISH),
reducedMonthNameFormatter("d-MMM-"),
DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy年M月d日", Locale.CHINA),
reducedChineseMonthFormatter()
);
private static final List<DateTimeFormatter> DATE_TIME_FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-M-d H:m:s", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy/M/d H:m:s", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy-M-d H:m", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy/M/d H:m", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M/d/yyyy H:m:s", Locale.ENGLISH),
DateTimeFormatter.ofPattern("M/d/yyyy H:m", Locale.ENGLISH),
reducedDateTimeFormatter("M/d/", " H:m:s"),
reducedDateTimeFormatter("M/d/", " H:m"),
DateTimeFormatter.ofPattern("M/d/yyyy h:m:s a", Locale.ENGLISH),
DateTimeFormatter.ofPattern("yyyy-M-d'T'H:m:s", Locale.ENGLISH)
);
private PoImportRowMapper() {
}
/**
* 构造带两位年的日期格式。
* 入参 prefix 为“月日分隔”前缀,例如 {@code M/d/},后面再接 22 这种两位年。
*/
private static DateTimeFormatter reducedYearFormatter(String prefix) {
return new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern(prefix)
.appendValueReduced(ChronoField.YEAR, 2, 2, TWO_DIGIT_YEAR_BASE)
.toFormatter(Locale.ENGLISH);
}
/**
* 构造 {@code 13-Oct-22} 这类英文月份 + 两位年格式。
*/
private static DateTimeFormatter reducedMonthNameFormatter(String prefix) {
return new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern(prefix)
.appendValueReduced(ChronoField.YEAR, 2, 2, TWO_DIGIT_YEAR_BASE)
.toFormatter(Locale.ENGLISH);
}
/**
* 构造 {@code 13-10月-22} 这类中文月份 + 两位年格式。
*/
private static DateTimeFormatter reducedChineseMonthFormatter() {
return new DateTimeFormatterBuilder()
.appendPattern("d-M月-")
.appendValueReduced(ChronoField.YEAR, 2, 2, TWO_DIGIT_YEAR_BASE)
.toFormatter(Locale.CHINA);
}
/**
* 构造带时分秒的两位年日期时间格式,兼容 Excel 读出的 {@code 10/13/22 0:00:00}。
*/
private static DateTimeFormatter reducedDateTimeFormatter(String datePrefix, String timePattern) {
return new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern(datePrefix)
.appendValueReduced(ChronoField.YEAR, 2, 2, TWO_DIGIT_YEAR_BASE)
.appendPattern(timePattern)
.toFormatter(Locale.ENGLISH);
}
public static PODetailVo toDetailVo(Map<String, Object> row, String site, String customer) {
PODetailVo vo = new PODetailVo();
vo.setSite(site);
@ -70,21 +165,32 @@ public final class PoImportRowMapper {
return null;
}
if (fieldCode.startsWith("ext_dt") || Date.class.equals(type)) {
try {
return DATE_FMT.parse(text.length() > 10 ? text.substring(0, 10) : text);
} catch (ParseException ex) {
throw new IllegalArgumentException("日期格式错误(" + fieldCode + "): " + text);
}
return parseImportDate(fieldCode, text);
}
if (Number.class.isAssignableFrom(type) || type == double.class || type == Double.class
|| type == int.class || type == Integer.class) {
if (BigDecimal.class.equals(type)) {
return new BigDecimal(text);
String numberText = normalizeImportNumberText(text);
if (!StringUtils.hasText(numberText)) {
return null;
}
if (type == Integer.class || type == int.class) {
return Integer.valueOf(text.contains(".") ? text.substring(0, text.indexOf('.')) : text);
try {
if (BigDecimal.class.equals(type)) {
return new BigDecimal(numberText);
}
if (type == Integer.class || type == int.class) {
String integerText = numberText.contains(".")
? numberText.substring(0, numberText.indexOf('.'))
: numberText;
return Integer.valueOf(integerText);
}
return Double.valueOf(numberText);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("数字格式错误(" + fieldCode + "): " + text);
}
return Double.valueOf(text);
}
if ("value".equals(fieldCode)) {
// P_Value 是字符串落库,但仍按数字处理:去掉 10,000.00 中的千分位逗号。
return normalizeImportNumberText(text);
}
if ("part_no".equals(fieldCode) && !StringUtils.hasText(text)) {
return null;
@ -101,4 +207,134 @@ public final class PoImportRowMapper {
}
return text;
}
/**
* 去掉导入数字里的千分位逗号(含中文逗号、不间断空格)。
* 入参:Excel 读出的原始数字文本,例如 10,000.00。
* 出参:可供 BigDecimal/Double/Integer 解析的文本,例如 10000.00。
*/
private static String normalizeImportNumberText(String text) {
if (text == null) {
return "";
}
return text.replace('\u00A0', ' ')
.replace(",", "")
.replace(",", "")
.trim();
}
/**
* 解析 PO 导入日期。
* 入参:fieldCode 用于错误提示;rawText 为 Excel DataFormatter 读出的单元格文本。
* 出参:仅保留日期部分的 java.sql.Date。
* 兼容:yyyy-MM-dd、yyyy/M/d、M/d/yyyy、10/13/22、13-Oct-22、2022年10月13日,以及带时分秒的同类文本。
* 斜杠/短日期优先按美式月/日解释,因此 10/13/22 为 2022-10-13,而不是日/月。
* 失败时抛 IllegalArgumentException,预览行黄标,保存接口同样拦截。
*/
private static Date parseImportDate(String fieldCode, String rawText) {
String text = normalizeDateText(rawText);
if (!StringUtils.hasText(text)) {
return null;
}
LocalDate localDate = tryParseLocalDate(text);
if (localDate == null && (text.contains(" ") || text.contains("T"))) {
String datePart = text.split("[ T]")[0];
localDate = tryParseLocalDate(datePart);
}
if (localDate != null) {
return java.sql.Date.valueOf(localDate);
}
Date serialDate = tryParseExcelSerial(text);
if (serialDate != null) {
return serialDate;
}
throw new IllegalArgumentException("日期格式错误(" + fieldCode + "): " + rawText);
}
/**
* 去掉不间断空格、首尾空白,便于匹配多种日期文本。
*/
private static String normalizeDateText(String rawText) {
if (rawText == null) {
return "";
}
return rawText.replace('\u00A0', ' ').trim();
}
/**
* 依次尝试日期、日期时间格式。任意一种成功即返回 LocalDate,全部失败返回 null。
* 两位年若被解析成公元 22 年,会按 80 年窗口折算到 1922-2022 这一段业务年份。
*/
private static LocalDate tryParseLocalDate(String text) {
for (DateTimeFormatter formatter : DATE_FORMATTERS) {
LocalDate parsed = acceptBusinessDate(tryParseAsLocalDate(text, formatter));
if (parsed != null) {
return parsed;
}
}
for (DateTimeFormatter formatter : DATE_TIME_FORMATTERS) {
LocalDate parsed = acceptBusinessDate(tryParseAsLocalDateTime(text, formatter));
if (parsed != null) {
return parsed;
}
}
return null;
}
private static LocalDate tryParseAsLocalDate(String text, DateTimeFormatter formatter) {
try {
return LocalDate.parse(text, formatter);
} catch (DateTimeParseException ignored) {
return null;
}
}
private static LocalDate tryParseAsLocalDateTime(String text, DateTimeFormatter formatter) {
try {
return LocalDateTime.parse(text, formatter).toLocalDate();
} catch (DateTimeParseException ignored) {
return null;
}
}
/**
* 只接受业务上合理的年份。
* 入参可能是完整年(2022)或被当成公元年的两位年(22)。
* 出参:1900-2100 之间的日期;无法折算则返回 null,让调用方继续试下一种格式。
*/
private static LocalDate acceptBusinessDate(LocalDate date) {
if (date == null) {
return null;
}
int year = date.getYear();
if (year >= 0 && year <= 99) {
int base = TWO_DIGIT_YEAR_BASE;
int fullYear = base + Math.floorMod(year - Math.floorMod(base, 100), 100);
date = date.withYear(fullYear);
year = date.getYear();
}
if (year < 1900 || year > 2100) {
return null;
}
return date;
}
/**
* 兼容 Excel 把日期单元格格式化成序列号的情况,例如 44847。
* 仅接受合理业务区间,避免把普通数字误当成日期。
*/
private static Date tryParseExcelSerial(String text) {
if (!text.matches("\\d{5}(?:\\.\\d+)?")) {
return null;
}
try {
double serial = Double.parseDouble(text);
if (serial < 20000d || serial > 80000d) {
return null;
}
return DateUtil.getJavaDate(serial);
} catch (RuntimeException ignored) {
return null;
}
}
}
Loading…
Cancel
Save