🔀 合并 fix/issue-2-10-import-batch-rollback
导入链路整批修复(issues #2-#10 + 违章/记录类/台账类/运输对账等): - 所有批量导入改为「任一行失败即整批回滚」 - 失败明细返回原表全量并逐列标红(不再只给错误行) - 读取阶段异常兜底,不再暴露 HTTP 500 技术文案 - 空表提示改为「未读取到数据行,请确认表头下方已填写内容」 - 修复常用货物型号必填漏校验、运输对账动态费用列静默转 0、 失败明细行号错位等 详见该分支 16 个提交。
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.common.excel;
|
||||
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 单元格读取失败异常 —— 用于「异常不是 fastexcel 抛出、但同样发生在读取阶段」的场景。
|
||||
* <p>
|
||||
* fastexcel 自身解析失败时抛 {@code ExcelDataConvertException},位置信息齐全,
|
||||
* {@link ImportFailureExcelUtil#exportReadFailure} 可以直接定位到行与列。但有些模块
|
||||
* 在读取阶段有自己的解析逻辑(例如运输对账的动态费用列:模板列不固定,由
|
||||
* {@code VehicleReconciliationFeeReader} 自行按表头解析),这些代码如果只是
|
||||
* {@code catch (NumberFormatException)} 后返回 {@code BigDecimal.ZERO},
|
||||
* 脏数据就会静默入库(费用 0.00 还会参与结算金额与自动匹配)。
|
||||
* <p>
|
||||
* 正确做法是改抛本异常,把位置信息补齐,让导出工具能把原因挂到正确的行、标红正确的列。
|
||||
* <p>
|
||||
* 本类位于 blade-common,供 blade-system 与 blade-transport 等模块共用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class ImportCellReadException extends ServiceException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 出错的单元格所在的 Excel 行号,<b>绝对 0 基</b>(表头占 0,第一个数据行为 1)。
|
||||
* 与 {@code ExcelDataConvertException.getRowIndex()} 保持同一口径,
|
||||
* 便于导出工具统一换算。
|
||||
*/
|
||||
private final transient Integer rowIndex;
|
||||
|
||||
/**
|
||||
* 出错的列名(导入模板表头原文),会写进原因文案里,导出工具靠它标红对应列。
|
||||
*/
|
||||
private final transient String columnName;
|
||||
|
||||
public ImportCellReadException(Integer rowIndex, String columnName, String cellValue) {
|
||||
super(buildMessage(rowIndex, columnName, cellValue));
|
||||
this.rowIndex = rowIndex;
|
||||
this.columnName = columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文案格式与 {@link ImportFailureExcelUtil#describeReadFailure} 保持一致:
|
||||
* 「第N行「列名」格式不正确:<原值>」。列名必须包在「」里,导出工具才能标红该列。
|
||||
*/
|
||||
private static String buildMessage(Integer rowIndex, String columnName, String cellValue) {
|
||||
String rowText = rowIndex == null ? "第?行" : "第" + (rowIndex + 1) + "行";
|
||||
String columnText = columnName == null || columnName.isBlank() ? "该列" : "「" + columnName + "」";
|
||||
StringBuilder message = new StringBuilder(rowText).append(columnText).append("格式不正确");
|
||||
if (cellValue != null && !cellValue.isBlank()) {
|
||||
message.append(":").append(cellValue);
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
public Integer getRowIndex() {
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,6 +80,371 @@ public class ImportFailureExcelUtil {
|
||||
addValidationError(validationErrors, value != null && value.length() > maxLength, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把「读取 Excel 阶段」的转换异常转成可读的失败原因。
|
||||
* <p>
|
||||
* 背景:单元格里填了无法转换的内容(如金额列填「一万二千元」)时,EasyExcel 会在
|
||||
* {@code ExcelUtil.read(...)} 阶段就抛 {@link cn.idev.excel.exception.ExcelDataConvertException},
|
||||
* 根本走不到行级校验;若不兜底,用户看到的是
|
||||
* 「Convert data ... to class java.math.BigDecimal error」这类技术文案。
|
||||
* <p>
|
||||
* 本方法据异常里的行列位置与目标类型,生成「第N行「列名」格式不正确:<原值>」这样
|
||||
* 用户能看懂、能定位的文案;列名靠表头反射拿 {@code @ExcelProperty},拿不到则退回列序号。
|
||||
*
|
||||
* @param exception 读取阶段抛出的异常
|
||||
* @param excelClass 导入模型类(用于按列序号反查表头名)
|
||||
* @return 可读的失败原因;非转换类异常返回 null(交由上层按原样处理)
|
||||
*/
|
||||
public static String describeReadFailure(Throwable exception, Class<?> excelClass) {
|
||||
cn.idev.excel.exception.ExcelDataConvertException convertException = findCause(exception,
|
||||
cn.idev.excel.exception.ExcelDataConvertException.class);
|
||||
if (convertException == null) {
|
||||
// 不是 fastexcel 抛的转换异常:模块自带的读取期解析逻辑(如运输对账的动态费用列)
|
||||
// 用 ImportCellReadException 补齐了位置信息,直接透传它已经拼好的原因文案。
|
||||
ImportCellReadException cellReadException = findCause(exception, ImportCellReadException.class);
|
||||
return cellReadException == null ? null : cellReadException.getMessage();
|
||||
}
|
||||
Integer rowIndex = convertException.getRowIndex();
|
||||
Integer columnIndex = convertException.getColumnIndex();
|
||||
String columnName = columnName(excelClass, columnIndex);
|
||||
// Excel 行号:getRowIndex() 是绝对 0 基行号(表头占 0),故 Excel 行号 = rowIndex + 1
|
||||
String rowText = rowIndex == null ? "第?行" : "第" + (rowIndex + 1) + "行";
|
||||
String columnText = columnName == null ? ("第" + (columnIndex == null ? "?" : columnIndex + 1) + "列") : "「" + columnName + "」";
|
||||
String value = cellValue(convertException);
|
||||
StringBuilder message = new StringBuilder(rowText).append(columnText).append("格式不正确");
|
||||
if (value != null && !value.isBlank()) {
|
||||
message.append(":").append(value);
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在异常链里查找指定类型的 cause
|
||||
*/
|
||||
private static <T extends Throwable> T findCause(Throwable throwable, Class<T> type) {
|
||||
for (Throwable current = throwable; current != null; current = current.getCause()) {
|
||||
if (type.isInstance(current)) {
|
||||
return type.cast(current);
|
||||
}
|
||||
if (current.getCause() == current) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按列序号反查表头名(@ExcelProperty 的 value,取不到则用字段名)
|
||||
*/
|
||||
private static String columnName(Class<?> excelClass, Integer columnIndex) {
|
||||
if (excelClass == null || columnIndex == null) {
|
||||
return null;
|
||||
}
|
||||
List<Field> fields = excelFields(excelClass);
|
||||
if (columnIndex < 0 || columnIndex >= fields.size()) {
|
||||
return null;
|
||||
}
|
||||
Field field = fields.get(columnIndex);
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
if (excelProperty == null) {
|
||||
return field.getName();
|
||||
}
|
||||
String[] value = excelProperty.value();
|
||||
String columnName = value.length == 0 ? field.getName() : value[0];
|
||||
return columnName.replace("*", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取触发转换失败的单元格原值
|
||||
*/
|
||||
private static String cellValue(cn.idev.excel.exception.ExcelDataConvertException exception) {
|
||||
try {
|
||||
cn.idev.excel.metadata.data.CellData<?> cellData = exception.getCellData();
|
||||
if (cellData == null) {
|
||||
return null;
|
||||
}
|
||||
Object data = cellData.getData();
|
||||
if (data == null) {
|
||||
data = cellData.getStringValue();
|
||||
}
|
||||
return data == null ? null : String.valueOf(data).trim();
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以「原始单元格文本」方式重读导入文件,得到的行用于构造读取阶段的失败明细。
|
||||
* <p>
|
||||
* 用途:当 {@code ExcelUtil.read(file, XxxExcel.class)} 因单元格无法转换而失败时,
|
||||
* 已解析的实体列表是不完整的(异常中断了整批读取)。此时用本方法重读一次:
|
||||
* 不做类型转换(直接读成 Map),因此绝不会再抛转换异常,可拿到**原表全部行**,
|
||||
* 供导出「原表全量 + 仅出错行带原因」的失败明细。
|
||||
*
|
||||
* @param file 上传的 Excel 文件
|
||||
* @param excelClass 导入模型类(按列序号反查表头名,用于把列序号映射成列名)
|
||||
* @return 按行组织的明细数据:每行是「表头名 → 单元格文本」的有序 Map;
|
||||
* 读不出内容时返回空列表(调用方按空明细处理)
|
||||
*/
|
||||
public static List<Map<String, String>> readRawRows(java.io.InputStream file, Class<?> excelClass) {
|
||||
List<Map<Integer, String>> rawRows = new ArrayList<>();
|
||||
try {
|
||||
cn.idev.excel.read.builder.ExcelReaderBuilder builder = cn.idev.excel.FastExcel
|
||||
.read(file, new cn.idev.excel.event.AnalysisEventListener<Map<Integer, String>>() {
|
||||
@Override
|
||||
public void invoke(Map<Integer, String> data, cn.idev.excel.context.AnalysisContext context) {
|
||||
rawRows.add(new java.util.LinkedHashMap<>(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(cn.idev.excel.context.AnalysisContext context) {
|
||||
// 无需处理
|
||||
}
|
||||
});
|
||||
builder.headRowNumber(1).sheet().doRead();
|
||||
} catch (Exception ignored) {
|
||||
// 重读本身失败时退回空明细,由调用方按「无法读取」提示
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<Field> fields = excelClass == null ? List.of() : excelFields(excelClass);
|
||||
List<Map<String, String>> rows = new ArrayList<>();
|
||||
for (Map<Integer, String> rawRow : rawRows) {
|
||||
Map<String, String> row = new java.util.LinkedHashMap<>();
|
||||
for (Map.Entry<Integer, String> entry : rawRow.entrySet()) {
|
||||
Integer columnIndex = entry.getKey();
|
||||
String columnName = columnIndex != null && columnIndex < fields.size()
|
||||
? columnName(excelClass, columnIndex)
|
||||
: null;
|
||||
if (columnName == null) {
|
||||
columnName = "第" + (columnIndex == null ? "?" : columnIndex + 1) + "列";
|
||||
}
|
||||
row.put(columnName, entry.getValue());
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出「读取阶段失败」明细:原表全量行 + 末尾「导入失败原因」列。
|
||||
* <p>
|
||||
* 与 {@link #export} 的区别:本方法直接按重读得到的「表头名 → 单元格文本」写出,
|
||||
* 不依赖导入模型的字段反射,因此适用于「单元格无法转换为实体字段」的场景
|
||||
* (此时实体列表不完整,无法走常规导出)。
|
||||
*
|
||||
* @param response 响应
|
||||
* @param fileName 文件名
|
||||
* @param sheetName 工作表名
|
||||
* @param rawRows {@link #readRawRows} 得到的原表全部行
|
||||
* @param failureMessageByRow 行号(0 基,对应 rawRows 下标)→ 该行失败原因;为 null 表示该行无错
|
||||
*/
|
||||
public static void exportRawFailure(HttpServletResponse response, String fileName, String sheetName,
|
||||
List<Map<String, String>> rawRows, Map<Integer, String> failureMessageByRow) {
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName + ".xlsx");
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Map<String, String> row : rawRows) {
|
||||
for (String key : row.keySet()) {
|
||||
if (!headers.contains(key)) {
|
||||
headers.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
headers.add(FAILURE_REASON);
|
||||
List<List<String>> head = new ArrayList<>();
|
||||
for (String header : headers) {
|
||||
head.add(List.of(header));
|
||||
}
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
Map<Integer, Set<Integer>> redColumnsByRow = new HashMap<>();
|
||||
for (int rowIndex = 0; rowIndex < rawRows.size(); rowIndex++) {
|
||||
Map<String, String> rawRow = rawRows.get(rowIndex);
|
||||
List<Object> row = new ArrayList<>();
|
||||
Set<Integer> redColumns = new LinkedHashSet<>();
|
||||
// 失败原因列恒标红
|
||||
redColumns.add(headers.size() - 1);
|
||||
String failureMessage = failureMessageByRow == null ? null : failureMessageByRow.get(rowIndex);
|
||||
for (int columnIndex = 0; columnIndex < headers.size() - 1; columnIndex++) {
|
||||
String header = headers.get(columnIndex);
|
||||
row.add(rawRow.get(header));
|
||||
// 失败原因里出现该列名时,标红该列单元格
|
||||
if (failureMessage != null && !failureMessage.isBlank() && failureMessage.contains("「" + header + "」")) {
|
||||
redColumns.add(columnIndex);
|
||||
}
|
||||
}
|
||||
row.add(failureMessage == null ? "" : failureMessage);
|
||||
rows.add(row);
|
||||
redColumnsByRow.put(rowIndex, redColumns);
|
||||
}
|
||||
try {
|
||||
FastExcel.write(response.getOutputStream())
|
||||
.registerWriteHandler(new RawFailureCellStyleHandler(rows, redColumnsByRow))
|
||||
.head(head)
|
||||
.sheet(sheetName)
|
||||
.doWrite(rows);
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("导出导入失败明细失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取阶段失败明细的单元格样式处理器:统一列宽 + 标红失败原因列与出错列
|
||||
*/
|
||||
private static class RawFailureCellStyleHandler implements CellWriteHandler {
|
||||
|
||||
private final int failureReasonColumnIndex;
|
||||
private final Map<Integer, Set<Integer>> redColumnsByRow;
|
||||
private final Map<Short, CellStyle> redStyleCache = new HashMap<>();
|
||||
private final Map<Short, CellStyle> noWrapStyleCache = new HashMap<>();
|
||||
private final Map<Integer, Integer> columnWidthCache = new HashMap<>();
|
||||
|
||||
private RawFailureCellStyleHandler(List<List<Object>> rows, Map<Integer, Set<Integer>> redColumnsByRow) {
|
||||
this.failureReasonColumnIndex = rows.isEmpty() ? 0 : rows.get(0).size() - 1;
|
||||
this.redColumnsByRow = redColumnsByRow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
|
||||
List<WriteCellData<?>> cellDataList, Cell cell, cn.idev.excel.metadata.Head head,
|
||||
Integer relativeRowIndex, Boolean isHead) {
|
||||
if (Boolean.TRUE.equals(isHead)) {
|
||||
adjustColumnWidth(cell);
|
||||
markNoWrap(cell);
|
||||
return;
|
||||
}
|
||||
adjustColumnWidth(cell);
|
||||
if (relativeRowIndex == null || relativeRowIndex < 0) {
|
||||
return;
|
||||
}
|
||||
Set<Integer> redColumns = redColumnsByRow.get(relativeRowIndex);
|
||||
if (redColumns != null && redColumns.contains(cell.getColumnIndex())) {
|
||||
markRed(cell);
|
||||
}
|
||||
}
|
||||
|
||||
private void markRed(Cell cell) {
|
||||
CellStyle currentStyle = cell.getCellStyle();
|
||||
CellStyle redStyle = redStyleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> {
|
||||
Workbook workbook = cell.getSheet().getWorkbook();
|
||||
CellStyle newStyle = workbook.createCellStyle();
|
||||
newStyle.cloneStyleFrom(currentStyle);
|
||||
Font font = workbook.createFont();
|
||||
font.setColor(IndexedColors.RED.getIndex());
|
||||
newStyle.setFont(font);
|
||||
newStyle.setWrapText(true);
|
||||
return newStyle;
|
||||
});
|
||||
cell.setCellStyle(redStyle);
|
||||
}
|
||||
|
||||
private void markNoWrap(Cell cell) {
|
||||
CellStyle currentStyle = cell.getCellStyle();
|
||||
CellStyle noWrapStyle = noWrapStyleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> {
|
||||
CellStyle newStyle = cell.getSheet().getWorkbook().createCellStyle();
|
||||
newStyle.cloneStyleFrom(currentStyle);
|
||||
newStyle.setWrapText(false);
|
||||
return newStyle;
|
||||
});
|
||||
cell.setCellStyle(noWrapStyle);
|
||||
}
|
||||
|
||||
private void adjustColumnWidth(Cell cell) {
|
||||
int columnIndex = cell.getColumnIndex();
|
||||
int columnWidth = Math.min(Math.max(displayWidth(cell.toString()) + 4, 12), 80) * 256;
|
||||
Integer currentWidth = columnWidthCache.get(columnIndex);
|
||||
if (currentWidth == null || columnWidth > currentWidth) {
|
||||
columnWidthCache.put(columnIndex, columnWidth);
|
||||
cell.getSheet().setColumnWidth(columnIndex, columnWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private int displayWidth(String value) {
|
||||
int width = 0;
|
||||
for (int index = 0; index < value.length(); index++) {
|
||||
width += value.charAt(index) > 255 ? 2 : 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取阶段异常的一站式兜底:重读原表 → 导出「全量行 + 出错行原因」失败明细。
|
||||
* <p>
|
||||
* 控制器用法:在导入的 try 块末尾追加
|
||||
* <pre>
|
||||
* } catch (Exception exception) {
|
||||
* if (ImportFailureExcelUtil.exportReadFailure(response, fileName, sheetName, file, XxxExcel.class, exception)) {
|
||||
* return null;
|
||||
* }
|
||||
* throw exception;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return true 表示已识别为「单元格转换失败」并已写出明细(调用方直接 return null);
|
||||
* false 表示不是转换类异常(调用方应继续抛出,保持原有处理)
|
||||
*/
|
||||
public static boolean exportReadFailure(HttpServletResponse response, String fileName, String sheetName,
|
||||
org.springframework.web.multipart.MultipartFile file,
|
||||
Class<?> excelClass, Throwable exception) {
|
||||
String failureMessage = describeReadFailure(exception, excelClass);
|
||||
if (failureMessage == null) {
|
||||
return false;
|
||||
}
|
||||
List<Map<String, String>> rawRows;
|
||||
try (java.io.InputStream inputStream = file.getInputStream()) {
|
||||
rawRows = readRawRows(inputStream, excelClass);
|
||||
} catch (IOException ioException) {
|
||||
rawRows = new ArrayList<>();
|
||||
}
|
||||
Map<Integer, String> failureMessageByRow = new HashMap<>();
|
||||
if (rawRows.isEmpty()) {
|
||||
// 重读也拿不到内容时,退化为「仅失败原因」一行,至少让用户看到可读原因
|
||||
Map<String, String> single = new java.util.LinkedHashMap<>();
|
||||
single.put(FAILURE_REASON, failureMessage);
|
||||
rawRows.add(single);
|
||||
failureMessageByRow.put(0, failureMessage);
|
||||
exportRawFailure(response, fileName, sheetName, rawRows, failureMessageByRow);
|
||||
return true;
|
||||
}
|
||||
// 异常里的数据行下标(0 基)用于把原因挂到对应行
|
||||
Integer rowIndex = readFailureRowIndex(exception);
|
||||
int targetRow = rowIndex == null ? 0 : Math.max(0, Math.min(rowIndex, rawRows.size() - 1));
|
||||
failureMessageByRow.put(targetRow, failureMessage);
|
||||
exportRawFailure(response, fileName, sheetName, rawRows, failureMessageByRow);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取读取阶段异常中「失败数据行」的下标(0 基,只对数据行计数),供挂失败原因使用。
|
||||
* <p>
|
||||
* 无论哪种异常,源信息都是<b>绝对 0 基行号</b>(表头占 0,第一个数据行为 1),
|
||||
* 而这里要的下标是相对数据行的,故统一减 1。
|
||||
* 不减的话原因会整体下移一行:错误恰好落在最后一行时会因调用处的 clamping
|
||||
* 侥幸正确,落在首行/中间行就会标到不该标的行上,误导用户去改正确的那一行。
|
||||
*/
|
||||
private static Integer readFailureRowIndex(Throwable exception) {
|
||||
Integer excelRowIndex = readFailureExcelRowIndex(exception);
|
||||
return excelRowIndex == null ? null : Math.max(0, excelRowIndex - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取读取阶段异常的绝对 0 基行号(表头 = 0)。
|
||||
* <p>
|
||||
* 两条来源统一到这里:fastexcel 自带的 {@code ExcelDataConvertException},
|
||||
* 以及模块自行解析时抛出的 {@link ImportCellReadException}。
|
||||
*/
|
||||
private static Integer readFailureExcelRowIndex(Throwable exception) {
|
||||
cn.idev.excel.exception.ExcelDataConvertException convertException = findCause(exception,
|
||||
cn.idev.excel.exception.ExcelDataConvertException.class);
|
||||
if (convertException != null) {
|
||||
return convertException.getRowIndex();
|
||||
}
|
||||
ImportCellReadException cellReadException = findCause(exception, ImportCellReadException.class);
|
||||
return cellReadException == null ? null : cellReadException.getRowIndex();
|
||||
}
|
||||
|
||||
private static final String FAILURE_REASON = "导入失败原因";
|
||||
private static final String ERROR_MESSAGE_FIELD = "errorMessage";
|
||||
private static final String FAILURE_REASON_FIELD = "failureReason";
|
||||
|
||||
+9
-3
@@ -23,7 +23,7 @@
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.system.excel;
|
||||
package org.springblade.common.excel;
|
||||
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
|
||||
@@ -33,8 +33,14 @@ import java.util.List;
|
||||
/**
|
||||
* 导入失败异常,携带失败明细用于导出原表并标注错误。
|
||||
* <p>
|
||||
* 批量导入采用「全失败即整批回滚」语义:任一行校验失败都会抛出本异常触发事务回滚,
|
||||
* 失败明细在抛异常前已收集完毕,因此回滚不影响明细的内容。
|
||||
* 批量导入采用「任一行失败即整批回滚」语义:循环内先收集完全部错误明细,
|
||||
* 再抛出本异常触发事务回滚。明细在抛异常前已收集完毕,因此回滚不影响明细内容。
|
||||
* <p>
|
||||
* 注意:仅标注 {@code @Transactional} 并不足够。若循环内把异常吞掉(catch 后
|
||||
* 只往 errorList 塞数据并正常 return),方法会正常返回,Spring 会照常提交事务。
|
||||
* 必须抛出异常才能让事务真正回滚。
|
||||
* <p>
|
||||
* 本类位于 blade-common,供 blade-system 与 blade-transport 等模块共用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
+14
-3
@@ -157,10 +157,21 @@ public class AirportMasterController extends BladeController {
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
List<AirportMasterExcel> failureList = airportMasterService.importAirportMaster(ExcelUtil.read(file, AirportMasterExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "空港机场主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, AirportMasterExcel.class);
|
||||
try {
|
||||
airportMasterService.importAirportMaster(ExcelUtil.read(file, AirportMasterExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "机场主数据导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), AirportMasterExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "机场主数据导入失败明细" + DateUtil.time(), "导入失败明细", file, AirportMasterExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+31
-5
@@ -46,9 +46,9 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.system.excel.CargoTypeExcel;
|
||||
import org.springblade.system.excel.CargoTypeExportExcel;
|
||||
import org.springblade.system.excel.CargoTypeImportFailureExcel;
|
||||
import org.springblade.system.pojo.entity.CargoType;
|
||||
import org.springblade.system.pojo.vo.CargoTypeVO;
|
||||
import org.springblade.system.service.ICargoTypeService;
|
||||
@@ -156,15 +156,41 @@ public class CargoTypeController extends BladeController {
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入货物类型", description = "传入excel")
|
||||
public R importCargoType(MultipartFile file, HttpServletResponse response) {
|
||||
List<CargoTypeExcel> data = ExcelUtil.read(file, CargoTypeExcel.class);
|
||||
List<CargoTypeImportFailureExcel> failureList = cargoTypeService.importCargoType(data);
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "货物类型导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CargoTypeExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
cargoTypeService.importCargoType(ExcelUtil.read(file, CargoTypeExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
exportFailure(response, exception.getFailureList());
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "货物类型导入失败明细" + DateUtil.time(), "导入失败明细", file, CargoTypeExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
|
||||
* <p>
|
||||
* 失败数据仅标红出错单元格与失败原因列,表头保持默认样式。
|
||||
*/
|
||||
private void exportFailure(HttpServletResponse response, List<?> failureList) {
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(response, "货物类型导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CargoTypeExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出货物类型
|
||||
*/
|
||||
|
||||
+21
-3
@@ -149,10 +149,28 @@ public class CurrencyController extends BladeController {
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入币种汇率", description = "传入excel")
|
||||
public R importCurrency(MultipartFile file, HttpServletResponse response) {
|
||||
List<CurrencyExcel> failureList = currencyService.importCurrency(ExcelUtil.read(file, CurrencyExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "币种汇率导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CurrencyExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
currencyService.importCurrency(ExcelUtil.read(file, CurrencyExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "币种汇率导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), CurrencyExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "币种汇率导入失败明细" + DateUtil.time(), "导入失败明细", file, CurrencyExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+22
-5
@@ -36,6 +36,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
@@ -47,7 +48,6 @@ import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.excel.FeeItemExcel;
|
||||
import org.springblade.system.excel.FeeItemExportExcel;
|
||||
import org.springblade.system.excel.FeeItemImportFailureExcel;
|
||||
import org.springblade.system.pojo.entity.FeeItem;
|
||||
import org.springblade.system.pojo.vo.FeeItemVO;
|
||||
import org.springblade.system.service.IFeeItemService;
|
||||
@@ -138,11 +138,28 @@ public class FeeItemController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入费用项", description = "传入excel")
|
||||
public R importFeeItem(MultipartFile file, HttpServletResponse response) {
|
||||
List<FeeItemExcel> data = ExcelUtil.read(file, FeeItemExcel.class);
|
||||
List<FeeItemImportFailureExcel> failureList = feeItemService.importFeeItem(data);
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "费用项导入失败明细" + DateUtil.time(), "导入失败明细", failureList, FeeItemExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
feeItemService.importFeeItem(ExcelUtil.read(file, FeeItemExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(response, "费用项导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), FeeItemExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "费用项导入失败明细" + DateUtil.time(), "导入失败明细", file, FeeItemExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.system.excel.ImportFailureException;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.system.excel.PortTerminalExcel;
|
||||
import org.springblade.system.excel.PortTerminalExportExcel;
|
||||
import org.springblade.system.excel.PortTerminalImporter;
|
||||
|
||||
+14
-3
@@ -160,10 +160,21 @@ public class RailwayStationController extends BladeController {
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
List<RailwayStationExcel> failureList = railwayStationService.importRailwayStation(ExcelUtil.read(file, RailwayStationExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "铁路车站主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, RailwayStationExcel.class);
|
||||
try {
|
||||
railwayStationService.importRailwayStation(ExcelUtil.read(file, RailwayStationExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "铁路站点导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), RailwayStationExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "铁路站点导入失败明细" + DateUtil.time(), "导入失败明细", file, RailwayStationExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+22
-3
@@ -46,6 +46,7 @@ import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.pojo.entity.Region;
|
||||
import org.springblade.system.excel.RegionExcel;
|
||||
import org.springblade.system.excel.RegionExportExcel;
|
||||
@@ -191,10 +192,28 @@ public class RegionController extends BladeController {
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "导入行政区划", description = "传入excel")
|
||||
public R importRegion(MultipartFile file, Integer isCovered, HttpServletResponse response) {
|
||||
List<RegionExcel> failureList = regionService.importRegion(ExcelUtil.read(file, RegionExcel.class), Objects.equals(isCovered, 1));
|
||||
if (!failureList.isEmpty()) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "行政区划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, RegionExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
regionService.importRegion(ExcelUtil.read(file, RegionExcel.class), Objects.equals(isCovered, 1));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "行政区划导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), RegionExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "行政区划导入失败明细" + DateUtil.time(), "导入失败明细", file, RegionExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class CurrencyExcel implements Serializable {
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -127,7 +127,7 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<AirportMasterExcel> importAirportMaster(List<AirportMasterExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<AirportMasterExcel> errorList = new ArrayList<>();
|
||||
List<AirportMaster> airportMasterList = new ArrayList<>();
|
||||
@@ -160,8 +160,9 @@ public class AirportMasterServiceImpl extends BaseServiceImpl<AirportMasterMappe
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (AirportMaster airportMaster : airportMasterList) {
|
||||
// 编码命中逻辑删除记录时复用原主键,恢复后更新,避免唯一索引冲突。
|
||||
|
||||
+11
-1
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
@@ -134,10 +135,14 @@ public class CargoTypeServiceImpl extends BaseServiceImpl<CargoTypeMapper, Cargo
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CargoTypeImportFailureExcel> importCargoType(List<CargoTypeExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
|
||||
// Spring 会照常提交事务,@Transactional 起不到回滚作用。
|
||||
List<CargoTypeImportFailureExcel> failureList = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
CargoTypeExcel excel = data.get(index);
|
||||
@@ -152,6 +157,11 @@ public class CargoTypeServiceImpl extends BaseServiceImpl<CargoTypeMapper, Cargo
|
||||
failureList.add(toImportFailureExcel(excel, message));
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new ImportFailureException(data);
|
||||
}
|
||||
return failureList;
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -115,10 +115,16 @@ public class CurrencyServiceImpl extends BaseServiceImpl<CurrencyMapper, Currenc
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CurrencyExcel> importCurrency(List<CurrencyExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意:循环内除 save 外还有 rebuildEnabledValidity 这个派生写——
|
||||
// 若吞掉异常正常 return,主表数据和派生的有效期区间都会落库;
|
||||
// 必须抛异常让两者一起回滚,否则会出现「主表回滚了、有效期区间
|
||||
// 却按失败批次算过」的脏数据。
|
||||
List<CurrencyExcel> errorList = new ArrayList<>();
|
||||
int successCount = 0;
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
@@ -140,6 +146,11 @@ public class CurrencyServiceImpl extends BaseServiceImpl<CurrencyMapper, Currenc
|
||||
errorList.add(excel);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚(含 rebuildEnabledValidity 的派生写)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -106,8 +106,11 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<FeeItemImportFailureExcel> importFeeItem(List<FeeItemExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意不能只往 failureList 里塞数据就 return——异常被吞掉后方法会正常返回,
|
||||
// Spring 会照常提交事务,@Transactional 起不到回滚作用。
|
||||
List<FeeItemImportFailureExcel> failureList = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
FeeItemExcel excel = data.get(index);
|
||||
@@ -118,9 +121,18 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
save(feeItem);
|
||||
} catch (Exception exception) {
|
||||
String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
|
||||
// 除收集到 failureList 外,还必须把原因写回原行对象:
|
||||
// 抛出异常时携带的是原表 data,导出工具从行对象的 errorMessage 字段取原因,
|
||||
// 只写在副本上会导致失败明细里错误行的原因为空。
|
||||
excel.setErrorMessage("第" + (index + 2) + "行:" + message);
|
||||
failureList.add(toImportFailureExcel(excel, "第" + (index + 2) + "行:" + message));
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
return failureList;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.excel.ImportFailureException;
|
||||
import org.springblade.system.excel.PortTerminalExcel;
|
||||
import org.springblade.system.excel.PortTerminalExportExcel;
|
||||
import org.springblade.system.mapper.PortTerminalMapper;
|
||||
@@ -140,7 +140,7 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<PortTerminalExcel> importPortTerminal(List<PortTerminalExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
List<PortTerminalExcel> failureList = new ArrayList<>();
|
||||
|
||||
+4
-3
@@ -129,7 +129,7 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<RailwayStationExcel> importRailwayStation(List<RailwayStationExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<RailwayStationExcel> errorList = new ArrayList<>();
|
||||
List<RailwayStation> railwayStationList = new ArrayList<>();
|
||||
@@ -165,8 +165,9 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (RailwayStation railwayStation : railwayStationList) {
|
||||
prepareImportTarget(railwayStation);
|
||||
|
||||
+26
-6
@@ -41,6 +41,7 @@ import org.springblade.system.mapper.RegionMapper;
|
||||
import org.springblade.system.service.IRegionService;
|
||||
import org.springblade.system.pojo.vo.RegionVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -60,6 +61,17 @@ public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> impleme
|
||||
|
||||
@Override
|
||||
public boolean submit(Region region) {
|
||||
return submit(region, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交区划,clearCache 控制是否在保存成功后立即清理惰性树缓存。
|
||||
* <p>
|
||||
* 批量导入路径传 false:导入内部会收集全部错误,只有整批成功才统一清缓存;
|
||||
* 若逐行清缓存,失败批次中已成功行会把缓存清掉/重算成中间状态,
|
||||
* 与随后的事务回滚形成「库回滚了、缓存却是脏的」。
|
||||
*/
|
||||
private boolean submit(Region region, boolean clearCache) {
|
||||
Date now = new Date();
|
||||
Long currentUserId = AuthUtil.getUserId();
|
||||
boolean isNew = StringUtil.isBlank(region.getOriginalCode());
|
||||
@@ -138,7 +150,7 @@ public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> impleme
|
||||
region.setVillageName(name);
|
||||
}
|
||||
boolean result = StringUtil.isNotBlank(region.getOriginalCode()) ? this.updateById(region) : this.save(region);
|
||||
if (result) {
|
||||
if (result && clearCache) {
|
||||
clearLazyTree();
|
||||
}
|
||||
return result;
|
||||
@@ -189,12 +201,15 @@ public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> impleme
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<RegionExcel> importRegion(List<RegionExcel> data, Boolean isCovered) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
|
||||
// Spring 会照常提交事务,@Transactional 起不到回滚作用。
|
||||
List<RegionExcel> errorList = new ArrayList<>();
|
||||
boolean cacheChanged = false;
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
RegionExcel excel = data.get(index);
|
||||
try {
|
||||
@@ -210,15 +225,20 @@ public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> impleme
|
||||
if (Boolean.TRUE.equals(isCovered) && this.getById(region.getCode()) != null) {
|
||||
region.setOriginalCode(region.getCode());
|
||||
}
|
||||
cacheChanged = this.submit(region) || cacheChanged;
|
||||
// 导入路径不逐行清缓存,缓存只在整批成功后统一清一次。
|
||||
this.submit(region, false);
|
||||
} catch (Exception exception) {
|
||||
excel.setErrorMessage(exception.getMessage());
|
||||
errorList.add(excel);
|
||||
}
|
||||
}
|
||||
if (cacheChanged) {
|
||||
clearLazyTree();
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚。缓存清理在异常抛出后不可达,
|
||||
// 只会发生在成功路径上——避免「库回滚了、惰性树缓存却按失败批次
|
||||
// 的中间状态被清掉/重算」的脏缓存。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
clearLazyTree();
|
||||
return errorList;
|
||||
}
|
||||
|
||||
|
||||
+21
-3
@@ -128,10 +128,28 @@ public class AccidentRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入事故记录", description = "传入excel")
|
||||
public R importAccidentRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<AccidentRecordExcel> failureList = accidentRecordService.importAccidentRecord(ExcelUtil.read(file, AccidentRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "事故记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, AccidentRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
accidentRecordService.importAccidentRecord(ExcelUtil.read(file, AccidentRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "事故记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), AccidentRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "事故记录导入失败明细" + DateUtil.time(), "导入失败明细", file, AccidentRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -125,10 +125,28 @@ public class AnnualInspectionRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入年检记录", description = "传入excel")
|
||||
public R importAnnualInspectionRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<AnnualInspectionRecordExcel> failureList = annualInspectionRecordService.importAnnualInspectionRecord(ExcelUtil.read(file, AnnualInspectionRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "年检记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, AnnualInspectionRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
annualInspectionRecordService.importAnnualInspectionRecord(ExcelUtil.read(file, AnnualInspectionRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "年检记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), AnnualInspectionRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "年检记录导入失败明细" + DateUtil.time(), "导入失败明细", file, AnnualInspectionRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+31
-4
@@ -38,9 +38,9 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.transport.excel.CommonCargoExcel;
|
||||
import org.springblade.transport.excel.CommonCargoExportExcel;
|
||||
import org.springblade.transport.excel.CommonCargoImportFailureExcel;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springblade.transport.pojo.entity.CommonCargo;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
@@ -110,14 +110,41 @@ public class CommonCargoController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入常用货物", description = "传入excel")
|
||||
public R importCommonCargo(MultipartFile file, HttpServletResponse response) {
|
||||
List<CommonCargoImportFailureExcel> failureList = commonCargoService.importCommonCargo(ExcelUtil.read(file, CommonCargoExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "常用货物导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CommonCargoImportFailureExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
commonCargoService.importCommonCargo(ExcelUtil.read(file, CommonCargoExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
exportFailure(response, exception.getFailureList());
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "常用货物导入失败明细" + DateUtil.time(), "导入失败明细", file, CommonCargoExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
|
||||
* <p>
|
||||
* 失败数据仅标红出错单元格与失败原因列,表头保持默认样式。
|
||||
*/
|
||||
private void exportFailure(HttpServletResponse response, List<?> failureList) {
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(response, "常用货物导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CommonCargoExcel.class);
|
||||
}
|
||||
|
||||
@GetMapping("/export-template")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导出模板")
|
||||
|
||||
+23
-6
@@ -38,6 +38,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.transport.excel.CommonRouteExportExcel;
|
||||
import org.springblade.transport.excel.CommonRouteImportExcel;
|
||||
import org.springblade.transport.pojo.entity.CommonRoute;
|
||||
@@ -109,18 +110,34 @@ public class CommonRouteController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入常用线路", description = "传入excel")
|
||||
public R importCommonRoute(MultipartFile file, HttpServletResponse response) {
|
||||
List<CommonRouteImportExcel> failureList = commonRouteService.importCommonRoute(
|
||||
ExcelUtil.read(file, CommonRouteImportExcel.class)
|
||||
);
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
commonRouteService.importCommonRoute(ExcelUtil.read(file, CommonRouteImportExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(
|
||||
response,
|
||||
"常用线路导入失败明细" + DateUtil.time(),
|
||||
"导入失败明细",
|
||||
failureList,
|
||||
exception.getFailureList(),
|
||||
CommonRouteImportExcel.class
|
||||
);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "常用线路导入失败明细" + DateUtil.time(), "导入失败明细", file, CommonRouteImportExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -138,10 +138,28 @@ public class CreditScoreQuantificationController extends BladeController {
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入评分量化表", description = "传入excel")
|
||||
public R importCreditScoreQuantification(MultipartFile file, HttpServletResponse response) {
|
||||
List<CreditScoreQuantificationExcel> failureList = creditScoreQuantificationService.importCreditScoreQuantification(ExcelUtil.read(file, CreditScoreQuantificationExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "评分量化表导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CreditScoreQuantificationExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
creditScoreQuantificationService.importCreditScoreQuantification(ExcelUtil.read(file, CreditScoreQuantificationExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一分组失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "评分量化表导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), CreditScoreQuantificationExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "评分量化表导入失败明细" + DateUtil.time(), "导入失败明细", file, CreditScoreQuantificationExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -90,10 +90,28 @@ public class EquipmentLedgerController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入设备台账")
|
||||
public R importEquipmentLedger(MultipartFile file, HttpServletResponse response) {
|
||||
List<EquipmentLedgerExcel> failureList = equipmentLedgerService.importEquipmentLedger(ExcelUtil.read(file, EquipmentLedgerExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "设备台账导入失败明细" + DateUtil.time(), "导入失败明细", failureList, EquipmentLedgerExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
equipmentLedgerService.importEquipmentLedger(ExcelUtil.read(file, EquipmentLedgerExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "设备台账导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), EquipmentLedgerExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "设备台账导入失败明细" + DateUtil.time(), "导入失败明细", file, EquipmentLedgerExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -94,10 +94,28 @@ public class EtcRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入ETC记录", description = "传入excel")
|
||||
public R importEtcRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<EtcRecordExcel> failureList = etcRecordService.importEtcRecord(ExcelUtil.read(file, EtcRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "ETC记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, EtcRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
etcRecordService.importEtcRecord(ExcelUtil.read(file, EtcRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "ETC记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), EtcRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "ETC记录导入失败明细" + DateUtil.time(), "导入失败明细", file, EtcRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -129,10 +129,28 @@ public class InsuranceRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入保险记录", description = "传入excel")
|
||||
public R importInsuranceRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<InsuranceRecordExcel> failureList = insuranceRecordService.importInsuranceRecord(ExcelUtil.read(file, InsuranceRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "保险记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, InsuranceRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
insuranceRecordService.importInsuranceRecord(ExcelUtil.read(file, InsuranceRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "保险记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), InsuranceRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "保险记录导入失败明细" + DateUtil.time(), "导入失败明细", file, InsuranceRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -128,10 +128,28 @@ public class MaintenancePlanController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入保养记录", description = "传入excel")
|
||||
public R importMaintenancePlan(MultipartFile file, HttpServletResponse response) {
|
||||
List<MaintenancePlanExcel> failureList = maintenancePlanService.importMaintenancePlan(ExcelUtil.read(file, MaintenancePlanExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "保养记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, MaintenancePlanExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
maintenancePlanService.importMaintenancePlan(ExcelUtil.read(file, MaintenancePlanExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "保养计划导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), MaintenancePlanExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "保养计划导入失败明细" + DateUtil.time(), "导入失败明细", file, MaintenancePlanExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -128,10 +128,28 @@ public class MaintenanceRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入维修记录", description = "传入excel")
|
||||
public R importMaintenanceRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<MaintenanceRecordExcel> failureList = maintenanceRecordService.importMaintenanceRecord(ExcelUtil.read(file, MaintenanceRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "维修记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, MaintenanceRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
maintenanceRecordService.importMaintenanceRecord(ExcelUtil.read(file, MaintenanceRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "维修记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), MaintenanceRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "维修记录导入失败明细" + DateUtil.time(), "导入失败明细", file, MaintenanceRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -116,10 +116,28 @@ public class MileageRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入里程记录", description = "传入excel")
|
||||
public R importMileageRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<MileageRecordExcel> failureList = mileageRecordService.importMileageRecord(ExcelUtil.read(file, MileageRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "里程记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, MileageRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
mileageRecordService.importMileageRecord(ExcelUtil.read(file, MileageRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "里程记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), MileageRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "里程记录导入失败明细" + DateUtil.time(), "导入失败明细", file, MileageRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -94,10 +94,28 @@ public class OilElectricRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入油电记录", description = "传入excel")
|
||||
public R importOilElectricRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<OilElectricRecordExcel> failureList = oilElectricRecordService.importOilElectricRecord(ExcelUtil.read(file, OilElectricRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "油电记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, OilElectricRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
oilElectricRecordService.importOilElectricRecord(ExcelUtil.read(file, OilElectricRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "油电记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), OilElectricRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "油电记录导入失败明细" + DateUtil.time(), "导入失败明细", file, OilElectricRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -94,10 +94,28 @@ public class OtherExpenseRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入其他费用记录", description = "传入excel")
|
||||
public R importOtherExpenseRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<OtherExpenseRecordExcel> failureList = otherExpenseRecordService.importOtherExpenseRecord(ExcelUtil.read(file, OtherExpenseRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "其他费用记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, OtherExpenseRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
otherExpenseRecordService.importOtherExpenseRecord(ExcelUtil.read(file, OtherExpenseRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "其他费用记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), OtherExpenseRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "其他费用记录导入失败明细" + DateUtil.time(), "导入失败明细", file, OtherExpenseRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -128,10 +128,28 @@ public class TireReplacementRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入换胎记录", description = "传入excel")
|
||||
public R importTireReplacementRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<TireReplacementRecordExcel> failureList = tireReplacementRecordService.importTireReplacementRecord(ExcelUtil.read(file, TireReplacementRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "换胎记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TireReplacementRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
tireReplacementRecordService.importTireReplacementRecord(ExcelUtil.read(file, TireReplacementRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "换胎记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), TireReplacementRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "换胎记录导入失败明细" + DateUtil.time(), "导入失败明细", file, TireReplacementRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -113,10 +113,28 @@ public class TransportChangeRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入变更记录", description = "传入excel")
|
||||
public R importTransportChangeRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<TransportChangeRecordExcel> failureList = transportChangeRecordService.importTransportChangeRecord(ExcelUtil.read(file, TransportChangeRecordExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "变更记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TransportChangeRecordExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
transportChangeRecordService.importTransportChangeRecord(ExcelUtil.read(file, TransportChangeRecordExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "运输变更记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), TransportChangeRecordExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "运输变更记录导入失败明细" + DateUtil.time(), "导入失败明细", file, TransportChangeRecordExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+22
-4
@@ -147,11 +147,29 @@ public class TransportPlanController extends BladeController {
|
||||
public R importTransportPlan(MultipartFile file, @RequestParam Long projectId, @RequestParam String projectName,
|
||||
@RequestParam Long contractId, @RequestParam String contractName, @RequestParam String customerName,
|
||||
HttpServletResponse response) {
|
||||
List<TransportPlanImportExcel> failureList = transportPlanService.importTransportPlan(
|
||||
ExcelUtil.read(file, TransportPlanImportExcel.class), projectId, projectName, contractId, contractName, customerName);
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TransportPlanImportExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
transportPlanService.importTransportPlan(
|
||||
ExcelUtil.read(file, TransportPlanImportExcel.class), projectId, projectName, contractId, contractName, customerName);
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), TransportPlanImportExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", file, TransportPlanImportExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
+49
-12
@@ -19,6 +19,7 @@ import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.transport.excel.CargoReconciliationExcel;
|
||||
import org.springblade.transport.excel.CargoReconciliationFeeReader;
|
||||
@@ -126,13 +127,31 @@ public class TransportReconciliationController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入整车总额外部账单")
|
||||
public R importVehicle(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
|
||||
List<VehicleReconciliationExcel> rows = ExcelUtil.read(file, VehicleReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = VehicleReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
List<VehicleReconciliationFailureExcel> failures = reconciliationService.importVehicles(id, rows);
|
||||
if (!failures.isEmpty()) {
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", failures, VehicleReconciliationFailureExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
List<VehicleReconciliationExcel> rows = ExcelUtil.read(file, VehicleReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = VehicleReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
reconciliationService.importVehicles(id, rows);
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), VehicleReconciliationExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", file, VehicleReconciliationExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
@@ -141,13 +160,31 @@ public class TransportReconciliationController extends BladeController {
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入货物明细外部账单")
|
||||
public R importCargo(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
|
||||
List<CargoReconciliationExcel> rows = ExcelUtil.read(file, CargoReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = CargoReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
List<CargoReconciliationFailureExcel> failures = reconciliationService.importCargoes(id, rows);
|
||||
if (!failures.isEmpty()) {
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", failures, CargoReconciliationFailureExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
List<CargoReconciliationExcel> rows = ExcelUtil.read(file, CargoReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = CargoReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
reconciliationService.importCargoes(id, rows);
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), CargoReconciliationExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", file, CargoReconciliationExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
+21
-3
@@ -127,10 +127,28 @@ public class ViolationRecordController extends BladeController {
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "导入违章记录", description = "传入excel")
|
||||
public R importViolationRecord(MultipartFile file, HttpServletResponse response) {
|
||||
List<ViolationRecordImportExcel> failureList = violationRecordService.importViolationRecord(ExcelUtil.read(file, ViolationRecordImportExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "违章记录导入失败明细" + DateUtil.time(), "导入失败明细", failureList, ViolationRecordImportExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
violationRecordService.importViolationRecord(ExcelUtil.read(file, ViolationRecordImportExcel.class));
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红),用户修正后重新导入。
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.export(response, "违章记录导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), ViolationRecordImportExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "违章记录导入失败明细" + DateUtil.time(), "导入失败明细", file, ViolationRecordImportExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("操作成功");
|
||||
}
|
||||
|
||||
+22
-3
@@ -34,6 +34,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
@@ -242,10 +243,28 @@ public class WaybillController extends BladeController {
|
||||
@ApiOperationSupport(order = 13)
|
||||
@Operation(summary = "导入运单管理", description = "传入excel")
|
||||
public R importWaybill(MultipartFile file, HttpServletResponse response) {
|
||||
List<WaybillExcel> failureList = waybillService.importWaybill(ExcelUtil.read(file, WaybillExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "运单管理导入失败明细" + DateUtil.time(), "导入失败明细", failureList, WaybillExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
waybillService.importWaybill(ExcelUtil.read(file, WaybillExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(response, "运单管理导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), WaybillExcel.class);
|
||||
return null;
|
||||
} catch (Exception readException) {
|
||||
// 读取阶段异常兜底:单元格内容无法转换(如金额列填「一万二千元」)时,
|
||||
// EasyExcel 在 ExcelUtil.read 阶段即抛异常,走不到行级校验。
|
||||
// 此处用原始文本重读原表,导出「全量行 + 出错行原因」,避免用户看到 500 技术文案。
|
||||
if (org.springblade.common.excel.ImportFailureExcelUtil.exportReadFailure(
|
||||
response, "运单管理导入失败明细" + DateUtil.time(), "导入失败明细", file, WaybillExcel.class, readException)) {
|
||||
return null;
|
||||
}
|
||||
throw readException;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
+8
-3
@@ -13,6 +13,7 @@ import cn.idev.excel.FastExcel;
|
||||
import cn.idev.excel.context.AnalysisContext;
|
||||
import cn.idev.excel.event.AnalysisEventListener;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import org.springblade.common.excel.ImportCellReadException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -60,10 +61,11 @@ public final class CargoReconciliationFeeReader {
|
||||
@Override
|
||||
public void invoke(Map<Integer, ReadCellData<?>> row, AnalysisContext context) {
|
||||
Map<String, BigDecimal> values = new LinkedHashMap<>();
|
||||
Integer excelRowIndex = context.readRowHolder() == null ? null : context.readRowHolder().getRowIndex();
|
||||
if (freightColumn >= 0 && settlementColumn > freightColumn) {
|
||||
for (int column = freightColumn + 1; column < settlementColumn; column++) {
|
||||
String name = headers.get(column);
|
||||
if (name != null && !name.isBlank()) values.put(name.trim(), decimal(row.get(column)));
|
||||
if (name != null && !name.isBlank()) values.put(name.trim(), decimal(row.get(column), name.trim(), excelRowIndex));
|
||||
}
|
||||
}
|
||||
feeItems.add(values);
|
||||
@@ -80,7 +82,7 @@ public final class CargoReconciliationFeeReader {
|
||||
.findFirst().orElse(-1);
|
||||
}
|
||||
|
||||
private BigDecimal decimal(ReadCellData<?> cellData) {
|
||||
private BigDecimal decimal(ReadCellData<?> cellData, String columnName, Integer excelRowIndex) {
|
||||
if (cellData == null) return BigDecimal.ZERO.setScale(2);
|
||||
Object value = cellData.getData();
|
||||
if (value == null) {
|
||||
@@ -95,7 +97,10 @@ public final class CargoReconciliationFeeReader {
|
||||
try {
|
||||
return new BigDecimal(String.valueOf(value).trim()).setScale(2);
|
||||
} catch (NumberFormatException exception) {
|
||||
return BigDecimal.ZERO.setScale(2);
|
||||
// 这里绝不能静默返回 0:费用列会进 feeItemsJson 参与结算金额与自动匹配,
|
||||
// 把「一百元」这类写法悄悄变成 0.00 等于往库里写脏数据。
|
||||
// 改为抛带位置信息的异常,由 Controller 的读取阶段兜底导出原表全量明细。
|
||||
throw new ImportCellReadException(excelRowIndex, columnName, String.valueOf(value).trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ public class CommonCargoExcel implements Serializable {
|
||||
@ExcelProperty("规格")
|
||||
private String specification;
|
||||
|
||||
@ExcelProperty("型号")
|
||||
@ExcelProperty("*型号")
|
||||
private String model;
|
||||
|
||||
@ExcelProperty("单价")
|
||||
|
||||
+8
-3
@@ -13,6 +13,7 @@ import cn.idev.excel.FastExcel;
|
||||
import cn.idev.excel.context.AnalysisContext;
|
||||
import cn.idev.excel.event.AnalysisEventListener;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import org.springblade.common.excel.ImportCellReadException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -60,10 +61,11 @@ public final class VehicleReconciliationFeeReader {
|
||||
@Override
|
||||
public void invoke(Map<Integer, ReadCellData<?>> row, AnalysisContext context) {
|
||||
Map<String, BigDecimal> values = new LinkedHashMap<>();
|
||||
Integer excelRowIndex = context.readRowHolder() == null ? null : context.readRowHolder().getRowIndex();
|
||||
if (freightColumn >= 0 && settlementColumn > freightColumn) {
|
||||
for (int column = freightColumn + 1; column < settlementColumn; column++) {
|
||||
String name = headers.get(column);
|
||||
if (name != null && !name.isBlank()) values.put(name.trim(), decimal(row.get(column)));
|
||||
if (name != null && !name.isBlank()) values.put(name.trim(), decimal(row.get(column), name.trim(), excelRowIndex));
|
||||
}
|
||||
}
|
||||
feeItems.add(values);
|
||||
@@ -80,7 +82,7 @@ public final class VehicleReconciliationFeeReader {
|
||||
.findFirst().orElse(-1);
|
||||
}
|
||||
|
||||
private BigDecimal decimal(ReadCellData<?> cellData) {
|
||||
private BigDecimal decimal(ReadCellData<?> cellData, String columnName, Integer excelRowIndex) {
|
||||
if (cellData == null) return BigDecimal.ZERO.setScale(2);
|
||||
Object value = cellData.getData();
|
||||
if (value == null) {
|
||||
@@ -95,7 +97,10 @@ public final class VehicleReconciliationFeeReader {
|
||||
try {
|
||||
return new BigDecimal(String.valueOf(value).trim()).setScale(2);
|
||||
} catch (NumberFormatException exception) {
|
||||
return BigDecimal.ZERO.setScale(2);
|
||||
// 这里绝不能静默返回 0:费用列会进 feeItemsJson 参与结算金额与自动匹配,
|
||||
// 把「一百元」这类写法悄悄变成 0.00 等于往库里写脏数据。
|
||||
// 改为抛带位置信息的异常,由 Controller 的读取阶段兜底导出原表全量明细。
|
||||
throw new ImportCellReadException(excelRowIndex, columnName, String.valueOf(value).trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -86,7 +86,7 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl<AccidentRecordMap
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<AccidentRecordExcel> importAccidentRecord(List<AccidentRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<AccidentRecordExcel> errorList = new ArrayList<>();
|
||||
List<AccidentRecord> accidentRecordList = new ArrayList<>();
|
||||
@@ -111,8 +111,9 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl<AccidentRecordMap
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (AccidentRecord accidentRecord : accidentRecordList) {
|
||||
if (!save(accidentRecord)) {
|
||||
|
||||
+4
-3
@@ -105,7 +105,7 @@ public class AnnualInspectionRecordServiceImpl extends BaseServiceImpl<AnnualIns
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<AnnualInspectionRecordExcel> importAnnualInspectionRecord(List<AnnualInspectionRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<AnnualInspectionRecordExcel> errorList = new ArrayList<>();
|
||||
List<AnnualInspectionRecord> annualInspectionRecordList = new ArrayList<>();
|
||||
@@ -131,8 +131,9 @@ public class AnnualInspectionRecordServiceImpl extends BaseServiceImpl<AnnualIns
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (AnnualInspectionRecord annualInspectionRecord : annualInspectionRecordList) {
|
||||
if (!save(annualInspectionRecord)) {
|
||||
|
||||
+22
-11
@@ -25,6 +25,7 @@ package org.springblade.transport.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
@@ -70,10 +71,6 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(CommonCargo commonCargo) {
|
||||
return submit(commonCargo, true);
|
||||
}
|
||||
|
||||
private boolean submit(CommonCargo commonCargo, boolean requireModel) {
|
||||
boolean created = Func.isEmpty(commonCargo.getId());
|
||||
if (!created) {
|
||||
CommonCargo oldRecord = loadEditable(commonCargo.getId(), true);
|
||||
@@ -81,7 +78,7 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
commonCargo.setDeptName(oldRecord.getDeptName());
|
||||
}
|
||||
prepare(commonCargo);
|
||||
validate(commonCargo, requireModel);
|
||||
validate(commonCargo);
|
||||
return saveOrUpdate(commonCargo);
|
||||
}
|
||||
|
||||
@@ -141,8 +138,12 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CommonCargoImportFailureExcel> importCommonCargo(List<CommonCargoExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意:本方法虽已标注 @Transactional,但循环内把异常吞掉后正常 return,
|
||||
// Spring 会认为一切成功并提交事务——注解救不了被吞掉的异常。
|
||||
// 必须把错误明细收集完后抛异常,回滚才会真正发生。
|
||||
List<CommonCargoImportFailureExcel> failureList = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
CommonCargoExcel excel = data.get(index);
|
||||
@@ -168,14 +169,23 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
commonCargo.setDescriptionOne(TransportBusinessSupport.trimToNull(excel.getDescriptionOne()));
|
||||
commonCargo.setRemark(TransportBusinessSupport.trimToNull(excel.getRemark()));
|
||||
commonCargo.setDataSource("批量导入");
|
||||
submit(commonCargo, false);
|
||||
submit(commonCargo);
|
||||
} catch (Exception exception) {
|
||||
// 除收集到 failureList 外,还必须把原因写回原行对象:
|
||||
// 抛出异常时携带的是原表 data,导出工具从行对象的 errorMessage 字段取原因,
|
||||
// 只写在副本上会导致失败明细里错误行的原因为空。
|
||||
excel.setErrorMessage(exception.getMessage());
|
||||
CommonCargoImportFailureExcel failureExcel = new CommonCargoImportFailureExcel();
|
||||
BeanUtil.copyProperties(excel, failureExcel);
|
||||
failureExcel.setErrorMessage(exception.getMessage());
|
||||
failureList.add(failureExcel);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new ImportFailureException(data);
|
||||
}
|
||||
return failureList;
|
||||
}
|
||||
|
||||
@@ -301,14 +311,15 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
return cargoValue != null && cargoValue.compareTo(BigDecimal.ONE.negate()) == 0 ? null : cargoValue;
|
||||
}
|
||||
|
||||
private void validate(CommonCargo commonCargo, boolean requireModel) {
|
||||
private void validate(CommonCargo commonCargo) {
|
||||
TransportBusinessSupport.validateRequired(commonCargo.getFirstCargoTypeName(), "请选择一级货物类型");
|
||||
TransportBusinessSupport.validateRequired(commonCargo.getSecondCargoTypeCode(), "请选择二级货物类型");
|
||||
TransportBusinessSupport.validateRequired(commonCargo.getCargoName(), "请输入货物名称");
|
||||
TransportBusinessSupport.validateRequired(commonCargo.getCargoCode(), "请输入货物编号");
|
||||
if (requireModel) {
|
||||
TransportBusinessSupport.validateRequired(commonCargo.getModel(), "请输入型号");
|
||||
}
|
||||
// 型号必填:新增/编辑入口(/submit)一直要求,导入侧此前用 requireModel=false
|
||||
// 放过了它,导致「页面上存不进去、导入却能写进库」,与根文档
|
||||
// docs/import-spec.md 的「导入校验必须与新增/编辑一致」冲突,故统一为必填。
|
||||
TransportBusinessSupport.validateRequired(commonCargo.getModel(), "请输入型号");
|
||||
TransportBusinessSupport.validateLength(commonCargo.getFirstCargoTypeName(), 100, "一级货物类型不能超过100字");
|
||||
TransportBusinessSupport.validateLength(commonCargo.getFirstCargoTypeCode(), 100, "一级货物类型编码不能超过100字");
|
||||
TransportBusinessSupport.validateLength(commonCargo.getSecondCargoTypeName(), 100, "二级货物类型不能超过100字");
|
||||
|
||||
+11
-1
@@ -25,6 +25,7 @@ package org.springblade.transport.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
@@ -128,8 +129,12 @@ public class CommonRouteServiceImpl extends BaseServiceImpl<CommonRouteMapper, C
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CommonRouteImportExcel> importCommonRoute(List<CommonRouteImportExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意:本方法虽已标注 @Transactional,但循环内把异常吞掉后正常 return,
|
||||
// Spring 会认为一切成功并提交事务——注解救不了被吞掉的异常。
|
||||
// 必须把错误明细收集完后抛异常,回滚才会真正发生。
|
||||
List<CommonRouteImportExcel> failureList = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
CommonRouteImportExcel excel = data.get(index);
|
||||
@@ -142,6 +147,11 @@ public class CommonRouteServiceImpl extends BaseServiceImpl<CommonRouteMapper, C
|
||||
failureList.add(excel);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new ImportFailureException(data);
|
||||
}
|
||||
return failureList;
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -196,7 +196,7 @@ public class CreditScoreQuantificationServiceImpl extends BaseServiceImpl<Credit
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CreditScoreQuantificationExcel> importCreditScoreQuantification(List<CreditScoreQuantificationExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
Map<String, List<CreditScoreQuantificationExcel>> dataMap = data.stream()
|
||||
.filter(row -> Func.isNotEmpty(trimToNull(row.getTableName())))
|
||||
@@ -214,6 +214,13 @@ public class CreditScoreQuantificationServiceImpl extends BaseServiceImpl<Credit
|
||||
errorList.addAll(rows);
|
||||
}
|
||||
});
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚:任一分组失败时,
|
||||
// 失败分组**之前已 submit 的正确分组**也必须一起退掉。
|
||||
// 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
|
||||
// Spring 会照常提交事务,@Transactional 起不到回滚作用。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -79,7 +79,7 @@ public class EquipmentLedgerServiceImpl extends BaseServiceImpl<EquipmentLedgerM
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<EquipmentLedgerExcel> importEquipmentLedger(List<EquipmentLedgerExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<EquipmentLedgerExcel> errorList = new ArrayList<>();
|
||||
List<EquipmentLedger> equipmentLedgerList = new ArrayList<>();
|
||||
@@ -114,8 +114,9 @@ public class EquipmentLedgerServiceImpl extends BaseServiceImpl<EquipmentLedgerM
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (EquipmentLedger equipmentLedger : equipmentLedgerList) {
|
||||
if (!save(equipmentLedger)) {
|
||||
|
||||
+4
-3
@@ -61,7 +61,7 @@ public class EtcRecordServiceImpl extends BaseServiceImpl<EtcRecordMapper, EtcRe
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<EtcRecordExcel> importEtcRecord(List<EtcRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<EtcRecordExcel> errorList = new ArrayList<>();
|
||||
List<EtcRecord> etcRecordList = new ArrayList<>();
|
||||
@@ -85,8 +85,9 @@ public class EtcRecordServiceImpl extends BaseServiceImpl<EtcRecordMapper, EtcRe
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (EtcRecord etcRecord : etcRecordList) {
|
||||
if (!save(etcRecord)) {
|
||||
|
||||
+4
-3
@@ -110,7 +110,7 @@ public class InsuranceRecordServiceImpl extends BaseServiceImpl<InsuranceRecordM
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<InsuranceRecordExcel> importInsuranceRecord(List<InsuranceRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<InsuranceRecordExcel> errorList = new ArrayList<>();
|
||||
List<InsuranceRecord> insuranceRecordList = new ArrayList<>();
|
||||
@@ -142,8 +142,9 @@ public class InsuranceRecordServiceImpl extends BaseServiceImpl<InsuranceRecordM
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (InsuranceRecord insuranceRecord : insuranceRecordList) {
|
||||
if (!save(insuranceRecord)) {
|
||||
|
||||
+4
-3
@@ -80,7 +80,7 @@ public class MaintenancePlanServiceImpl extends BaseServiceImpl<MaintenancePlanM
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<MaintenancePlanExcel> importMaintenancePlan(List<MaintenancePlanExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<MaintenancePlanExcel> errorList = new ArrayList<>();
|
||||
List<MaintenancePlan> maintenancePlanList = new ArrayList<>();
|
||||
@@ -103,8 +103,9 @@ public class MaintenancePlanServiceImpl extends BaseServiceImpl<MaintenancePlanM
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (MaintenancePlan maintenancePlan : maintenancePlanList) {
|
||||
if (!save(maintenancePlan)) {
|
||||
|
||||
+4
-3
@@ -82,7 +82,7 @@ public class MaintenanceRecordServiceImpl extends BaseServiceImpl<MaintenanceRec
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<MaintenanceRecordExcel> importMaintenanceRecord(List<MaintenanceRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<MaintenanceRecordExcel> errorList = new ArrayList<>();
|
||||
List<MaintenanceRecord> maintenanceRecordList = new ArrayList<>();
|
||||
@@ -107,8 +107,9 @@ public class MaintenanceRecordServiceImpl extends BaseServiceImpl<MaintenanceRec
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (MaintenanceRecord maintenanceRecord : maintenanceRecordList) {
|
||||
if (!save(maintenanceRecord)) {
|
||||
|
||||
+4
-3
@@ -81,7 +81,7 @@ public class MileageRecordServiceImpl extends BaseServiceImpl<MileageRecordMappe
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<MileageRecordExcel> importMileageRecord(List<MileageRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<MileageRecordExcel> errorList = new ArrayList<>();
|
||||
List<MileageRecord> mileageRecordList = new ArrayList<>();
|
||||
@@ -105,8 +105,9 @@ public class MileageRecordServiceImpl extends BaseServiceImpl<MileageRecordMappe
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (MileageRecord mileageRecord : mileageRecordList) {
|
||||
if (!save(mileageRecord)) {
|
||||
|
||||
+4
-3
@@ -70,7 +70,7 @@ public class OilElectricRecordServiceImpl extends BaseServiceImpl<OilElectricRec
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<OilElectricRecordExcel> importOilElectricRecord(List<OilElectricRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<OilElectricRecordExcel> errorList = new ArrayList<>();
|
||||
List<OilElectricRecord> oilElectricRecordList = new ArrayList<>();
|
||||
@@ -94,8 +94,9 @@ public class OilElectricRecordServiceImpl extends BaseServiceImpl<OilElectricRec
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (OilElectricRecord oilElectricRecord : oilElectricRecordList) {
|
||||
if (!save(oilElectricRecord)) {
|
||||
|
||||
+4
-3
@@ -67,7 +67,7 @@ public class OtherExpenseRecordServiceImpl extends BaseServiceImpl<OtherExpenseR
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<OtherExpenseRecordExcel> importOtherExpenseRecord(List<OtherExpenseRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<OtherExpenseRecordExcel> errorList = new ArrayList<>();
|
||||
List<OtherExpenseRecord> otherExpenseRecordList = new ArrayList<>();
|
||||
@@ -92,8 +92,9 @@ public class OtherExpenseRecordServiceImpl extends BaseServiceImpl<OtherExpenseR
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (OtherExpenseRecord otherExpenseRecord : otherExpenseRecordList) {
|
||||
if (!save(otherExpenseRecord)) {
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl<ShippingTemplat
|
||||
@Override
|
||||
public List<ShippingTemplateGoodsExcel> importGoods(List<ShippingTemplateGoodsExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<ShippingTemplateGoodsExcel> failures = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
|
||||
+4
-3
@@ -82,7 +82,7 @@ public class TireReplacementRecordServiceImpl extends BaseServiceImpl<TireReplac
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<TireReplacementRecordExcel> importTireReplacementRecord(List<TireReplacementRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<TireReplacementRecordExcel> errorList = new ArrayList<>();
|
||||
List<TireReplacementRecord> tireReplacementRecordList = new ArrayList<>();
|
||||
@@ -107,8 +107,9 @@ public class TireReplacementRecordServiceImpl extends BaseServiceImpl<TireReplac
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (TireReplacementRecord tireReplacementRecord : tireReplacementRecordList) {
|
||||
if (!save(tireReplacementRecord)) {
|
||||
|
||||
+4
-3
@@ -81,7 +81,7 @@ public class TransportChangeRecordServiceImpl extends BaseServiceImpl<TransportC
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<TransportChangeRecordExcel> importTransportChangeRecord(List<TransportChangeRecordExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<TransportChangeRecordExcel> errorList = new ArrayList<>();
|
||||
List<TransportChangeRecord> transportChangeRecordList = new ArrayList<>();
|
||||
@@ -104,8 +104,9 @@ public class TransportChangeRecordServiceImpl extends BaseServiceImpl<TransportC
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (TransportChangeRecord transportChangeRecord : transportChangeRecordList) {
|
||||
if (!save(transportChangeRecord)) {
|
||||
|
||||
+10
-7
@@ -198,7 +198,7 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
@Override
|
||||
public List<TransportPlanImportExcel> validateTransportPlan(List<TransportPlanImportExcel> data, Long projectId, String projectName, Long contractId, String contractName, String customerName) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
if (Func.isEmpty(projectId)) {
|
||||
throw new ServiceException("项目不能为空");
|
||||
@@ -250,7 +250,7 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<TransportPlanImportExcel> importTransportPlan(List<TransportPlanImportExcel> data, Long projectId, String projectName, Long contractId, String contractName, String customerName) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
if (Func.isEmpty(projectId)) {
|
||||
throw new ServiceException("项目不能为空");
|
||||
@@ -294,8 +294,9 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
|
||||
// 如果有校验错误,回滚事务
|
||||
if (Func.isNotEmpty(errorMap)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return new ArrayList<>(errorMap.values());
|
||||
// 任一行失败即整批回滚:抛异常触发事务回滚(替代旧的 setRollbackOnly),
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
|
||||
// 第二阶段:批量导入
|
||||
@@ -453,12 +454,14 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 校验数量和里程为正数
|
||||
// 5. 校验数量和里程非负。
|
||||
// 文案与规则必须一致:条件只拦 < 0(即 0 是合法值),就不能写「必须为正数」。
|
||||
// 新增/编辑走 TransportBusinessSupport.validateNonNegative,文案是「不能小于0」。
|
||||
if (excel.getQuantity() != null && excel.getQuantity().compareTo(BigDecimal.ZERO) < 0) {
|
||||
errors.add("数量必须为正数");
|
||||
errors.add("数量不能小于0");
|
||||
}
|
||||
if (excel.getMileage() != null && excel.getMileage().compareTo(BigDecimal.ZERO) < 0) {
|
||||
errors.add("里程必须为正数");
|
||||
errors.add("里程不能小于0");
|
||||
}
|
||||
|
||||
// 6. 校验计量单位(如果有值)
|
||||
|
||||
+31
-9
@@ -290,7 +290,7 @@ public class TransportReconciliationServiceImpl
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<VehicleReconciliationFailureExcel> importVehicles(Long id, List<VehicleReconciliationExcel> rows) {
|
||||
if (Func.isEmpty(rows)) throw new ServiceException("导入数据不能为空");
|
||||
if (Func.isEmpty(rows)) throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
TransportReconciliation bill = editable(id);
|
||||
if (!VEHICLE.equals(bill.getReconciliationMode())) throw new ServiceException("当前对账模式不是整车总额对账");
|
||||
resetExternal(id);
|
||||
@@ -300,22 +300,33 @@ public class TransportReconciliationServiceImpl
|
||||
VehicleReconciliationExcel row = rows.get(index);
|
||||
List<String> validationErrors = validateImportVehicle(row);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
// 原因同时写回原行:异常携带的是原表 rows,
|
||||
// 导出工具从行对象的 errorMessage 字段取原因,只写副本会丢原因。
|
||||
row.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
VehicleReconciliationFailureExcel failure = new VehicleReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
failure.setErrorMessage(row.getErrorMessage());
|
||||
failures.add(failure);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
validExternals.add(buildVehicleExternal(id, index, row));
|
||||
} catch (Exception exception) {
|
||||
String message = org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败"));
|
||||
row.setErrorMessage(message);
|
||||
VehicleReconciliationFailureExcel failure = new VehicleReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败")));
|
||||
failure.setErrorMessage(message);
|
||||
failures.add(failure);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failures)) {
|
||||
// 任一行失败即整批回滚:抛异常触发事务回滚,使前置的 resetExternal
|
||||
// 清空与本批正确行的插入一并退掉;否则会出现「旧外部账单被清空、
|
||||
// 正确行却已入库」的数据损坏。
|
||||
throw new org.springblade.common.excel.ImportFailureException(rows);
|
||||
}
|
||||
for (TransportReconciliationExternal external : validExternals) {
|
||||
if (externalMapper.insert(external) <= 0) throw new ServiceException("外部账单保存失败");
|
||||
}
|
||||
@@ -408,7 +419,7 @@ public class TransportReconciliationServiceImpl
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CargoReconciliationFailureExcel> importCargoes(Long id, List<CargoReconciliationExcel> rows) {
|
||||
if (Func.isEmpty(rows)) throw new ServiceException("导入数据不能为空");
|
||||
if (Func.isEmpty(rows)) throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
TransportReconciliation bill = editable(id);
|
||||
if (!CARGO.equals(bill.getReconciliationMode())) throw new ServiceException("当前对账模式不是货物明细对账");
|
||||
resetExternal(id);
|
||||
@@ -418,22 +429,33 @@ public class TransportReconciliationServiceImpl
|
||||
CargoReconciliationExcel row = rows.get(index);
|
||||
List<String> validationErrors = validateImportCargo(row);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
// 原因同时写回原行:异常携带的是原表 rows,
|
||||
// 导出工具从行对象的 errorMessage 字段取原因,只写副本会丢原因。
|
||||
row.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
failure.setErrorMessage(row.getErrorMessage());
|
||||
failures.add(failure);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
validExternals.add(buildCargoExternal(id, index, row));
|
||||
} catch (Exception exception) {
|
||||
String message = org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败"));
|
||||
row.setErrorMessage(message);
|
||||
CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败")));
|
||||
failure.setErrorMessage(message);
|
||||
failures.add(failure);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failures)) {
|
||||
// 任一行失败即整批回滚:抛异常触发事务回滚,使前置的 resetExternal
|
||||
// 清空与本批正确行的插入一并退掉;否则会出现「旧外部账单被清空、
|
||||
// 正确行却已入库」的数据损坏。
|
||||
throw new org.springblade.common.excel.ImportFailureException(rows);
|
||||
}
|
||||
for (TransportReconciliationExternal external : validExternals) {
|
||||
if (externalMapper.insert(external) <= 0) throw new ServiceException("外部账单保存失败");
|
||||
}
|
||||
@@ -465,7 +487,7 @@ public class TransportReconciliationServiceImpl
|
||||
private List<String> validateImportCargo(CargoReconciliationExcel row) {
|
||||
List<String> validationErrors = new ArrayList<>();
|
||||
if (row == null) {
|
||||
validationErrors.add("导入数据不能为空");
|
||||
validationErrors.add("未读取到数据行,请确认表头下方已填写内容");
|
||||
return validationErrors;
|
||||
}
|
||||
org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors,
|
||||
|
||||
+6
-3
@@ -91,7 +91,7 @@ public class ViolationRecordServiceImpl extends BaseServiceImpl<ViolationRecordM
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<ViolationRecordImportExcel> importViolationRecord(List<ViolationRecordImportExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<ViolationRecordImportExcel> errorList = new ArrayList<>();
|
||||
List<ViolationRecord> violationRecordList = new ArrayList<>();
|
||||
@@ -116,8 +116,11 @@ public class ViolationRecordServiceImpl extends BaseServiceImpl<ViolationRecordM
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return errorList;
|
||||
// 抛出携带失败明细的异常,触发事务回滚(替代旧的 setRollbackOnly)。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
// 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
|
||||
// Spring 会照常提交事务,@Transactional 起不到回滚作用。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
for (ViolationRecord violationRecord : violationRecordList) {
|
||||
if (!save(violationRecord)) {
|
||||
|
||||
+8
-1
@@ -901,7 +901,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<WaybillExcel> importWaybill(List<WaybillExcel> data) {
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
|
||||
}
|
||||
List<WaybillExcel> errorList = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
@@ -922,6 +922,13 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
errorList.add(excel);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(errorList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚:submit 内部一次写运单主表+
|
||||
// 货物+配载路线等多张关联表,回滚边界必须覆盖全部关联表。
|
||||
// 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
|
||||
// Spring 会照常提交事务,@Transactional 起不到回滚作用。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user