diff --git a/blade-common/src/main/java/org/springblade/common/excel/ImportCellReadException.java b/blade-common/src/main/java/org/springblade/common/excel/ImportCellReadException.java
new file mode 100644
index 00000000..7b038ad0
--- /dev/null
+++ b/blade-common/src/main/java/org/springblade/common/excel/ImportCellReadException.java
@@ -0,0 +1,93 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.common.excel;
+
+import org.springblade.core.log.exception.ServiceException;
+
+import java.io.Serial;
+
+/**
+ * 单元格读取失败异常 —— 用于「异常不是 fastexcel 抛出、但同样发生在读取阶段」的场景。
+ *
+ * fastexcel 自身解析失败时抛 {@code ExcelDataConvertException},位置信息齐全,
+ * {@link ImportFailureExcelUtil#exportReadFailure} 可以直接定位到行与列。但有些模块
+ * 在读取阶段有自己的解析逻辑(例如运输对账的动态费用列:模板列不固定,由
+ * {@code VehicleReconciliationFeeReader} 自行按表头解析),这些代码如果只是
+ * {@code catch (NumberFormatException)} 后返回 {@code BigDecimal.ZERO},
+ * 脏数据就会静默入库(费用 0.00 还会参与结算金额与自动匹配)。
+ *
+ * 正确做法是改抛本异常,把位置信息补齐,让导出工具能把原因挂到正确的行、标红正确的列。
+ *
+ * 本类位于 blade-common,供 blade-system 与 blade-transport 等模块共用。
+ *
+ * @author Chill
+ */
+public class ImportCellReadException extends ServiceException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 出错的单元格所在的 Excel 行号,绝对 0 基 (表头占 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;
+ }
+
+}
diff --git a/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java
index 6b0ad40d..0fc7d2f1 100644
--- a/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java
+++ b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java
@@ -45,7 +45,9 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -80,6 +82,443 @@ public class ImportFailureExcelUtil {
addValidationError(validationErrors, value != null && value.length() > maxLength, message);
}
+ /**
+ * 把「读取 Excel 阶段」的转换异常转成可读的失败原因。
+ *
+ * 背景:单元格里填了无法转换的内容(如金额列填「一万二千元」)时,EasyExcel 会在
+ * {@code ExcelUtil.read(...)} 阶段就抛 {@link cn.idev.excel.exception.ExcelDataConvertException},
+ * 根本走不到行级校验;若不兜底,用户看到的是
+ * 「Convert data ... to class java.math.BigDecimal error」这类技术文案。
+ *
+ * 本方法据异常里的行列位置与目标类型,生成「第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 findCause(Throwable throwable, Class 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 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;
+ }
+ }
+
+ /**
+ * 以「原始单元格文本」方式重读导入文件,得到的行用于构造失败明细。
+ *
+ * 优先使用 Excel 文件真实表头(含动态费用列等模型类未声明的列),保证失败明细
+ * 与原导入模板列一致,用户修正后可直接用该文件重新导入。
+ *
+ * 用途:当 {@code ExcelUtil.read(file, XxxExcel.class)} 因单元格无法转换而失败时,
+ * 已解析的实体列表是不完整的(异常中断了整批读取)。此时用本方法重读一次:
+ * 不做类型转换(直接读成 Map),因此绝不会再抛转换异常,可拿到**原表全部行**,
+ * 供导出「原表全量 + 仅出错行带原因」的失败明细。
+ *
+ * @param file 上传的 Excel 文件
+ * @param excelClass 导入模型类(表头缺失时按列序号反查表头名)
+ * @return 按行组织的明细数据:每行是「表头名 → 单元格文本」的有序 Map;
+ * 读不出内容时返回空列表(调用方按空明细处理)
+ */
+ public static List> readRawRows(java.io.InputStream file, Class> excelClass) {
+ List> rawRows = new ArrayList<>();
+ Map headerByIndex = new LinkedHashMap<>();
+ try {
+ cn.idev.excel.read.builder.ExcelReaderBuilder builder = cn.idev.excel.FastExcel
+ .read(file, new cn.idev.excel.event.AnalysisEventListener>() {
+ @Override
+ public void invokeHeadMap(Map headMap, cn.idev.excel.context.AnalysisContext context) {
+ if (headMap == null) {
+ return;
+ }
+ headMap.forEach((index, header) -> {
+ if (index == null) {
+ return;
+ }
+ String name = header == null ? "" : header.trim();
+ // 历史失败明细再导入时忽略旧的失败原因列,导出时重新追加
+ if (!name.isEmpty() && !FAILURE_REASON.equals(name)) {
+ headerByIndex.put(index, name);
+ }
+ });
+ }
+
+ @Override
+ public void invoke(Map data, cn.idev.excel.context.AnalysisContext context) {
+ rawRows.add(data == null ? new LinkedHashMap<>() : new LinkedHashMap<>(data));
+ }
+
+ @Override
+ public void doAfterAllAnalysed(cn.idev.excel.context.AnalysisContext context) {
+ // 无需处理
+ }
+ });
+ builder.headRowNumber(1).sheet().doRead();
+ } catch (Exception ignored) {
+ // 重读本身失败时退回空明细,由调用方按「无法读取」提示
+ return new ArrayList<>();
+ }
+ List> rows = new ArrayList<>();
+ for (Map rawRow : rawRows) {
+ Map row = new LinkedHashMap<>();
+ Set columnIndexes = new LinkedHashSet<>(headerByIndex.keySet());
+ if (rawRow != null) {
+ columnIndexes.addAll(rawRow.keySet());
+ }
+ List sortedIndexes = new ArrayList<>(columnIndexes);
+ Collections.sort(sortedIndexes);
+ for (Integer columnIndex : sortedIndexes) {
+ if (columnIndex == null) {
+ continue;
+ }
+ String columnName = headerByIndex.get(columnIndex);
+ if (columnName == null || columnName.isBlank()) {
+ columnName = columnName(excelClass, columnIndex);
+ }
+ if (columnName == null || columnName.isBlank()) {
+ columnName = "第" + (columnIndex + 1) + "列";
+ }
+ if (FAILURE_REASON.equals(columnName)) {
+ continue;
+ }
+ row.put(columnName, rawRow == null ? null : rawRow.get(columnIndex));
+ }
+ rows.add(row);
+ }
+ return rows;
+ }
+
+ /**
+ * 按原上传文件真实表头导出失败明细(含动态列),并追加「导入失败原因」。
+ *
+ * 适用于运输对账等「模板列 = 固定列 + 动态费用列」场景:按实体字段反射导出
+ * 会丢掉动态列,用户无法用失败文件修正后重新导入。本方法重读原文件全量列,
+ * 再按 failureList 行序挂上失败原因。
+ *
+ * @param response 响应
+ * @param fileName 文件名
+ * @param sheetName 工作表名
+ * @param file 原导入文件
+ * @param failureList 校验失败列表(与原表行序一致,成功行 errorMessage 可为空)
+ * @param excelClass 导入模型类(原文件重读失败时回退到按实体导出)
+ */
+ public static void exportOriginalWithFailures(HttpServletResponse response, String fileName, String sheetName,
+ org.springframework.web.multipart.MultipartFile file,
+ List> failureList, Class> excelClass) {
+ List> rawRows = new ArrayList<>();
+ if (file != null && !file.isEmpty()) {
+ try (java.io.InputStream inputStream = file.getInputStream()) {
+ rawRows = readRawRows(inputStream, excelClass);
+ } catch (IOException ignored) {
+ rawRows = new ArrayList<>();
+ }
+ }
+ if (rawRows.isEmpty()) {
+ // 原文件读不出时回退实体导出,至少保证失败原因可下载
+ if (failureList != null && !failureList.isEmpty()) {
+ export(response, fileName, sheetName, failureList, excelClass);
+ }
+ return;
+ }
+ Map failureMessageByRow = new HashMap<>();
+ if (failureList != null) {
+ for (int index = 0; index < failureList.size(); index++) {
+ Object message = errorMessage(failureList.get(index));
+ if (message != null && !String.valueOf(message).isBlank()) {
+ failureMessageByRow.put(index, String.valueOf(message));
+ }
+ }
+ }
+ exportRawFailure(response, fileName, sheetName, rawRows, failureMessageByRow);
+ }
+
+ /**
+ * 导出「读取阶段失败」明细:原表全量行 + 末尾「导入失败原因」列。
+ *
+ * 与 {@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> rawRows, Map 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 headers = new ArrayList<>();
+ for (Map row : rawRows) {
+ for (String key : row.keySet()) {
+ if (!headers.contains(key)) {
+ headers.add(key);
+ }
+ }
+ }
+ headers.add(FAILURE_REASON);
+ List> head = new ArrayList<>();
+ for (String header : headers) {
+ head.add(List.of(header));
+ }
+ List> rows = new ArrayList<>();
+ Map> redColumnsByRow = new HashMap<>();
+ List> headerKeywords = new ArrayList<>();
+ for (int columnIndex = 0; columnIndex < headers.size() - 1; columnIndex++) {
+ headerKeywords.add(headerKeywords(headers.get(columnIndex)));
+ }
+ for (int rowIndex = 0; rowIndex < rawRows.size(); rowIndex++) {
+ Map rawRow = rawRows.get(rowIndex);
+ List row = new ArrayList<>();
+ String failureMessage = failureMessageByRow == null ? null : failureMessageByRow.get(rowIndex);
+ for (int columnIndex = 0; columnIndex < headers.size() - 1; columnIndex++) {
+ row.add(rawRow.get(headers.get(columnIndex)));
+ }
+ row.add(failureMessage == null ? "" : failureMessage);
+ rows.add(row);
+ redColumnsByRow.put(rowIndex, resolveRawRedColumns(failureMessage, headerKeywords, headers.size() - 1));
+ }
+ 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> redColumnsByRow;
+ private final Map redStyleCache = new HashMap<>();
+ private final Map noWrapStyleCache = new HashMap<>();
+ private final Map columnWidthCache = new HashMap<>();
+
+ private RawFailureCellStyleHandler(List> rows, Map> redColumnsByRow) {
+ this.failureReasonColumnIndex = rows.isEmpty() ? 0 : rows.get(0).size() - 1;
+ this.redColumnsByRow = redColumnsByRow;
+ }
+
+ @Override
+ public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
+ List> 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 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;
+ }
+ }
+
+ /**
+ * 读取阶段异常的一站式兜底:重读原表 → 导出「全量行 + 出错行原因」失败明细。
+ *
+ * 控制器用法:在导入的 try 块末尾追加
+ *
+ * } catch (Exception exception) {
+ * if (ImportFailureExcelUtil.exportReadFailure(response, fileName, sheetName, file, XxxExcel.class, exception)) {
+ * return null;
+ * }
+ * throw exception;
+ * }
+ *
+ *
+ * @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> rawRows;
+ try (java.io.InputStream inputStream = file.getInputStream()) {
+ rawRows = readRawRows(inputStream, excelClass);
+ } catch (IOException ioException) {
+ rawRows = new ArrayList<>();
+ }
+ Map failureMessageByRow = new HashMap<>();
+ if (rawRows.isEmpty()) {
+ // 重读也拿不到内容时,退化为「仅失败原因」一行,至少让用户看到可读原因
+ Map 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 基,只对数据行计数),供挂失败原因使用。
+ *
+ * 无论哪种异常,源信息都是绝对 0 基行号 (表头占 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)。
+ *
+ * 两条来源统一到这里: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";
@@ -247,6 +686,69 @@ public class ImportFailureExcelUtil {
.toList();
}
+ /**
+ * 按真实表头生成定位关键词(用于原表失败明细标红)
+ */
+ private static List headerKeywords(String header) {
+ if (header == null || header.isBlank()) {
+ return List.of();
+ }
+ List keywords = new ArrayList<>();
+ keywords.add(header);
+ keywords.addAll(Arrays.asList(header.replace("*", "").split("[//、()()\\s]+")));
+ return keywords.stream()
+ .map(ImportFailureExcelUtil::normalize)
+ .filter(keyword -> keyword.length() >= 2)
+ .distinct()
+ .toList();
+ }
+
+ /**
+ * 按失败原因命中表头关键词,计算需要标红的列(含失败原因列)
+ */
+ private static Set resolveRawRedColumns(String failureMessage, List> headerKeywords,
+ int failureReasonColumnIndex) {
+ Set redColumns = new LinkedHashSet<>();
+ redColumns.add(failureReasonColumnIndex);
+ String normalizedReason = normalize(failureMessage);
+ if (normalizedReason.isEmpty()) {
+ return redColumns;
+ }
+ List matches = new ArrayList<>();
+ for (int column = 0; column < headerKeywords.size(); column++) {
+ for (String keyword : headerKeywords.get(column)) {
+ int fromIndex = 0;
+ while (true) {
+ int index = normalizedReason.indexOf(keyword, fromIndex);
+ if (index < 0) {
+ break;
+ }
+ matches.add(new int[]{index, index + keyword.length(), column});
+ fromIndex = index + 1;
+ }
+ }
+ }
+ for (int[] match : matches) {
+ if (!isRawMatchSubsumed(match, matches)) {
+ redColumns.add(match[2]);
+ }
+ }
+ return redColumns;
+ }
+
+ private static boolean isRawMatchSubsumed(int[] match, List matches) {
+ int matchLength = match[1] - match[0];
+ for (int[] other : matches) {
+ if (other[2] == match[2]) {
+ continue;
+ }
+ if ((other[1] - other[0]) > matchLength && other[0] <= match[0] && other[1] >= match[1]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static class ImportFailureCellStyleHandler implements CellWriteHandler {
private final int failureReasonColumnIndex;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureException.java
similarity index 72%
rename from blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java
rename to blade-common/src/main/java/org/springblade/common/excel/ImportFailureException.java
index 0277ec01..6065ce0f 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java
+++ b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureException.java
@@ -23,7 +23,7 @@
*
* 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;
/**
* 导入失败异常,携带失败明细用于导出原表并标注错误。
*
- * 批量导入采用「全失败即整批回滚」语义:任一行校验失败都会抛出本异常触发事务回滚,
- * 失败明细在抛异常前已收集完毕,因此回滚不影响明细的内容。
+ * 批量导入采用「任一行失败即整批回滚」语义:循环内先收集完全部错误明细,
+ * 再抛出本异常触发事务回滚。明细在抛异常前已收集完毕,因此回滚不影响明细内容。
+ *
+ * 注意:仅标注 {@code @Transactional} 并不足够。若循环内把异常吞掉(catch 后
+ * 只往 errorList 塞数据并正常 return),方法会正常返回,Spring 会照常提交事务。
+ * 必须抛出异常才能让事务真正回滚。
+ *
+ * 本类位于 blade-common,供 blade-system 与 blade-transport 等模块共用。
*
* @author Chill
*/
diff --git a/blade-gateway/src/main/resources/bootstrap.yml b/blade-gateway/src/main/resources/bootstrap.yml
index 0f5092a9..e4fa3a81 100644
--- a/blade-gateway/src/main/resources/bootstrap.yml
+++ b/blade-gateway/src/main/resources/bootstrap.yml
@@ -2,6 +2,9 @@ server:
port: 80
spring:
+ codec:
+ # 运单批量导入等大 JSON / 失败明细导出场景,默认 256KB 会触发网关 500
+ max-in-memory-size: 20MB
cloud:
gateway:
server:
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java
index ecbdf0c6..4cf979bb 100644
--- a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java
@@ -7,6 +7,7 @@ import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
import org.springblade.process.pojo.vo.BusinessProcessVO;
+import org.springblade.process.pojo.vo.MkFormBindVO;
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
import org.springblade.process.pojo.vo.ProcessTodoVO;
import org.springframework.cloud.openfeign.FeignClient;
@@ -40,6 +41,8 @@ public interface IBusinessProcessClient {
String QUERY_APPROVED_RECORD_LIST = API_PREFIX + "/queryApprovedRecordsNoAttachments";
String GET_CURRENT_NODES = API_PREFIX + "/getCurrentNodes";
String GET_PROCESS_INFO = API_PREFIX + "/getProcessInfo";
+ String RESOLVE_BY_FORM_INSTANCE_ID = API_PREFIX + "/resolveByFormInstanceId";
+ String MARK_PROCESS_FINISHED = API_PREFIX + "/markProcessFinished";
/**
* 提交业务流程
@@ -126,4 +129,24 @@ public interface IBusinessProcessClient {
@GetMapping(GET_PROCESS_INFO)
FR getProcessInfo(@RequestParam("processInstanceId") String processInstanceId,
@RequestParam(value = "loginName", required = false) String loginName);
+
+ /**
+ * 按表单实例id解析业务绑定
+ *
+ * @param formInstanceId 表单实例id
+ * @return 绑定信息(含业务主键)
+ */
+ @GetMapping(RESOLVE_BY_FORM_INSTANCE_ID)
+ FR resolveByFormInstanceId(@RequestParam("formInstanceId") Long formInstanceId);
+
+ /**
+ * 公开页流程结束时标记业务流程通过/驳回并完结
+ *
+ * @param processInstanceId 流程实例id
+ * @param approved true=通过 false=驳回
+ * @return 是否更新成功
+ */
+ @PostMapping(MARK_PROCESS_FINISHED)
+ FR markProcessFinished(@RequestParam("processInstanceId") String processInstanceId,
+ @RequestParam("approved") boolean approved);
}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java
index bac2329d..45329958 100644
--- a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java
@@ -65,6 +65,12 @@ public class BusinessProcess implements Serializable {
*/
@Schema(description = "业务id")
private Long bizId;
+ /**
+ * MK表单实例id
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "MK表单实例id")
+ private Long formInstanceId;
/**
* 流程实例id
*/
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/MkFormBind.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/MkFormBind.java
new file mode 100644
index 00000000..03499c83
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/MkFormBind.java
@@ -0,0 +1,94 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * MK表单实例绑定表 实体类
+ *
+ * @author BladeX
+ */
+@Data
+@TableName("blade_mk_form_bind")
+@Schema(description = "MkFormBind对象")
+public class MkFormBind implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private Long id;
+
+ @Schema(description = "租户ID")
+ private String tenantId;
+
+ @Schema(description = "业务类型")
+ private String bizType;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "业务主键")
+ private Long bizId;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "MK表单实例id")
+ private Long formInstanceId;
+
+ @Schema(description = "MK流程实例id")
+ private String processInstanceId;
+
+ @Schema(description = "是否当前生效(1是0否)")
+ private Integer isCurrent;
+
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @Schema(description = "创建时间", hidden = true)
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @Schema(description = "更新时间", hidden = true)
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/MkFormBindVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/MkFormBindVO.java
new file mode 100644
index 00000000..a1623972
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/MkFormBindVO.java
@@ -0,0 +1,61 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.pojo.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * MK表单实例绑定 VO
+ *
+ * @author BladeX
+ */
+@Data
+@Schema(description = "MkFormBindVO")
+public class MkFormBindVO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "业务主键")
+ private Long bizId;
+
+ @Schema(description = "业务类型")
+ private String bizType;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "MK表单实例id")
+ private Long formInstanceId;
+
+ @Schema(description = "MK流程实例id")
+ private String processInstanceId;
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java
index 932e41a7..18338ed0 100644
--- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java
@@ -138,6 +138,12 @@ public class Dept extends TenantEntity {
@Schema(description = "部门编码")
private String deptCode;
+ /**
+ * 金蝶部门代码
+ */
+ @Schema(description = "金蝶部门代码")
+ private String kingdeeDeptCode;
+
/**
* 组织简称
*/
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/AirportMasterVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/AirportMasterVO.java
index 28c7da60..f445c719 100644
--- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/AirportMasterVO.java
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/AirportMasterVO.java
@@ -52,6 +52,13 @@ public class AirportMasterVO extends AirportMaster {
@Schema(description = "导入错误信息")
private String errorMessage;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java
index b45d4580..7b77ee32 100644
--- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/FeeItemVO.java
@@ -49,6 +49,10 @@ public class FeeItemVO extends FeeItem {
@Schema(description = "组织")
private String createDeptName;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人")
private String updateUserName;
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/PortTerminalVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/PortTerminalVO.java
index 78474b07..b08a4c8a 100644
--- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/PortTerminalVO.java
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/PortTerminalVO.java
@@ -52,6 +52,13 @@ public class PortTerminalVO extends PortTerminal {
@Schema(description = "导入错误信息")
private String errorMessage;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RailwayStationVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RailwayStationVO.java
index c28a15fc..17a50ba7 100644
--- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RailwayStationVO.java
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RailwayStationVO.java
@@ -52,6 +52,13 @@ public class RailwayStationVO extends RailwayStation {
@Schema(description = "导入错误信息")
private String errorMessage;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/WaybillImportBatchRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/WaybillImportBatchRequest.java
index c346165c..e445395e 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/WaybillImportBatchRequest.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/WaybillImportBatchRequest.java
@@ -33,4 +33,6 @@ public class WaybillImportBatchRequest {
private String planName;
private String remark;
private List> rows;
+ /** 分批确认导入时,是否追加到已有批次(首批为 false,后续批为 true)。 */
+ private Boolean append;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java
index 6393fc0a..e037a882 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java
@@ -56,7 +56,7 @@ public class LoadingManage extends TenantEntity {
@Schema(description = "货物名称")
private String cargoName;
- @Schema(description = "车/船/航班/班列")
+ @Schema(description = "车牌号/船班号/船次号/班次号")
private String vehicleNo;
@Schema(description = "挂车车牌号")
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java
index 81c481d9..dec1a0b5 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java
@@ -162,7 +162,7 @@ public class Waybill extends TenantEntity {
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String driverRejectReason;
- @Schema(description = "车/船/航班/班列号")
+ @Schema(description = "车牌号/船班号/船次号/班次号")
private String vehicleNo;
@Schema(description = "船长")
@@ -234,6 +234,9 @@ public class Waybill extends TenantEntity {
@Schema(description = "配载单号")
private String loadingNo;
+ @Schema(description = "配载标识号")
+ private String loadingIdentifier;
+
@Schema(description = "运单批次号")
private String batchNo;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AccidentRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AccidentRecordVO.java
index e06dc084..427bf362 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AccidentRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AccidentRecordVO.java
@@ -80,6 +80,13 @@ public class AccidentRecordVO extends AccidentRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AnnualInspectionRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AnnualInspectionRecordVO.java
index b76896a3..24606c29 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AnnualInspectionRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AnnualInspectionRecordVO.java
@@ -68,6 +68,10 @@ public class AnnualInspectionRecordVO extends AnnualInspectionRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java
index 802ed9ed..5e924aec 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java
@@ -58,6 +58,10 @@ public class CustomerArchiveVO extends CustomerArchive {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "联系人")
private List contacts = new ArrayList<>();
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java
index 856575c6..5b22c7ac 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverVO.java
@@ -69,4 +69,8 @@ public class DriverVO extends Driver {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate warningDate;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EquipmentLedgerVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EquipmentLedgerVO.java
index 6df81ad4..72def95e 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EquipmentLedgerVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EquipmentLedgerVO.java
@@ -25,5 +25,8 @@ public class EquipmentLedgerVO extends EquipmentLedger {
@Serial
private static final long serialVersionUID = 1L;
@TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+ @TableField(exist = false)
private String updateUserName;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EtcRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EtcRecordVO.java
index cef2656d..9c636dcd 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EtcRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/EtcRecordVO.java
@@ -26,6 +26,10 @@ public class EtcRecordVO extends EtcRecord {
@Serial
private static final long serialVersionUID = 1L;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InsuranceRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InsuranceRecordVO.java
index 3af9b3b0..0aeeb030 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InsuranceRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InsuranceRecordVO.java
@@ -63,6 +63,13 @@ public class InsuranceRecordVO extends InsuranceRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenancePlanVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenancePlanVO.java
index bb4d225d..35306e67 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenancePlanVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenancePlanVO.java
@@ -62,6 +62,13 @@ public class MaintenancePlanVO extends MaintenancePlan {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java
index 260f0946..bfacf9f3 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java
@@ -62,6 +62,13 @@ public class MaintenanceRecordVO extends MaintenanceRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java
index 490607ab..8ef58199 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MasterOrderVO.java
@@ -41,6 +41,11 @@ public class MasterOrderVO extends MasterOrder {
private List boundWaybills;
@TableField(exist = false)
private BigDecimal totalQuantity;
+ /**
+ * 各品类已调度量(按货物名称+类型聚合,取各分段已调度量的最大值)
+ */
+ @TableField(exist = false)
+ private Map dispatchedGoods;
@TableField(exist = false)
private String createUserName;
@TableField(exist = false)
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MileageRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MileageRecordVO.java
index 4c39754a..963e809c 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MileageRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MileageRecordVO.java
@@ -66,6 +66,10 @@ public class MileageRecordVO extends MileageRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OilElectricRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OilElectricRecordVO.java
index 94b35024..d3a08860 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OilElectricRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OilElectricRecordVO.java
@@ -76,6 +76,10 @@ public class OilElectricRecordVO extends OilElectricRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OtherExpenseRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OtherExpenseRecordVO.java
index 0c6db1e5..1639fa43 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OtherExpenseRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/OtherExpenseRecordVO.java
@@ -49,6 +49,10 @@ public class OtherExpenseRecordVO extends OtherExpenseRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TireReplacementRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TireReplacementRecordVO.java
index f9140956..eaf4b1c4 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TireReplacementRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TireReplacementRecordVO.java
@@ -63,6 +63,13 @@ public class TireReplacementRecordVO extends TireReplacementRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportChangeRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportChangeRecordVO.java
index 2ae66b51..60f904b6 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportChangeRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportChangeRecordVO.java
@@ -57,6 +57,10 @@ public class TransportChangeRecordVO extends TransportChangeRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportShipVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportShipVO.java
index 76fb12af..27e06308 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportShipVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportShipVO.java
@@ -62,4 +62,8 @@ public class TransportShipVO extends TransportShip {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate warningDate;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java
index 7536d193..bd4ab362 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportVehicleVO.java
@@ -76,4 +76,8 @@ public class TransportVehicleVO extends TransportVehicle {
@Schema(description = "绑定司机")
private String boundDriver;
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ViolationRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ViolationRecordVO.java
index 98b7569c..0e462908 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ViolationRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ViolationRecordVO.java
@@ -63,6 +63,13 @@ public class ViolationRecordVO extends ViolationRecord {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
+ /**
+ * 创建人姓名
+ */
+ @TableField(exist = false)
+ @Schema(description = "创建人姓名")
+ private String createUserName;
+
/**
* 更新人姓名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherFolderVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherFolderVO.java
index 1f70c945..6c9ca5a5 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherFolderVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherFolderVO.java
@@ -25,6 +25,7 @@ public class VoucherFolderVO implements Serializable {
private String folderName;
private Long waybillId;
private String waybillNo;
+ private String loadingNo;
private Integer matched;
private String processStatus;
private String fileName;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportRowErrorVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportRowErrorVO.java
new file mode 100644
index 00000000..2fa95b19
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportRowErrorVO.java
@@ -0,0 +1,26 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ */
+package org.springblade.transport.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/** 运单批量导入单行校验错误。 */
+@Data
+@Schema(description = "运单批量导入单行校验错误")
+public class WaybillImportRowErrorVO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "行号(从0开始,相对当前批次)")
+ private Integer rowIndex;
+
+ @Schema(description = "错误信息")
+ private String errorMessage;
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportValidateResultVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportValidateResultVO.java
new file mode 100644
index 00000000..193c5ee7
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportValidateResultVO.java
@@ -0,0 +1,28 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ */
+package org.springblade.transport.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/** 运单批量导入校验结果。 */
+@Data
+@Schema(description = "运单批量导入校验结果")
+public class WaybillImportValidateResultVO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "是否全部通过")
+ private Boolean success;
+
+ @Schema(description = "校验错误列表")
+ private List errors = new ArrayList<>();
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java
index cea37c38..ebba5343 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java
@@ -66,6 +66,10 @@ public class WaybillVO extends Waybill {
@Schema(description = "业务状态名称")
private String businessStatusName;
+ @TableField(exist = false)
+ @Schema(description = "运输方式名称")
+ private String transportTypeName;
+
@TableField(exist = false)
@Schema(description = "是否需要司机确认接单(过程配置接单节点)")
private Boolean requireAccept;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java
index 6f83dd9f..f65cc45e 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java
@@ -13,6 +13,7 @@ import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.StringUtil;
import org.springblade.process.pojo.dto.ApprovalDTO;
import org.springblade.process.pojo.vo.ApprovalVO;
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
@@ -55,15 +56,27 @@ public class BusinessProcessController extends BladeController {
@PostMapping("/processSubmit")
@ApiOperationSupport(order = 2)
- @Operation(summary = "提交MK审核流", description = "调用mk processSubmit,传入templateCode/submitIdentity/formInstanceId")
+ @Operation(summary = "提交MK审核流", description = "传入bizId/bizType/templateCode,后端生成或复用formInstanceId后调用mk processSubmit")
public R processSubmit(@Validated @RequestBody MKProcessCreateDTO param) {
return R.data(businessProcessService.processSubmit(param));
}
+ @GetMapping("/hasMkProcessInstance")
+ @ApiOperationSupport(order = 8)
+ @Operation(summary = "查询MK流程实例是否存在", description = "传入业务主键bizId,有数据则无需删除/重新提交")
+ public R hasMkProcessInstance(@Valid @NotBlank(message = "业务id不能为空") String bizId) {
+ return R.data(businessProcessService.hasMkProcessInstance(bizId));
+ }
+
@PostMapping("/processDelete")
@ApiOperationSupport(order = 7)
+ @Operation(summary = "删除MK审核流", description = "传入bizId,删除当前业务对应MK流程实例")
public R processDelete(@RequestBody MKProcessCreateDTO param) {
- return R.data(businessProcessService.processDelete(param == null ? null : param.getFormInstanceId()));
+ String bizId = null;
+ if (param != null) {
+ bizId = StringUtil.isNotBlank(param.getBizId()) ? param.getBizId() : param.getFormInstanceId();
+ }
+ return R.data(businessProcessService.processDelete(bizId));
}
@GetMapping("/isEditView")
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java
index fa87f975..6319b680 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java
@@ -11,6 +11,7 @@ import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
import org.springblade.process.pojo.vo.BusinessProcessVO;
+import org.springblade.process.pojo.vo.MkFormBindVO;
import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
import org.springblade.process.pojo.vo.ProcessTodoVO;
import org.springblade.process.service.IBusinessProcessService;
@@ -94,4 +95,19 @@ public class BusinessProcessClient implements IBusinessProcessClient {
@RequestParam(value = "loginName", required = false) String loginName) {
return FR.data(businessProcessService.getProcessInfo(processInstanceId, loginName));
}
+
+ @PreAuth(AuthConstant.PERMIT_ALL)
+ @GetMapping(RESOLVE_BY_FORM_INSTANCE_ID)
+ @Override
+ public FR resolveByFormInstanceId(@RequestParam("formInstanceId") Long formInstanceId) {
+ return FR.data(businessProcessService.resolveByFormInstanceId(formInstanceId));
+ }
+
+ @PreAuth(AuthConstant.PERMIT_ALL)
+ @PostMapping(MARK_PROCESS_FINISHED)
+ @Override
+ public FR markProcessFinished(@RequestParam("processInstanceId") String processInstanceId,
+ @RequestParam("approved") boolean approved) {
+ return FR.data(businessProcessService.markProcessFinished(processInstanceId, approved));
+ }
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml
index 47b45b79..4aa5df53 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml
@@ -6,6 +6,7 @@
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/mapper/MkFormBindMapper.java b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/MkFormBindMapper.java
new file mode 100644
index 00000000..23e701f4
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/MkFormBindMapper.java
@@ -0,0 +1,38 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.process.pojo.entity.MkFormBind;
+
+/**
+ * MK表单实例绑定表 Mapper 接口
+ *
+ * @author BladeX
+ */
+public interface MkFormBindMapper extends BaseMapper {
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/mapper/MkFormBindMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/MkFormBindMapper.xml
new file mode 100644
index 00000000..b19c1df2
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/MkFormBindMapper.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java
index e3b7834a..36b15ce0 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java
@@ -29,18 +29,43 @@ public interface IBusinessProcessService extends IService {
/**
* 直接调用 MK processSubmit 提交流程
*
- * @param param MK 流程创建参数
+ * @param param MK 流程创建参数(传 bizId+bizType,后端生成/复用 formInstanceId)
* @return 流程实例 id
*/
String processSubmit(MKProcessCreateDTO param);
/**
- * 按业务表单实例 id 删除 MK 流程(不删除本地业务流程记录,供驳回后重新提交使用)
+ * 按业务 id 查询 MK 流程实例是否仍有数据(本地有 processInstanceId 且 MK getProcessInfo 有返回)
*
- * @param formInstanceId 业务表单实例 id
+ * @param bizId 业务主键
+ * @return true=流程实例仍存在
+ */
+ boolean hasMkProcessInstance(String bizId);
+
+ /**
+ * 按业务主键删除 MK 流程(不删除本地业务流程记录与表单绑定,供驳回后重新提交使用)
+ *
+ * @param bizId 业务主键
* @return 是否成功
*/
- boolean processDelete(String formInstanceId);
+ boolean processDelete(String bizId);
+
+ /**
+ * 按表单实例id解析业务绑定
+ *
+ * @param formInstanceId 表单实例id
+ * @return 绑定信息
+ */
+ MkFormBindVO resolveByFormInstanceId(Long formInstanceId);
+
+ /**
+ * 公开页流程结束时标记业务流程通过/驳回并完结
+ *
+ * @param processInstanceId 流程实例id
+ * @param approved true=通过 false=驳回
+ * @return 是否更新成功
+ */
+ boolean markProcessFinished(String processInstanceId, boolean approved);
/**
* 获取流程当前节点详情
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IMkFormBindService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IMkFormBindService.java
new file mode 100644
index 00000000..5bd9fe2b
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IMkFormBindService.java
@@ -0,0 +1,81 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.process.pojo.entity.MkFormBind;
+import org.springblade.process.pojo.vo.MkFormBindVO;
+
+/**
+ * MK表单实例绑定表 服务类
+ *
+ * @author BladeX
+ */
+public interface IMkFormBindService extends IService {
+
+ /**
+ * 查询业务当前生效的表单实例绑定
+ *
+ * @param bizType 业务类型(可空,空时仅按业务id查)
+ * @param bizId 业务主键
+ * @return 当前绑定,不存在返回 null
+ */
+ MkFormBind getCurrentBind(String bizType, Long bizId);
+
+ /**
+ * 按表单实例id查询绑定
+ *
+ * @param formInstanceId 表单实例id
+ * @return 绑定记录
+ */
+ MkFormBind getByFormInstanceId(Long formInstanceId);
+
+ /**
+ * 按表单实例id解析业务主键等信息
+ *
+ * @param formInstanceId 表单实例id
+ * @return 绑定 VO
+ */
+ MkFormBindVO resolveByFormInstanceId(Long formInstanceId);
+
+ /**
+ * 解析或生成表单实例id:无当前绑定则新建;强制新建/当前流程已通过或已完结则新建;否则复用
+ *
+ * @param bizType 业务类型
+ * @param bizId 业务主键
+ * @param forceNew 是否强制生成新表单实例(审核通过后再次提交)
+ * @return 本次应使用的表单实例id
+ */
+ Long resolveOrCreateFormInstanceId(String bizType, Long bizId, boolean forceNew);
+
+ /**
+ * 回写流程实例id到当前绑定
+ *
+ * @param formInstanceId 表单实例id
+ * @param processInstanceId 流程实例id
+ */
+ void updateProcessInstanceId(Long formInstanceId, String processInstanceId);
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java
index 088c83d5..fed83a70 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java
@@ -25,6 +25,7 @@ import org.springblade.process.pojo.entity.BusinessProcess;
import org.springblade.process.pojo.enums.TodoStatus;
import org.springblade.process.pojo.vo.*;
import org.springblade.process.service.IBusinessProcessService;
+import org.springblade.process.service.IMkFormBindService;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.service.IUserService;
import org.springblade.thirdparty.mk.config.MKProperties;
@@ -72,6 +73,7 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery()
+ .eq(BusinessProcess::getBizId, bizId)
+ .orderByDesc(BusinessProcess::getUpdateTime)
+ .last("limit 1")
+ );
+ boolean forceNew = Boolean.TRUE.equals(param.getForceNewFormInstance())
+ || shouldForceNewFormInstance(businessProcess, loginName);
+ Long formInstanceId = mkFormBindService.resolveOrCreateFormInstanceId(bizType, bizId, forceNew);
+ String formInstanceIdText = String.valueOf(formInstanceId);
+ param.setFormInstanceId(formInstanceIdText);
+
+ // 已有流程实例且 MK 仍能查到详情时,不再删除、不再重新创建提交(强制新建或流程已结束时跳过短路)
+ if (!forceNew && businessProcess != null && StringUtil.isNotBlank(businessProcess.getProcessInstanceId())
+ && Objects.equals(formInstanceId, businessProcess.getFormInstanceId())) {
+ String existingProcessInstanceId = businessProcess.getProcessInstanceId();
+ Object existingProcessInfo = this.getProcessInfo(existingProcessInstanceId, loginName);
+ if (hasProcessInfoData(existingProcessInfo) && !isMkProcessFinished(existingProcessInfo)) {
+ log.info("MK流程实例已存在,跳过创建并提交流程 bizId={} formInstanceId={} processInstanceId={}",
+ bizId, formInstanceIdText, existingProcessInstanceId);
+ businessProcess.setApproveStatus(ApproveStatusEnum.APPROVING.getValue());
+ if (StringUtil.isNotBlank(param.getSubject())) {
+ businessProcess.setSubject(param.getSubject());
+ }
+ businessProcess.setPromoterLoginName(loginName);
+ businessProcess.setFormInstanceId(formInstanceId);
+ this.updateById(businessProcess);
+ this.getCurrentNodes(existingProcessInstanceId, loginName);
+ this.syncBizFromProcessInfo(bizType, String.valueOf(bizId), existingProcessInfo);
+ return existingProcessInstanceId;
+ }
+ // 已结束但仍能查到详情:继续走新建提交流程
+ forceNew = true;
+ formInstanceId = mkFormBindService.resolveOrCreateFormInstanceId(bizType, bizId, true);
+ formInstanceIdText = String.valueOf(formInstanceId);
+ param.setFormInstanceId(formInstanceIdText);
+ }
+
MKProcessCreateDTO mkParam = new MKProcessCreateDTO();
- mkParam.setFormInstanceId(param.getFormInstanceId());
+ mkParam.setFormInstanceId(formInstanceIdText);
mkParam.setSubject(param.getSubject());
mkParam.setSubmitIdentity(loginName);
mkParam.setLoginName(loginName);
@@ -149,15 +189,6 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery()
- .eq(BusinessProcess::getBizId, bizId)
- );
if (businessProcess == null) {
businessProcess = new BusinessProcess();
businessProcess.setBizId(bizId);
@@ -177,30 +208,151 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery()
+ .eq(BusinessProcess::getBizId, id)
+ .orderByDesc(BusinessProcess::getUpdateTime)
+ .last("limit 1")
+ );
+ if (businessProcess == null || StringUtil.isBlank(businessProcess.getProcessInstanceId())) {
+ return false;
+ }
+ // 审批通过 / 已完结:允许再次提交新表单实例
+ if (Objects.equals(ApproveStatusEnum.APPROVED.getValue(), businessProcess.getApproveStatus())
+ || (businessProcess.getIsCompleted() != null && businessProcess.getIsCompleted() == 1)) {
+ return false;
+ }
+ String loginName = businessProcess.getPromoterLoginName();
+ if (StringUtil.isBlank(loginName)) {
+ loginName = resolveCurrentUserPhone();
+ }
+ if (StringUtil.isBlank(loginName)) {
+ log.warn("查询MK流程实例跳过,登录名为空 bizId={} processInstanceId={}",
+ bizId, businessProcess.getProcessInstanceId());
+ return false;
+ }
+ Object processInfo = this.getProcessInfo(businessProcess.getProcessInstanceId(), loginName);
+ // MK 流程已结束(fdProcessStatus=30)时视为无有效待办,需重新提交新实例
+ if (isMkProcessFinished(processInfo)) {
+ return false;
+ }
+ return hasProcessInfoData(processInfo);
+ }
+
+ /**
+ * MK getProcessInfo 是否为已结束流程
+ */
+ private boolean isMkProcessFinished(Object processInfo) {
+ if (!(processInfo instanceof Map, ?> map)) {
+ return false;
+ }
+ Object status = map.get("fdProcessStatus");
+ return status != null && "30".equals(String.valueOf(status));
+ }
+
+ /**
+ * MK getProcessInfo 是否返回有效数据
+ */
+ private boolean hasProcessInfoData(Object processInfo) {
+ if (processInfo == null) {
+ return false;
+ }
+ if (processInfo instanceof Map, ?> map) {
+ return !map.isEmpty();
+ }
+ if (processInfo instanceof Collection> collection) {
+ return !collection.isEmpty();
+ }
+ if (processInfo instanceof CharSequence text) {
+ return StringUtil.isNotBlank(text.toString());
+ }
+ return true;
+ }
+
@Transactional(rollbackFor = Exception.class)
@Override
public boolean processDelete(String formInstanceId) {
- AssertUtils.notBlank(formInstanceId, "表单实例id不能为空");
+ AssertUtils.notBlank(formInstanceId, "业务id不能为空");
Long bizId;
try {
bizId = Long.valueOf(formInstanceId);
} catch (NumberFormatException e) {
- throw new ServiceException("表单实例id格式不正确");
+ throw new ServiceException("业务id格式不正确");
}
BusinessProcess businessProcess = this.getOne(Wrappers.lambdaQuery()
.eq(BusinessProcess::getBizId, bizId)
+ .orderByDesc(BusinessProcess::getUpdateTime)
+ .last("limit 1")
);
if (businessProcess == null || StringUtil.isBlank(businessProcess.getProcessInstanceId())) {
- log.warn("客商驳回后删除流程跳过,未找到流程实例 formInstanceId={}", formInstanceId);
+ log.warn("驳回后删除流程跳过,未找到流程实例 bizId={}", bizId);
return true;
}
String loginName = businessProcess.getPromoterLoginName();
@@ -208,8 +360,8 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery()
+ .eq(BusinessProcess::getBizId, formInstanceId)
+ .last("limit 1")
+ );
+ if (businessProcess == null) {
+ businessProcess = this.getOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getFormInstanceId, formInstanceId)
+ .last("limit 1")
+ );
+ }
+ if (businessProcess == null) {
+ return null;
+ }
+ MkFormBindVO fallback = new MkFormBindVO();
+ fallback.setBizId(businessProcess.getBizId());
+ fallback.setFormInstanceId(businessProcess.getFormInstanceId() != null
+ ? businessProcess.getFormInstanceId() : businessProcess.getBizId());
+ fallback.setProcessInstanceId(businessProcess.getProcessInstanceId());
+ return fallback;
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public boolean markProcessFinished(String processInstanceId, boolean approved) {
+ AssertUtils.notBlank(processInstanceId, "流程实例id不能为空");
+ BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
+ if (businessProcess == null) {
+ log.warn("标记流程完结跳过,未找到业务流程 processInstanceId={}", processInstanceId);
+ return false;
+ }
+ String approveStatus = approved
+ ? ApproveStatusEnum.APPROVED.getValue()
+ : ApproveStatusEnum.REJECTED.getValue();
+ businessProcess.setApproveStatus(approveStatus);
+ businessProcess.setIsCompleted(1);
+ businessProcess.setCompleteTime(new Date());
+ businessProcess.setCurrentNodeIds(null);
+ businessProcess.setCurrentNodeNames(null);
+ businessProcess.setCurrentHandlers(null);
+ boolean updated = this.updateById(businessProcess);
+ log.info("标记流程完结 processInstanceId={} approved={} approveStatus={} result={}",
+ processInstanceId, approved, approveStatus, updated);
+ return updated;
+ }
+
@Override
public List> getCurrentNodes(String processInstanceId, String loginName) {
if (StringUtils.isBlank(processInstanceId)) {
@@ -358,17 +563,20 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery()
.eq(BusinessProcess::getBizId, bizId)
+ .orderByDesc(BusinessProcess::getUpdateTime)
.last("limit 1")
);
AssertUtils.notNull(businessProcess, "业务流程不存在");
processInstanceId = businessProcess.getProcessInstanceId();
}
try {
- String mkApprovalUrl = mkService.getMKApprovalUrl(processInstanceId, AuthUtil.getUserAccount());
+ String loginName = resolveCurrentUserPhone();
+ AssertUtils.notBlank(loginName, "当前用户手机号为空,无法获取审核跳转地址");
+ String mkApprovalUrl = mkService.getMKApprovalUrl(processInstanceId, loginName);
AssertUtils.notBlank(mkApprovalUrl, "获取mk审批页面链接异常");
return mkApprovalUrl;
} catch (MKException e) {
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/MkFormBindServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/MkFormBindServiceImpl.java
new file mode 100644
index 00000000..2e9c4346
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/MkFormBindServiceImpl.java
@@ -0,0 +1,177 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.log.utils.AssertUtils;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.process.mapper.BusinessProcessMapper;
+import org.springblade.process.mapper.MkFormBindMapper;
+import org.springblade.process.pojo.entity.BusinessProcess;
+import org.springblade.process.pojo.entity.MkFormBind;
+import org.springblade.process.pojo.enums.ApproveStatusEnum;
+import org.springblade.process.pojo.vo.MkFormBindVO;
+import org.springblade.process.service.IMkFormBindService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * MK表单实例绑定表 服务实现类
+ *
+ * @author BladeX
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MkFormBindServiceImpl extends ServiceImpl implements IMkFormBindService {
+
+ private final BusinessProcessMapper businessProcessMapper;
+
+ @Override
+ public MkFormBind getCurrentBind(String bizType, Long bizId) {
+ AssertUtils.notNull(bizId, "业务id不能为空");
+ MkFormBind bind = null;
+ if (StringUtil.isNotBlank(bizType)) {
+ bind = this.getOne(Wrappers.lambdaQuery()
+ .eq(MkFormBind::getBizType, bizType)
+ .eq(MkFormBind::getBizId, bizId)
+ .eq(MkFormBind::getIsCurrent, 1)
+ .orderByDesc(MkFormBind::getCreateTime)
+ .last("limit 1")
+ );
+ }
+ if (bind == null) {
+ // 兼容历史迁移数据 biz_type 为空,或未传 bizType
+ bind = this.getOne(Wrappers.lambdaQuery()
+ .eq(MkFormBind::getBizId, bizId)
+ .eq(MkFormBind::getIsCurrent, 1)
+ .orderByDesc(MkFormBind::getCreateTime)
+ .last("limit 1")
+ );
+ }
+ return bind;
+ }
+
+ @Override
+ public MkFormBind getByFormInstanceId(Long formInstanceId) {
+ AssertUtils.notNull(formInstanceId, "表单实例id不能为空");
+ return this.getOne(Wrappers.lambdaQuery()
+ .eq(MkFormBind::getFormInstanceId, formInstanceId)
+ .last("limit 1")
+ );
+ }
+
+ @Override
+ public MkFormBindVO resolveByFormInstanceId(Long formInstanceId) {
+ MkFormBind bind = this.getByFormInstanceId(formInstanceId);
+ if (bind == null) {
+ return null;
+ }
+ MkFormBindVO vo = new MkFormBindVO();
+ vo.setBizId(bind.getBizId());
+ vo.setBizType(bind.getBizType());
+ vo.setFormInstanceId(bind.getFormInstanceId());
+ vo.setProcessInstanceId(bind.getProcessInstanceId());
+ return vo;
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public Long resolveOrCreateFormInstanceId(String bizType, Long bizId, boolean forceNew) {
+ AssertUtils.notNull(bizId, "业务id不能为空");
+ MkFormBind current = this.getCurrentBind(bizType, bizId);
+ if (current == null) {
+ return this.createNewBind(bizType, bizId);
+ }
+ BusinessProcess businessProcess = businessProcessMapper.selectOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getBizId, bizId)
+ .orderByDesc(BusinessProcess::getUpdateTime)
+ .last("limit 1")
+ );
+ boolean approvedPass = businessProcess != null
+ && Objects.equals(ApproveStatusEnum.APPROVED.getValue(), businessProcess.getApproveStatus());
+ boolean completed = businessProcess != null
+ && businessProcess.getIsCompleted() != null
+ && businessProcess.getIsCompleted() == 1;
+ if (forceNew || approvedPass || completed) {
+ log.info("生成新表单实例 forceNew={} approvedPass={} completed={} bizType={} bizId={} oldFormInstanceId={}",
+ forceNew, approvedPass, completed, bizType, bizId, current.getFormInstanceId());
+ this.lambdaUpdate()
+ .eq(MkFormBind::getBizId, bizId)
+ .eq(MkFormBind::getIsCurrent, 1)
+ .set(MkFormBind::getIsCurrent, 0)
+ .set(MkFormBind::getUpdateTime, new Date())
+ .update();
+ return this.createNewBind(bizType, bizId);
+ }
+ log.info("复用当前表单实例 bizType={} bizId={} formInstanceId={}",
+ bizType, bizId, current.getFormInstanceId());
+ if (StringUtil.isNotBlank(bizType) && StringUtil.isBlank(current.getBizType())) {
+ current.setBizType(bizType);
+ current.setUpdateTime(new Date());
+ this.updateById(current);
+ }
+ return current.getFormInstanceId();
+ }
+
+ @Override
+ public void updateProcessInstanceId(Long formInstanceId, String processInstanceId) {
+ AssertUtils.notNull(formInstanceId, "表单实例id不能为空");
+ this.lambdaUpdate()
+ .eq(MkFormBind::getFormInstanceId, formInstanceId)
+ .set(MkFormBind::getProcessInstanceId, processInstanceId)
+ .set(MkFormBind::getUpdateTime, new Date())
+ .update();
+ }
+
+ private Long createNewBind(String bizType, Long bizId) {
+ Long formInstanceId = IdWorker.getId();
+ MkFormBind bind = new MkFormBind();
+ bind.setId(IdWorker.getId());
+ bind.setTenantId(StringUtil.isBlank(AuthUtil.getTenantId()) ? "000000" : AuthUtil.getTenantId());
+ bind.setBizType(StringUtil.isBlank(bizType) ? "" : bizType);
+ bind.setBizId(bizId);
+ bind.setFormInstanceId(formInstanceId);
+ bind.setIsCurrent(1);
+ bind.setCreateTime(new Date());
+ bind.setUpdateTime(new Date());
+ boolean saved = this.save(bind);
+ if (!saved) {
+ throw new ServiceException("保存表单实例绑定失败");
+ }
+ log.info("创建表单实例绑定 bizType={} bizId={} formInstanceId={}", bizType, bizId, formInstanceId);
+ return formInstanceId;
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/AirportMasterController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/AirportMasterController.java
index 59950862..355aaee9 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/AirportMasterController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/AirportMasterController.java
@@ -157,10 +157,21 @@ public class AirportMasterController extends BladeController {
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
return R.fail("请上传 .xls,.xlsx 标准格式文件");
}
- List 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("操作成功");
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/CargoTypeController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/CargoTypeController.java
index 745968b5..cf73d38c 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/CargoTypeController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/CargoTypeController.java
@@ -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 data = ExcelUtil.read(file, CargoTypeExcel.class);
- List 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("导入数据成功");
}
+ /**
+ * 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
+ *
+ * 失败数据仅标红出错单元格与失败原因列,表头保持默认样式。
+ */
+ private void exportFailure(HttpServletResponse response, List> failureList) {
+ ImportFailureExcelUtil.exportFailureReasonOnly(response, "货物类型导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CargoTypeExcel.class);
+ }
+
/**
* 导出货物类型
*/
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/CurrencyController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/CurrencyController.java
index 4bc43d95..8b6b6c44 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/CurrencyController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/CurrencyController.java
@@ -149,10 +149,28 @@ public class CurrencyController extends BladeController {
@ApiOperationSupport(order = 7)
@Operation(summary = "导入币种汇率", description = "传入excel")
public R importCurrency(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java
index 180dc550..67dbf494 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java
@@ -44,11 +44,10 @@ import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
-import org.springblade.system.cache.DictCache;
+import org.springblade.system.cache.DictBizCache;
import org.springblade.system.cache.UserCache;
import org.springblade.system.pojo.entity.Dept;
import org.springblade.system.pojo.entity.User;
-import org.springblade.system.pojo.enums.DictEnum;
import org.springblade.system.pojo.vo.DeptVO;
import org.springblade.system.pojo.vo.OaOrgSyncPageVO;
import org.springblade.system.pojo.vo.UserVO;
@@ -156,7 +155,7 @@ public class DeptController extends BladeController {
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
// 返回懒加载树更新节点所需字段
Kv kv = Kv.create().set("id", String.valueOf(dept.getId())).set("tenantId", dept.getTenantId())
- .set("deptCategoryName", DictCache.getValue(DictEnum.ORG_CATEGORY, dept.getDeptCategory()));
+ .set("deptCategoryName", DictBizCache.getValue("organization_type", dept.getDeptCategory()));
return R.data(kv);
}
return R.fail("操作失败");
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java
index b8eae386..d70330bd 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java
@@ -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 data = ExcelUtil.read(file, FeeItemExcel.class);
- List 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("导入数据成功");
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java
index 0639b52d..8c19eedc 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java
@@ -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;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RailwayStationController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RailwayStationController.java
index 7bc1f8c2..34149e9d 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RailwayStationController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RailwayStationController.java
@@ -160,10 +160,21 @@ public class RailwayStationController extends BladeController {
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
return R.fail("请上传 .xls,.xlsx 标准格式文件");
}
- List 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("操作成功");
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java
index 0de1e680..9f0ec270 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java
@@ -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 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("操作成功");
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/AirportMasterExportExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/AirportMasterExportExcel.java
index e237be36..d2918d23 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/AirportMasterExportExcel.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/AirportMasterExportExcel.java
@@ -102,6 +102,9 @@ public class AirportMasterExportExcel implements Serializable {
@ExcelProperty("备注")
private String remark;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("更新人")
private String updateUserName;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java
index b59573d5..998f3aac 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java
@@ -76,7 +76,7 @@ public class CurrencyExcel implements Serializable {
@ExcelProperty("备注")
private String remark;
- @ExcelProperty
+ @ExcelIgnore
private String errorMessage;
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExportExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExportExcel.java
index aedffeb5..593992ad 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExportExcel.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/FeeItemExportExcel.java
@@ -50,6 +50,9 @@ public class FeeItemExportExcel implements Serializable {
@ExcelProperty("组织")
private String createDeptName;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("更新人")
private String updateUserName;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/PortTerminalExportExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/PortTerminalExportExcel.java
index b1c6471b..5454526d 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/PortTerminalExportExcel.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/PortTerminalExportExcel.java
@@ -80,6 +80,9 @@ public class PortTerminalExportExcel implements Serializable {
@ExcelProperty("备注")
private String remark;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("更新人")
private String updateUserName;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/RailwayStationExportExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/RailwayStationExportExcel.java
index b745a80c..ddaf8230 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/RailwayStationExportExcel.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/RailwayStationExportExcel.java
@@ -92,6 +92,9 @@ public class RailwayStationExportExcel implements Serializable {
@ExcelProperty("备注")
private String remark;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("更新人")
private String updateUserName;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.java
index 55661a6a..6176e472 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.java
@@ -26,6 +26,7 @@
package org.springblade.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
import org.springblade.system.pojo.entity.Dept;
import org.springblade.system.pojo.vo.DeptVO;
@@ -66,6 +67,18 @@ public interface DeptMapper extends BaseMapper {
*/
List lazyTree(String tenantId, Long parentId);
+ /**
+ * 按组织 ID 集合查询树节点(用于数据权限过滤后的根节点回显)
+ *
+ * @param tenantId 租户ID,空表示不限
+ * @param ids 需要返回的组织 ID
+ * @param childIds 计算 hasChildren 时允许计入的子组织 ID;为空则 hasChildren 恒为 false
+ * @return 树节点列表
+ */
+ List treeNodesByIds(@Param("tenantId") String tenantId,
+ @Param("ids") List ids,
+ @Param("childIds") List childIds);
+
/**
* 获取部门名
*
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml
index baef84dc..e125b43b 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml
@@ -12,6 +12,7 @@
+
@@ -32,6 +33,7 @@
+
@@ -115,6 +117,47 @@
ORDER BY dept.sort
+
+ SELECT
+ dept.id,
+ dept.parent_id,
+ dept.dept_name AS title,
+ dept.id AS "value",
+ dept.id AS "key",
+ (
+ SELECT
+ CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
+ FROM
+ blade_dept child
+ WHERE
+ child.parent_id = dept.id
+ AND child.is_deleted = 0
+
+
+ AND child.id IN
+
+ #{childId}
+
+
+
+ AND 1 = 0
+
+
+ ) AS "has_children"
+ FROM
+ blade_dept dept
+ WHERE
+ dept.is_deleted = 0
+ AND dept.id IN
+
+ #{id}
+
+
+ AND dept.tenant_id = #{tenantId}
+
+ ORDER BY dept.sort, dept.id
+
+
SELECT
dept_name
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java
index cde043fe..cee1baf1 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java
@@ -68,10 +68,10 @@ public interface IDeptService extends IService {
List lazyList(String tenantId, Long parentId, Map param);
/**
- * 树形结构(含超管判定 + 当前租户隔离)
+ * 树形结构(含超管判定 + 当前租户隔离 + 用户组织数据权限)
*
* 仅超级管理员可指定任意 tenantId 查询;其他用户传入的 tenantId 一律被忽略,
- * 强制使用当前会话租户。
+ * 强制使用当前会话租户,并按用户「数据权限范围」「数据层级范围」过滤可见组织。
*
* @param tenantId 入参 tenantId(仅超管生效)
* @return 部门树
@@ -79,9 +79,11 @@ public interface IDeptService extends IService {
List tree(String tenantId);
/**
- * 懒加载树形结构(含超管判定 + 当前租户隔离)
+ * 懒加载树形结构(含超管判定 + 当前租户隔离 + 用户组织数据权限)
*
* 仅超级管理员可指定任意 tenantId,空值表示查询全部;其他用户强制使用当前会话租户。
+ * 非超管按用户「数据权限范围」「数据层级范围」过滤可见组织:
+ * 仅所属组织 / 自定义组织,并可选择是否包含无限下级。
*
* @param tenantId 入参 tenantId(仅超管生效)
* @param parentId 父级部门ID
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/AirportMasterServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/AirportMasterServiceImpl.java
index 55bdeb12..77679157 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/AirportMasterServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/AirportMasterServiceImpl.java
@@ -127,7 +127,7 @@ public class AirportMasterServiceImpl extends BaseServiceImpl importAirportMaster(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
List errorList = new ArrayList<>();
List airportMasterList = new ArrayList<>();
@@ -160,8 +160,9 @@ public class AirportMasterServiceImpl extends BaseServiceImpl importCargoType(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
+ // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
+ // 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
+ // Spring 会照常提交事务,@Transactional 起不到回滚作用。
List 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 importCurrency(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
+ // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
+ // 注意:循环内除 save 外还有 rebuildEnabledValidity 这个派生写——
+ // 若吞掉异常正常 return,主表数据和派生的有效期区间都会落库;
+ // 必须抛异常让两者一起回滚,否则会出现「主表回滚了、有效期区间
+ // 却按失败批次算过」的脏数据。
List errorList = new ArrayList<>();
int successCount = 0;
for (int index = 0; index < data.size(); index++) {
@@ -140,6 +146,11 @@ public class CurrencyServiceImpl extends BaseServiceImpl implements ID
private static final String IAM_SYNC_TENANT_ID = "000000";
private static final Long IAM_SYNC_PARENT_ID = 1123598813738675201L;
private static final HttpClient IAM_HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
+ /** 数据权限范围:仅所属组织 */
+ private static final int DATA_SCOPE_OWN = 1;
+ /** 数据权限范围:全部组织 */
+ private static final int DATA_SCOPE_ALL = 2;
+ /** 数据权限范围:自定义 */
+ private static final int DATA_SCOPE_CUSTOM = 3;
+ /** 数据层级范围:包含下级 */
+ private static final int DATA_LEVEL_INCLUDE_CHILDREN = 1;
+ /** 数据层级范围:仅本级 */
+ private static final int DATA_LEVEL_CURRENT_ONLY = 2;
private final IUserService userService;
+ private final UserDataScopeMapper userDataScopeMapper;
private final IamSyncProperties iamSyncProperties;
private final ObjectMapper objectMapper;
@@ -144,7 +157,11 @@ public class DeptServiceImpl extends ServiceImpl implements ID
String resolvedTenantId = AuthUtil.isAdministrator()
? Func.toStrWithEmpty(tenantId, AuthUtil.getTenantId())
: AuthUtil.getTenantId();
- return ForestNodeMerger.merge(baseMapper.tree(resolvedTenantId));
+ DeptDataScopeFilter scopeFilter = resolveDeptDataScopeFilter();
+ if (scopeFilter == null || scopeFilter.allDept()) {
+ return ForestNodeMerger.merge(baseMapper.tree(resolvedTenantId));
+ }
+ return buildScopedDeptTree(resolvedTenantId, scopeFilter);
}
@Override
@@ -153,7 +170,142 @@ public class DeptServiceImpl extends ServiceImpl implements ID
String resolvedTenantId = AuthUtil.isAdministrator()
? Func.toStr(tenantId, StringPool.EMPTY)
: AuthUtil.getTenantId();
- return ForestNodeMerger.merge(baseMapper.lazyTree(resolvedTenantId, parentId));
+ Long resolvedParentId = parentId == null ? BladeConstant.TOP_PARENT_ID : parentId;
+ DeptDataScopeFilter scopeFilter = resolveDeptDataScopeFilter();
+ if (scopeFilter == null || scopeFilter.allDept()) {
+ return ForestNodeMerger.merge(baseMapper.lazyTree(resolvedTenantId, resolvedParentId));
+ }
+ Set allowedIds = scopeFilter.allowedDeptIds();
+ if (Func.isEmpty(allowedIds)) {
+ return Collections.emptyList();
+ }
+ boolean includeChildren = scopeFilter.includeChildren();
+ List childIdsForHasChildren = includeChildren ? new ArrayList<>(allowedIds) : Collections.emptyList();
+ // 根层:返回「可见根」——权限集合中父节点不在集合内的组织,避免用户看不到深层权限节点。
+ if (Objects.equals(resolvedParentId, BladeConstant.TOP_PARENT_ID)) {
+ List visibleRootIds = resolveVisibleRootIds(allowedIds);
+ if (Func.isEmpty(visibleRootIds)) {
+ return Collections.emptyList();
+ }
+ return ForestNodeMerger.merge(baseMapper.treeNodesByIds(resolvedTenantId, visibleRootIds, childIdsForHasChildren));
+ }
+ // 非根层:无权限的父节点不允许展开;仅本级时不允许继续下钻。
+ if (!includeChildren || !allowedIds.contains(resolvedParentId)) {
+ return Collections.emptyList();
+ }
+ List children = baseMapper.lazyTree(resolvedTenantId, resolvedParentId);
+ List filteredChildIds = children.stream()
+ .map(DeptVO::getId)
+ .filter(allowedIds::contains)
+ .toList();
+ if (Func.isEmpty(filteredChildIds)) {
+ return Collections.emptyList();
+ }
+ return ForestNodeMerger.merge(baseMapper.treeNodesByIds(resolvedTenantId, filteredChildIds, childIdsForHasChildren));
+ }
+
+ /**
+ * 按数据权限构建完整组织树(用于 /dept/tree)。
+ */
+ private List buildScopedDeptTree(String tenantId, DeptDataScopeFilter scopeFilter) {
+ Set allowedIds = scopeFilter.allowedDeptIds();
+ if (Func.isEmpty(allowedIds)) {
+ return Collections.emptyList();
+ }
+ List childIdsForHasChildren = scopeFilter.includeChildren()
+ ? new ArrayList<>(allowedIds)
+ : Collections.emptyList();
+ return ForestNodeMerger.merge(baseMapper.treeNodesByIds(tenantId, new ArrayList<>(allowedIds), childIdsForHasChildren));
+ }
+
+ /**
+ * 计算懒加载首层可见根:父级不在权限集合内的组织。
+ */
+ private List resolveVisibleRootIds(Set allowedIds) {
+ return listByIds(allowedIds).stream()
+ .filter(dept -> dept.getParentId() == null
+ || Objects.equals(dept.getParentId(), BladeConstant.TOP_PARENT_ID)
+ || !allowedIds.contains(dept.getParentId()))
+ .sorted(Comparator.comparing(Dept::getSort, Comparator.nullsLast(Integer::compareTo))
+ .thenComparing(Dept::getId, Comparator.nullsLast(Long::compareTo)))
+ .map(Dept::getId)
+ .toList();
+ }
+
+ /**
+ * 解析当前登录用户的组织树数据权限。
+ *
+ * 超管或「全部组织」返回 allDept=true;其余按所属组织/自定义组织,并结合「包含下级/仅本级」展开。
+ *
+ * @return 过滤上下文;无法识别用户时按空权限处理
+ */
+ private DeptDataScopeFilter resolveDeptDataScopeFilter() {
+ if (AuthUtil.isAdministrator()) {
+ return DeptDataScopeFilter.all();
+ }
+ Long userId = AuthUtil.getUserId();
+ if (Func.isEmpty(userId) || userId <= 0) {
+ return DeptDataScopeFilter.empty();
+ }
+ User user = userService.getById(userId);
+ if (Func.isEmpty(user)) {
+ return DeptDataScopeFilter.empty();
+ }
+ Integer dataScopeRange = user.getDataScopeRange() == null ? DATA_SCOPE_OWN : user.getDataScopeRange();
+ Integer dataLevelRange = user.getDataLevelRange() == null ? DATA_LEVEL_INCLUDE_CHILDREN : user.getDataLevelRange();
+ if (Objects.equals(dataScopeRange, DATA_SCOPE_ALL)) {
+ return DeptDataScopeFilter.all();
+ }
+ List baseDeptIds;
+ if (Objects.equals(dataScopeRange, DATA_SCOPE_CUSTOM)) {
+ baseDeptIds = userDataScopeMapper.selectList(Wrappers.lambdaQuery()
+ .eq(UserDataScope::getUserId, userId))
+ .stream()
+ .map(UserDataScope::getDeptId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .toList();
+ } else {
+ // 仅所属组织:优先用户档案中的所属组织,其次当前会话组织。
+ baseDeptIds = Func.toLongList(user.getDeptId());
+ if (Func.isEmpty(baseDeptIds)) {
+ baseDeptIds = Func.toLongList(AuthUtil.getDeptId());
+ }
+ }
+ if (Func.isEmpty(baseDeptIds)) {
+ return DeptDataScopeFilter.empty();
+ }
+ boolean includeChildren = !Objects.equals(dataLevelRange, DATA_LEVEL_CURRENT_ONLY);
+ LinkedHashSet allowedDeptIds = new LinkedHashSet<>();
+ for (Long deptId : baseDeptIds) {
+ if (Func.isEmpty(deptId) || deptId <= 0) {
+ continue;
+ }
+ if (includeChildren) {
+ List childIds = SysCache.getDeptChildIds(deptId);
+ if (Func.isNotEmpty(childIds)) {
+ allowedDeptIds.addAll(childIds);
+ } else {
+ allowedDeptIds.add(deptId);
+ }
+ } else {
+ allowedDeptIds.add(deptId);
+ }
+ }
+ return new DeptDataScopeFilter(false, includeChildren, allowedDeptIds);
+ }
+
+ /**
+ * 组织懒加载树的数据权限过滤上下文。
+ */
+ private record DeptDataScopeFilter(boolean allDept, boolean includeChildren, Set allowedDeptIds) {
+ private static DeptDataScopeFilter all() {
+ return new DeptDataScopeFilter(true, true, Collections.emptySet());
+ }
+
+ private static DeptDataScopeFilter empty() {
+ return new DeptDataScopeFilter(false, false, Collections.emptySet());
+ }
}
@Override
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java
index a3825a46..d57d0fa1 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java
@@ -106,8 +106,11 @@ public class FeeItemServiceImpl extends BaseServiceImpl
@Transactional(rollbackFor = Exception.class)
public List importFeeItem(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
+ // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
+ // 注意不能只往 failureList 里塞数据就 return——异常被吞掉后方法会正常返回,
+ // Spring 会照常提交事务,@Transactional 起不到回滚作用。
List 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
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;
}
@@ -216,6 +228,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl
excel.setCreateDeptName(
Func.isEmpty(feeItem.getCreateDept()) ? "" : SysCache.getDeptName(feeItem.getCreateDept())
);
+ excel.setCreateUserName(UserCache.getUserRealName(feeItem.getCreateUser()));
excel.setUpdateUserName(UserCache.getUserRealName(feeItem.getUpdateUser()));
excel.setStatusName(Objects.equals(feeItem.getStatus(), STATUS_ENABLED) ? "启用" : "停用");
return excel;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java
index 892f4d62..8e96be1d 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java
@@ -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 importPortTerminal(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
List failureList = new ArrayList<>();
@@ -357,6 +357,7 @@ public class PortTerminalServiceImpl extends BaseServiceImpl importRailwayStation(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
List errorList = new ArrayList<>();
List railwayStationList = new ArrayList<>();
@@ -165,8 +165,9 @@ public class RailwayStationServiceImpl extends BaseServiceImpl impleme
@Override
public boolean submit(Region region) {
+ return submit(region, true);
+ }
+
+ /**
+ * 提交区划,clearCache 控制是否在保存成功后立即清理惰性树缓存。
+ *
+ * 批量导入路径传 false:导入内部会收集全部错误,只有整批成功才统一清缓存;
+ * 若逐行清缓存,失败批次中已成功行会把缓存清掉/重算成中间状态,
+ * 与随后的事务回滚形成「库回滚了、缓存却是脏的」。
+ */
+ private boolean submit(Region region, boolean clearCache) {
Date now = new Date();
Long currentUserId = AuthUtil.getUserId();
boolean isNew = StringUtil.isBlank(region.getOriginalCode());
@@ -98,6 +110,9 @@ public class RegionServiceImpl extends ServiceImpl impleme
validateUniqueCode(region);
// 设置祖区划编号
Region parent = getByCode(region.getParentCode());
+ if (level != null && level > COUNTRY_LEVEL && (Func.isEmpty(parent) || Func.isEmpty(parent.getCode()))) {
+ throw new ServiceException("父区划不存在,请重新选择父区划");
+ }
if (Func.isNotEmpty(parent) && Func.isNotEmpty(parent.getCode())) {
String ancestors = parent.getRegionLevel() != null && parent.getRegionLevel() == COUNTRY_LEVEL
? parent.getCode()
@@ -138,7 +153,7 @@ public class RegionServiceImpl extends ServiceImpl 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 +204,15 @@ public class RegionServiceImpl extends ServiceImpl impleme
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public List importRegion(List data, Boolean isCovered) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
+ // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
+ // 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回,
+ // Spring 会照常提交事务,@Transactional 起不到回滚作用。
List errorList = new ArrayList<>();
- boolean cacheChanged = false;
for (int index = 0; index < data.size(); index++) {
RegionExcel excel = data.get(index);
try {
@@ -210,15 +228,20 @@ public class RegionServiceImpl extends ServiceImpl 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;
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java
index ace47304..e356b5f8 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java
@@ -1130,22 +1130,22 @@ public class UserServiceImpl extends BaseServiceImpl implement
@Override
public void decryptPhone(IPhone user) {
-// if (user == null) {
-// return;
-// }
-// if (user.getPhone() != null && user.getPhone().length() > 20) {
-// user.setPhone(SM2Util.decrypt(user.getPhone(), properties.getPublicKey(), properties.getPrivateKey()));
-// }
+ if (user == null) {
+ return;
+ }
+ if (user.getPhone() != null && user.getPhone().length() > 20) {
+ user.setPhone(SM2Util.decrypt(user.getPhone(), properties.getPublicKey(), properties.getPrivateKey()));
+ }
}
@Override
public void encryptPhone(IPhone user) {
-// if (user == null) {
-// return;
-// }
-// if(StringUtil.isNotBlank(user.getPhone())) {
-// user.setPhone(Hex.toHexString(SM2Util.encrypt(user.getPhone(),properties.getPublicKey())));
-// }
+ if (user == null) {
+ return;
+ }
+ if (StringUtil.isNotBlank(user.getPhone())) {
+ user.setPhone(Hex.toHexString(SM2Util.encrypt(user.getPhone(), properties.getPublicKey())));
+ }
}
private void validatePlainPassword(String password) {
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/AirportMasterWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/AirportMasterWrapper.java
index 4aa8f112..03f94882 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/AirportMasterWrapper.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/AirportMasterWrapper.java
@@ -48,6 +48,7 @@ public class AirportMasterWrapper extends BaseEntityWrapper {
+ /**
+ * 组织类型业务字典编码
+ */
+ private static final String ORGANIZATION_TYPE_CODE = "organization_type";
+
public static DeptWrapper build() {
return new DeptWrapper();
}
@@ -60,7 +64,7 @@ public class DeptWrapper extends BaseEntityWrapper {
Dept parent = SysCache.getDept(dept.getParentId());
deptVO.setParentName(parent.getDeptName());
}
- String category = DictCache.getValue(DictEnum.ORG_CATEGORY, dept.getDeptCategory());
+ String category = DictBizCache.getValue(ORGANIZATION_TYPE_CODE, dept.getDeptCategory());
deptVO.setDeptCategoryName(category);
return deptVO;
}
@@ -69,7 +73,7 @@ public class DeptWrapper extends BaseEntityWrapper {
public List listNodeVO(List list) {
List collect = list.stream().map(dept -> {
DeptVO deptVO = BeanUtil.copyProperties(dept, DeptVO.class);
- String category = DictCache.getValue(DictEnum.ORG_CATEGORY, dept.getDeptCategory());
+ String category = DictBizCache.getValue(ORGANIZATION_TYPE_CODE, dept.getDeptCategory());
Objects.requireNonNull(deptVO).setDeptCategoryName(category);
return deptVO;
}).collect(Collectors.toList());
@@ -78,7 +82,7 @@ public class DeptWrapper extends BaseEntityWrapper {
public List listNodeLazyVO(List list) {
List collect = list.stream().peek(dept -> {
- String category = DictCache.getValue(DictEnum.ORG_CATEGORY, dept.getDeptCategory());
+ String category = DictBizCache.getValue(ORGANIZATION_TYPE_CODE, dept.getDeptCategory());
Objects.requireNonNull(dept).setDeptCategoryName(category);
}).collect(Collectors.toList());
return ForestNodeMerger.merge(collect);
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java
index 2d59255a..b1dbfdaa 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/FeeItemWrapper.java
@@ -52,6 +52,7 @@ public class FeeItemWrapper extends BaseEntityWrapper {
feeItemVO.setCreateDeptName(
Func.isEmpty(feeItem.getCreateDept()) ? "" : SysCache.getDeptName(feeItem.getCreateDept())
);
+ feeItemVO.setCreateUserName(UserCache.getUserRealName(feeItem.getCreateUser()));
feeItemVO.setUpdateUserName(UserCache.getUserRealName(feeItem.getUpdateUser()));
return feeItemVO;
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/PortTerminalWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/PortTerminalWrapper.java
index af2f698c..a37ec11e 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/PortTerminalWrapper.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/PortTerminalWrapper.java
@@ -48,6 +48,7 @@ public class PortTerminalWrapper extends BaseEntityWrapper 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/AnnualInspectionRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/AnnualInspectionRecordController.java
index e97fa2d1..f598e3c2 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/AnnualInspectionRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/AnnualInspectionRecordController.java
@@ -125,10 +125,28 @@ public class AnnualInspectionRecordController extends BladeController {
@ApiOperationSupport(order = 6)
@Operation(summary = "导入年检记录", description = "传入excel")
public R importAnnualInspectionRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java
index 37d37506..67125a3c 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java
@@ -33,11 +33,9 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.excel.util.ExcelUtil;
-import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
-import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
@@ -46,6 +44,7 @@ import org.springblade.transport.pojo.entity.CommonAddress;
import org.springblade.transport.pojo.vo.CommonAddressRemoveResultVO;
import org.springblade.transport.pojo.vo.CommonAddressVO;
import org.springblade.transport.service.ICommonAddressService;
+import org.springblade.transport.support.TransportBusinessSupport;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -54,7 +53,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
-import java.util.Objects;
/**
* 常用地址 控制器
@@ -164,15 +162,9 @@ public class CommonAddressController extends BladeController {
if (Func.isNotEmpty(ids)) {
queryWrapper.in(CommonAddress::getId, Func.toLongList(ids));
}
- if (Objects.equals(commonAddress.getAllDept(), 1)) {
- if (!AuthUtil.isAdministrator()) {
- throw new ServiceException("无权导出全部组织常用地址");
- }
- } else {
- queryWrapper.eq(CommonAddress::getDeptId, currentDeptId());
- }
- if (Objects.equals(commonAddress.getAllDept(), 1) && Func.isNotEmpty(commonAddress.getDeptId())) {
- queryWrapper.eq(CommonAddress::getDeptId, commonAddress.getDeptId());
+ List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonAddress.getAllDept(), commonAddress.getDeptId(), "常用地址");
+ if (Func.isNotEmpty(deptIdList)) {
+ queryWrapper.eq(CommonAddress::getDeptId, deptIdList.get(0));
}
if (Func.isNotEmpty(commonAddress.getAddressName())) {
queryWrapper.like(CommonAddress::getAddressName, commonAddress.getAddressName().trim());
@@ -198,12 +190,4 @@ public class CommonAddressController extends BladeController {
return queryWrapper;
}
- private Long currentDeptId() {
- Long deptId = Func.firstLong(AuthUtil.getDeptId());
- if (Func.isEmpty(deptId) || deptId <= 0) {
- throw new ServiceException("当前用户所属组织为空,无法导出常用地址");
- }
- return deptId;
- }
-
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonCargoController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonCargoController.java
index 34b54fa5..7cc90b6e 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonCargoController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonCargoController.java
@@ -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 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("导入数据成功");
}
+ /**
+ * 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
+ *
+ * 失败数据仅标红出错单元格与失败原因列,表头保持默认样式。
+ */
+ private void exportFailure(HttpServletResponse response, List> failureList) {
+ ImportFailureExcelUtil.exportFailureReasonOnly(response, "常用货物导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CommonCargoExcel.class);
+ }
+
@GetMapping("/export-template")
@ApiOperationSupport(order = 7)
@Operation(summary = "导出模板")
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonRouteController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonRouteController.java
index 61966056..98cf2900 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonRouteController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonRouteController.java
@@ -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 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("导入数据成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CreditScoreQuantificationController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CreditScoreQuantificationController.java
index 59c404ff..86e93d7b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CreditScoreQuantificationController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CreditScoreQuantificationController.java
@@ -138,10 +138,28 @@ public class CreditScoreQuantificationController extends BladeController {
@ApiOperationSupport(order = 7)
@Operation(summary = "导入评分量化表", description = "传入excel")
public R importCreditScoreQuantification(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java
index 3c44fd00..d0998aa3 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java
@@ -38,6 +38,7 @@ import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
@@ -220,10 +221,12 @@ public class CustomerArchiveController extends BladeController {
LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery()
.eq(CustomerArchive::getIsDeleted, 0)
.orderByDesc(CustomerArchive::getCreateTime);
- Long userId = AuthUtil.getUserId();
- queryWrapper.and(wrapper -> wrapper
- .exists("SELECT 1 FROM blade_user_customer_scope bucs WHERE bucs.user_id = " + userId
- + " AND bucs.customer_id = blade_customer_archive.id"));
+ if (!TransportBusinessSupport.isAdministrator()) {
+ Long userId = AuthUtil.getUserId();
+ queryWrapper.and(wrapper -> wrapper
+ .exists("SELECT 1 FROM blade_user_customer_scope bucs WHERE bucs.user_id = " + userId
+ + " AND bucs.customer_id = blade_customer_archive.id"));
+ }
if (Func.isNotEmpty(ids)) {
queryWrapper.in(CustomerArchive::getId, Func.toLongList(ids));
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java
index 1df02ea9..90717a31 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java
@@ -30,12 +30,16 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.secure.constant.AuthConstant;
import org.springblade.core.tenant.annotation.TenantIgnore;
+import org.springblade.core.tool.api.FR;
import org.springblade.core.tool.api.R;
+import org.springblade.process.feign.IBusinessProcessClient;
+import org.springblade.process.pojo.vo.MkFormBindVO;
import org.springblade.transport.pojo.vo.CustomerArchiveVO;
import org.springblade.transport.pojo.vo.CustomerChangeRecordVO;
import org.springblade.transport.service.ICustomerArchiveService;
@@ -65,15 +69,34 @@ public class CustomerArchivePublicController {
private final ICustomerArchiveService customerArchiveService;
private final CustomerArchivePublicProcessService customerArchivePublicProcessService;
+ private final IBusinessProcessClient businessProcessClient;
/**
* 公开详情
*/
@GetMapping("/detail")
@ApiOperationSupport(order = 1)
- @Operation(summary = "公开详情", description = "传入id,无需登录")
- public R detail(@Parameter(description = "主键", required = true) @RequestParam Long id) {
- return R.data(customerArchiveService.publicDetail(id));
+ @Operation(summary = "公开详情", description = "传入表单实例id,无需登录;内部反查业务主键后加载详情")
+ public R detail(@Parameter(description = "表单实例id", required = true) @RequestParam Long id) {
+ return R.data(customerArchiveService.publicDetail(resolveBizId(id)));
+ }
+
+ /**
+ * 公开页传入的 id 为 formInstanceId,反查业务主键;查不到时兼容历史(id 即 bizId)
+ */
+ private Long resolveBizId(Long formInstanceId) {
+ if (formInstanceId == null) {
+ throw new ServiceException("表单实例id不能为空");
+ }
+ try {
+ FR result = businessProcessClient.resolveByFormInstanceId(formInstanceId);
+ if (result != null && result.isSuccess() && result.getData() != null && result.getData().getBizId() != null) {
+ return result.getData().getBizId();
+ }
+ } catch (Exception e) {
+ log.warn("按表单实例id解析客商业务主键失败,回退按业务主键查询 formInstanceId={}", formInstanceId, e);
+ }
+ return formInstanceId;
}
/**
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EquipmentLedgerController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EquipmentLedgerController.java
index c1afe16e..c4554e4b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EquipmentLedgerController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EquipmentLedgerController.java
@@ -90,10 +90,28 @@ public class EquipmentLedgerController extends BladeController {
@ApiOperationSupport(order = 6)
@Operation(summary = "导入设备台账")
public R importEquipmentLedger(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EtcRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EtcRecordController.java
index 8258b7d1..c67992b3 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EtcRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/EtcRecordController.java
@@ -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 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InsuranceRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InsuranceRecordController.java
index 4dc31536..8c4f414a 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InsuranceRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InsuranceRecordController.java
@@ -129,10 +129,28 @@ public class InsuranceRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入保险记录", description = "传入excel")
public R importInsuranceRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenancePlanController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenancePlanController.java
index b279ccf4..a77493c6 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenancePlanController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenancePlanController.java
@@ -128,10 +128,28 @@ public class MaintenancePlanController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入保养记录", description = "传入excel")
public R importMaintenancePlan(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenanceRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenanceRecordController.java
index e24f851f..42f12239 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenanceRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MaintenanceRecordController.java
@@ -128,10 +128,28 @@ public class MaintenanceRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入维修记录", description = "传入excel")
public R importMaintenanceRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MileageRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MileageRecordController.java
index d46141cc..68dbf53a 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MileageRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MileageRecordController.java
@@ -116,10 +116,28 @@ public class MileageRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入里程记录", description = "传入excel")
public R importMileageRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java
index d8f0c354..6aac709b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java
@@ -7,10 +7,14 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.secure.constant.AuthConstant;
import org.springblade.core.tenant.annotation.TenantIgnore;
+import org.springblade.core.tool.api.FR;
import org.springblade.core.tool.api.R;
+import org.springblade.process.feign.IBusinessProcessClient;
+import org.springblade.process.pojo.vo.MkFormBindVO;
import org.springblade.transport.mk.MkProcessMessageService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -35,13 +39,33 @@ import java.util.Map;
public class MkProcessPublicController {
private final MkProcessMessageService mkProcessMessageService;
+ private final IBusinessProcessClient businessProcessClient;
@GetMapping("/{bizType}/detail")
@ApiOperationSupport(order = 1)
- @Operation(summary = "公开详情", description = "无需登录")
+ @Operation(summary = "公开详情", description = "无需登录;id 为 MK 表单实例id,内部反查业务主键后加载详情")
public R detail(@PathVariable String bizType,
- @Parameter(description = "主键", required = true) @RequestParam Long id) {
- return R.data(mkProcessMessageService.handler(bizType).publicDetail(id));
+ @Parameter(description = "表单实例id", required = true) @RequestParam Long id) {
+ Long bizId = resolveBizId(id);
+ return R.data(mkProcessMessageService.handler(bizType).publicDetail(bizId));
+ }
+
+ /**
+ * 公开页传入的 id 为 formInstanceId,反查业务主键;查不到时兼容历史(id 即 bizId)
+ */
+ private Long resolveBizId(Long formInstanceId) {
+ if (formInstanceId == null) {
+ throw new ServiceException("表单实例id不能为空");
+ }
+ try {
+ FR result = businessProcessClient.resolveByFormInstanceId(formInstanceId);
+ if (result != null && result.isSuccess() && result.getData() != null && result.getData().getBizId() != null) {
+ return result.getData().getBizId();
+ }
+ } catch (Exception e) {
+ log.warn("按表单实例id解析业务主键失败,回退按业务主键查询 formInstanceId={}", formInstanceId, e);
+ }
+ return formInstanceId;
}
@PostMapping("/{bizType}/process-message")
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OilElectricRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OilElectricRecordController.java
index ee3caae1..f2b37b13 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OilElectricRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OilElectricRecordController.java
@@ -94,10 +94,28 @@ public class OilElectricRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入油电记录", description = "传入excel")
public R importOilElectricRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OtherExpenseRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OtherExpenseRecordController.java
index 3ea8fb5a..d6b70a5a 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OtherExpenseRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/OtherExpenseRecordController.java
@@ -94,10 +94,28 @@ public class OtherExpenseRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入其他费用记录", description = "传入excel")
public R importOtherExpenseRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java
index c0cb3d30..68381d8c 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java
@@ -51,7 +51,9 @@ import org.springblade.transport.pojo.entity.VoucherManage;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.ProcessConfigVO;
+import org.springblade.transport.pojo.vo.VoucherFolderVO;
import org.springblade.transport.service.IProcessConfigService;
+import org.springblade.transport.service.IVoucherManageService;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
@@ -80,6 +82,7 @@ import java.util.concurrent.TimeUnit;
public class ProcessConfigController extends BladeController {
private final IProcessConfigService processConfigService;
+ private final IVoucherManageService voucherManageService;
private final WaybillMapper waybillMapper;
private final VoucherFileMapper voucherFileMapper;
private final VoucherImageMapper voucherImageMapper;
@@ -88,11 +91,13 @@ public class ProcessConfigController extends BladeController {
@Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}")
private String minioBucketName;
- public ProcessConfigController(IProcessConfigService processConfigService, WaybillMapper waybillMapper,
+ public ProcessConfigController(IProcessConfigService processConfigService, IVoucherManageService voucherManageService,
+ WaybillMapper waybillMapper,
VoucherFileMapper voucherFileMapper, VoucherImageMapper voucherImageMapper,
VoucherManageMapper voucherManageMapper,
ObjectProvider minioClientProvider) {
this.processConfigService = processConfigService;
+ this.voucherManageService = voucherManageService;
this.waybillMapper = waybillMapper;
this.voucherFileMapper = voucherFileMapper;
this.voucherImageMapper = voucherImageMapper;
@@ -128,6 +133,23 @@ public class ProcessConfigController extends BladeController {
return R.data(detail);
}
+ @GetMapping("/voucher-folders")
+ @ApiOperationSupport(order = 2)
+ @Operation(summary = "查询运单执行凭证文件夹")
+ public R> voucherFolders(
+ @Parameter(description = "运单主键", required = true) @RequestParam Long waybillId) {
+ return R.data(voucherManageService.listWaybillFolders(waybillId));
+ }
+
+ @GetMapping("/voucher-folder-detail")
+ @ApiOperationSupport(order = 2)
+ @Operation(summary = "查询运单执行凭证文件夹详情")
+ public R voucherFolderDetail(
+ @Parameter(description = "凭证批次主键", required = true) @RequestParam Long voucherId,
+ @Parameter(description = "车牌号", required = true) @RequestParam String plateNo) {
+ return R.data(voucherManageService.folderDetail(voucherId, plateNo));
+ }
+
@GetMapping("/voucher-images")
@ApiOperationSupport(order = 2)
@Operation(summary = "查询运单已关联凭证图片")
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TireReplacementRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TireReplacementRecordController.java
index d3d5741e..9db40c33 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TireReplacementRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TireReplacementRecordController.java
@@ -128,10 +128,28 @@ public class TireReplacementRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入换胎记录", description = "传入excel")
public R importTireReplacementRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportChangeRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportChangeRecordController.java
index 10848fa9..87200541 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportChangeRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportChangeRecordController.java
@@ -113,10 +113,28 @@ public class TransportChangeRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入变更记录", description = "传入excel")
public R importTransportChangeRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportPlanController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportPlanController.java
index ae64bb22..f8f8fe07 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportPlanController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportPlanController.java
@@ -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 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("导入数据成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportReconciliationController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportReconciliationController.java
index 2b5d7772..70fa983e 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportReconciliationController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/TransportReconciliationController.java
@@ -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,32 @@ public class TransportReconciliationController extends BladeController {
@ApiOperationSupport(order = 6)
@Operation(summary = "导入整车总额外部账单")
public R importVehicle(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
- List rows = ExcelUtil.read(file, VehicleReconciliationExcel.class);
- List> feeItems = VehicleReconciliationFeeReader.read(file);
- for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
- List 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 rows = ExcelUtil.read(file, VehicleReconciliationExcel.class);
+ List> 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.exportOriginalWithFailures(response, "运输对账导入失败明细" + DateUtil.time(),
+ "导入失败明细", file, 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 +161,32 @@ public class TransportReconciliationController extends BladeController {
@ApiOperationSupport(order = 7)
@Operation(summary = "导入货物明细外部账单")
public R importCargo(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
- List rows = ExcelUtil.read(file, CargoReconciliationExcel.class);
- List> feeItems = CargoReconciliationFeeReader.read(file);
- for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
- List 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 rows = ExcelUtil.read(file, CargoReconciliationExcel.class);
+ List> 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.exportOriginalWithFailures(response, "运输对账导入失败明细" + DateUtil.time(),
+ "导入失败明细", file, 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("导入数据成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ViolationRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ViolationRecordController.java
index d3a89443..d9a3e4c4 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ViolationRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ViolationRecordController.java
@@ -127,10 +127,28 @@ public class ViolationRecordController extends BladeController {
@ApiOperationSupport(order = 5)
@Operation(summary = "导入违章记录", description = "传入excel")
public R importViolationRecord(MultipartFile file, HttpServletResponse response) {
- List 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("操作成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java
index 3de34089..e7bc1194 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java
@@ -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;
@@ -53,6 +54,7 @@ import org.springblade.transport.pojo.dto.WaybillImportBatchRequest;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.LoadingManageVO;
import org.springblade.transport.pojo.vo.WaybillImportBatchVO;
+import org.springblade.transport.pojo.vo.WaybillImportValidateResultVO;
import org.springblade.transport.pojo.vo.WaybillLocateVO;
import org.springblade.transport.pojo.vo.WaybillTrackVO;
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
@@ -191,15 +193,22 @@ public class WaybillController extends BladeController {
@PostMapping("/import-batch/validate")
@ApiOperationSupport(order = 6)
@Operation(summary = "校验运单批量导入数据")
- public void validateImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) {
- waybillImportBatchService.validate(request, response);
+ public R validateImportBatch(@RequestBody WaybillImportBatchRequest request) {
+ return R.data(waybillImportBatchService.validate(request));
+ }
+
+ @PostMapping("/import-batch/export-failures")
+ @ApiOperationSupport(order = 6)
+ @Operation(summary = "导出运单批量导入失败明细")
+ public void exportImportFailures(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) {
+ waybillImportBatchService.exportFailures(request, response);
}
@PostMapping("/import-batch/confirm")
@ApiOperationSupport(order = 7)
@Operation(summary = "确认运单批量导入")
- public void confirmImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) {
- waybillImportBatchService.confirm(request, response);
+ public R confirmImportBatch(@RequestBody WaybillImportBatchRequest request) {
+ return R.data(waybillImportBatchService.confirm(request));
}
@PostMapping("/import-batch/remove")
@@ -242,10 +251,28 @@ public class WaybillController extends BladeController {
@ApiOperationSupport(order = 13)
@Operation(summary = "导入运单管理", description = "传入excel")
public R importWaybill(MultipartFile file, HttpServletResponse response) {
- List 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("导入数据成功");
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java
index 40d22aba..779e7821 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java
@@ -81,6 +81,9 @@ public class AccidentRecordExportExcel implements Serializable {
@ExcelProperty("事故原因及损坏情况")
private String accidentReasonDamage;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java
index 3b9a297a..36bf3ffa 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java
@@ -81,6 +81,9 @@ public class AnnualInspectionRecordExportExcel implements Serializable {
@ExcelProperty("评定(复核)单位")
private String assessmentUnit;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationFeeReader.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationFeeReader.java
index e2802105..ee7af2f8 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationFeeReader.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationFeeReader.java
@@ -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> row, AnalysisContext context) {
Map 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());
}
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExportExcel.java
index 09d16966..a80893e3 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExportExcel.java
@@ -88,6 +88,9 @@ public class CommonAddressExportExcel implements Serializable {
@ExcelProperty("备注")
private String remark;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("更新时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date updateTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExcel.java
index 4440dc7f..e5c26caa 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExcel.java
@@ -73,7 +73,7 @@ public class CommonCargoExcel implements Serializable {
@ExcelProperty("规格")
private String specification;
- @ExcelProperty("型号")
+ @ExcelProperty("*型号")
private String model;
@ExcelProperty("单价")
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java
index 8c99ad77..23ea39ef 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java
@@ -93,6 +93,9 @@ public class CommonCargoExportExcel implements Serializable {
@ExcelProperty("组织")
private String deptName;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("更新时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date updateTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java
index d363dfc3..ebe4eec2 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java
@@ -93,6 +93,9 @@ public class CustomerArchiveExcel implements Serializable {
@ExcelProperty("法人/负责人")
private String legalPerson;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/DriverExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/DriverExcel.java
index e69a82a8..996a85a6 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/DriverExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/DriverExcel.java
@@ -35,6 +35,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
+import java.util.Date;
/**
* 司机管理 Excel
@@ -128,4 +129,10 @@ public class DriverExcel implements Serializable {
@ExcelProperty("所属组织 *")
private String organizationName;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
+ @ExcelProperty("创建时间")
+ private Date createTime;
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EquipmentLedgerExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EquipmentLedgerExcel.java
index f8499608..cdb7df63 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EquipmentLedgerExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EquipmentLedgerExcel.java
@@ -7,10 +7,12 @@ package org.springblade.transport.excel;
import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.format.DateTimeFormat;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
+import java.util.Date;
/**
* 设备台账 Excel
@@ -29,5 +31,7 @@ public class EquipmentLedgerExcel implements Serializable {
@ExcelProperty("规格型号") private String specificationModel;
@ExcelProperty(value = "出厂日期", converter = org.springblade.common.excel.LenientDateStringConverter.class) private String factoryDate;
@ExcelProperty("备注") private String remark;
+ @ExcelProperty("创建人") private String createUserName;
+ @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") private Date createTime;
@ExcelIgnore private String errorMessage;
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java
index 1400ed00..96a064a0 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java
@@ -49,6 +49,9 @@ public class EtcRecordExportExcel implements Serializable {
@ExcelProperty("出口站")
private String exitStation;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java
index ad1f0e7a..d8461d4a 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java
@@ -81,6 +81,9 @@ public class InsuranceRecordExportExcel implements Serializable {
@ExcelProperty("开票日期")
private LocalDate invoiceDate;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java
index 6380c7a2..7b435f94 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java
@@ -34,7 +34,7 @@ public class LoadingManageExcel implements Serializable {
private String loadingNo;
@ExcelProperty("配载子单号")
private String loadingSubNos;
- @ExcelProperty("车牌号/航班号/船号/班列号")
+ @ExcelProperty("车牌号/船班号/船次号/班次号")
private String vehicleNo;
@ExcelProperty("司机")
private String driverName;
@@ -54,6 +54,8 @@ public class LoadingManageExcel implements Serializable {
private String transportType;
@ExcelProperty("数据来源")
private String dataSource;
+ @ExcelProperty("创建人")
+ private String createUserName;
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java
index a3c8a84e..10128b08 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java
@@ -24,14 +24,17 @@ public class MaintenancePlanExportExcel extends MaintenancePlanExcel {
@Serial
private static final long serialVersionUID = 1L;
- @ExcelProperty(value = "创建时间", index = 12)
+ @ExcelProperty(value = "创建人", index = 12)
+ private String createUserName;
+
+ @ExcelProperty(value = "创建时间", index = 13)
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
- @ExcelProperty(value = "更新人", index = 13)
+ @ExcelProperty(value = "更新人", index = 14)
private String updateUserName;
- @ExcelProperty(value = "更新时间", index = 14)
+ @ExcelProperty(value = "更新时间", index = 15)
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java
index db5f3e9d..b2fdde6b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java
@@ -96,6 +96,9 @@ public class MaintenanceRecordExcel implements Serializable {
@NumberFormat("0.00")
private BigDecimal mileage;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MasterOrderWaybillExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MasterOrderWaybillExcel.java
index 5f8f1ae0..282e8953 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MasterOrderWaybillExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MasterOrderWaybillExcel.java
@@ -59,7 +59,7 @@ public class MasterOrderWaybillExcel {
private String carrierName;
@ExcelProperty("司机")
private String driverName;
- @ExcelProperty("车牌号/航班号/船号/班列号")
+ @ExcelProperty("车牌号/船班号/船次号/班次号")
private String vehicleNo;
@ExcelProperty("开始日期")
private LocalDate startDate;
@@ -69,6 +69,10 @@ public class MasterOrderWaybillExcel {
private String businessStatus;
@ExcelProperty("备注")
private String remark;
+ @ExcelProperty("创建人")
+ private String createUserName;
+ @ExcelProperty("更新时间")
+ private Date updateTime;
@ExcelProperty("创建时间")
private Date createTime;
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java
index 5ad5f4ab..cb3e6999 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java
@@ -70,6 +70,9 @@ public class MileageRecordExportExcel implements Serializable {
@NumberFormat("0.00")
private BigDecimal totalMileage;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java
index 3db72793..d2bcccd9 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java
@@ -60,6 +60,9 @@ public class OilElectricRecordExportExcel implements Serializable {
@ExcelProperty("站点")
private String station;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java
index 1dfe171a..72daa532 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java
@@ -41,6 +41,9 @@ public class OtherExpenseRecordExportExcel implements Serializable {
@ExcelProperty("金额")
private BigDecimal amount;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java
index 26009e8c..2a8b25f7 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java
@@ -74,6 +74,9 @@ public class TireReplacementRecordExportExcel implements Serializable {
@ExcelProperty("换胎说明")
private String replacementDescription;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java
index f737cd26..23bfefb7 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java
@@ -55,6 +55,9 @@ public class TransportChangeRecordExportExcel implements Serializable {
@ExcelProperty("变更内容")
private String changeContent;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportShipExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportShipExcel.java
index 2dc65699..337c1bb3 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportShipExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportShipExcel.java
@@ -36,6 +36,7 @@ import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
+import java.util.Date;
/**
* 船舶管理 Excel
@@ -144,4 +145,10 @@ public class TransportShipExcel implements Serializable {
@ExcelProperty("船舶经营人")
private String shipOperator;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
+ @ExcelProperty("创建时间")
+ private Date createTime;
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportVehicleExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportVehicleExcel.java
index ac5c02dc..09ac2252 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportVehicleExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportVehicleExcel.java
@@ -35,6 +35,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
+import java.util.Date;
/**
* 车辆管理 Excel
@@ -83,4 +84,10 @@ public class TransportVehicleExcel implements Serializable {
@ExcelProperty("年审有效期")
private LocalDate annualReviewEndDate;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
+ @ExcelProperty("创建时间")
+ private Date createTime;
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/VehicleReconciliationFeeReader.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/VehicleReconciliationFeeReader.java
index e6d889a6..990e8973 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/VehicleReconciliationFeeReader.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/VehicleReconciliationFeeReader.java
@@ -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> row, AnalysisContext context) {
Map 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());
}
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java
index 7b95877e..2e96ecac 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java
@@ -94,6 +94,9 @@ public class ViolationRecordExcel implements Serializable {
@ExcelProperty("处理结果")
private String processResult;
+ @ExcelProperty("创建人")
+ private String createUserName;
+
@ExcelProperty("创建时间")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
private Date createTime;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java
index c093fdd8..ec1ca37c 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java
@@ -98,7 +98,7 @@ public class WaybillExcel implements Serializable {
private String driverName;
@ExcelProperty("司机手机号")
private String driverPhone;
- @ExcelProperty("车/船/航班/班列号")
+ @ExcelProperty("车牌号/船班号/船次号/班次号")
private String vehicleNo;
@ExcelProperty("挂车车牌号")
private String trailerVehicleNo;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillImportBatchExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillImportBatchExcel.java
index ac90cc8b..91528040 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillImportBatchExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillImportBatchExcel.java
@@ -28,7 +28,7 @@ public class WaybillImportBatchExcel implements Serializable {
private String originalNo;
@ExcelProperty("配载标识号")
private String loadingIdentifier;
- @ExcelProperty("*车牌号/航班号/船号/班列号")
+ @ExcelProperty("*车牌号/船班号/船次号/班次号")
private String vehicleNo;
@ExcelProperty("*运输方式")
private String transportType;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AccidentRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AccidentRecordMapper.xml
index 689037a7..99c0b886 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AccidentRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AccidentRecordMapper.xml
@@ -42,8 +42,8 @@
accident_location,
accident_nature,
accident_responsibility,
- CASE WHEN direct_economic_loss < 0 THEN 0 ELSE direct_economic_loss END AS direct_economic_loss,
- CASE WHEN insurance_claim_amount < 0 THEN 0 ELSE insurance_claim_amount END AS insurance_claim_amount,
+ CASE WHEN direct_economic_loss = -1 THEN NULL WHEN direct_economic_loss < 0 THEN 0 ELSE direct_economic_loss END AS direct_economic_loss,
+ CASE WHEN insurance_claim_amount = -1 THEN NULL WHEN insurance_claim_amount < 0 THEN 0 ELSE insurance_claim_amount END AS insurance_claim_amount,
accident_reason_damage,
attachments,
remark
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AnnualInspectionRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AnnualInspectionRecordMapper.xml
index 30fa5f39..83f5592b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AnnualInspectionRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/AnnualInspectionRecordMapper.xml
@@ -88,7 +88,7 @@
valid_until_date,
passenger_type_level,
inspection_unit,
- CASE WHEN fee < 0 THEN 0 ELSE fee END AS fee,
+ CASE WHEN fee = -1 THEN NULL WHEN fee < 0 THEN 0 ELSE fee END AS fee,
assessment_unit,
attachments,
remark
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InsuranceRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InsuranceRecordMapper.xml
index ea739687..074106e7 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InsuranceRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InsuranceRecordMapper.xml
@@ -44,8 +44,8 @@
policy_no,
start_date,
end_date,
- CASE WHEN insured_amount < 0 THEN 0 ELSE insured_amount END AS insured_amount,
- CASE WHEN premium < 0 THEN 0 ELSE premium END AS premium,
+ CASE WHEN insured_amount = -1 THEN NULL ELSE insured_amount END AS insured_amount,
+ CASE WHEN premium = -1 THEN NULL ELSE premium END AS premium,
invoice_no,
invoice_date,
ocr_template,
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenancePlanMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenancePlanMapper.xml
index b67970dc..192e57bc 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenancePlanMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenancePlanMapper.xml
@@ -44,7 +44,7 @@
vehicle_no,
maintainer,
maintenance_time,
- CASE WHEN mileage < 0 THEN 0 ELSE mileage END AS mileage,
+ CASE WHEN mileage = -1 THEN NULL WHEN mileage < 0 THEN 0 ELSE mileage END AS mileage,
mileage_unit,
maintenance_item,
cost,
@@ -52,7 +52,7 @@
contact_phone,
address,
next_maintenance_time,
- CASE WHEN next_maintenance_mileage < 0 THEN 0 ELSE next_maintenance_mileage END AS next_maintenance_mileage,
+ CASE WHEN next_maintenance_mileage = -1 THEN NULL WHEN next_maintenance_mileage < 0 THEN 0 ELSE next_maintenance_mileage END AS next_maintenance_mileage,
attachments,
remark
FROM
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MileageRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MileageRecordMapper.xml
index 8e3cbc79..d4dc2165 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MileageRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MileageRecordMapper.xml
@@ -36,10 +36,10 @@
is_deleted,
vehicle_type,
vehicle_no,
- CASE WHEN previous_month_mileage < 0 THEN 0 ELSE previous_month_mileage END AS previous_month_mileage,
- CASE WHEN current_month_mileage < 0 THEN 0 ELSE current_month_mileage END AS current_month_mileage,
- CASE WHEN monthly_mileage < 0 THEN 0 ELSE monthly_mileage END AS monthly_mileage,
- CASE WHEN total_mileage < 0 THEN 0 ELSE total_mileage END AS total_mileage,
+ CASE WHEN previous_month_mileage = -1 THEN NULL WHEN previous_month_mileage < 0 THEN 0 ELSE previous_month_mileage END AS previous_month_mileage,
+ CASE WHEN current_month_mileage = -1 THEN NULL WHEN current_month_mileage < 0 THEN 0 ELSE current_month_mileage END AS current_month_mileage,
+ CASE WHEN monthly_mileage = -1 THEN NULL WHEN monthly_mileage < 0 THEN 0 ELSE monthly_mileage END AS monthly_mileage,
+ CASE WHEN total_mileage = -1 THEN NULL WHEN total_mileage < 0 THEN 0 ELSE total_mileage END AS total_mileage,
mileage_unit,
attachments,
remark
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/OilElectricRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/OilElectricRecordMapper.xml
index 88612747..80f79c14 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/OilElectricRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/OilElectricRecordMapper.xml
@@ -48,10 +48,10 @@
vehicle_no,
card_holder,
data_source,
- CASE WHEN quantity < 0 THEN 0 ELSE quantity END AS quantity,
- CASE WHEN unit_price < 0 THEN 0 ELSE unit_price END AS unit_price,
- CASE WHEN transaction_amount < 0 THEN 0 ELSE transaction_amount END AS transaction_amount,
- CASE WHEN balance < 0 THEN 0 ELSE balance END AS balance,
+ CASE WHEN quantity = -1 THEN NULL WHEN quantity < 0 THEN 0 ELSE quantity END AS quantity,
+ CASE WHEN unit_price = -1 THEN NULL WHEN unit_price < 0 THEN 0 ELSE unit_price END AS unit_price,
+ CASE WHEN transaction_amount = -1 THEN NULL WHEN transaction_amount < 0 THEN 0 ELSE transaction_amount END AS transaction_amount,
+ CASE WHEN balance = -1 THEN NULL WHEN balance < 0 THEN 0 ELSE balance END AS balance,
station,
attachments,
remark
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TireReplacementRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TireReplacementRecordMapper.xml
index 97cb9965..4d821f53 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TireReplacementRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TireReplacementRecordMapper.xml
@@ -38,8 +38,8 @@
handler,
replacement_time,
tire_brand,
- CASE WHEN tire_quantity < 0 THEN 0 ELSE tire_quantity END AS tire_quantity,
- CASE WHEN replacement_cost < 0 THEN 0 ELSE replacement_cost END AS replacement_cost,
+ CASE WHEN tire_quantity = -1 THEN NULL WHEN tire_quantity < 0 THEN 0 ELSE tire_quantity END AS tire_quantity,
+ CASE WHEN replacement_cost = -1 THEN NULL WHEN replacement_cost < 0 THEN 0 ELSE replacement_cost END AS replacement_cost,
replacement_description,
attachments,
remark
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ViolationRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ViolationRecordMapper.xml
index 5b90c3f3..9563419c 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ViolationRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ViolationRecordMapper.xml
@@ -46,8 +46,8 @@
violation_item,
violation_time,
location,
- CASE WHEN fine_amount < 0 THEN 0 ELSE fine_amount END AS fine_amount,
- CASE WHEN deduct_points < 0 THEN 0 ELSE deduct_points END AS deduct_points,
+ CASE WHEN fine_amount = -1 THEN NULL WHEN fine_amount < 0 THEN 0 ELSE fine_amount END AS fine_amount,
+ CASE WHEN deduct_points = -1 THEN NULL WHEN deduct_points < 0 THEN 0 ELSE deduct_points END AS deduct_points,
penalty_unit,
process_status,
process_description,
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java
index b5456cf4..8e25176e 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java
@@ -3,19 +3,26 @@ package org.springblade.transport.mk;
import lombok.RequiredArgsConstructor;
import org.springblade.core.tenant.annotation.TenantIgnore;
import org.springblade.core.tool.utils.Func;
+import org.springblade.transport.pojo.dto.BillPaymentStatusRequest;
+import org.springblade.transport.pojo.dto.InvoiceApplicationStatusRequest;
+import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest;
+import org.springblade.transport.pojo.entity.BillPayment;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.FormalSettlement;
+import org.springblade.transport.pojo.entity.InvoiceApplication;
+import org.springblade.transport.pojo.entity.InvoiceReceipt;
import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.entity.ProjectApply;
-import org.springblade.transport.pojo.entity.Waybill;
+import org.springblade.transport.service.IBillPaymentService;
import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.service.ICustomerArchiveService;
import org.springblade.transport.service.IFormalSettlementService;
+import org.springblade.transport.service.IInvoiceApplicationService;
+import org.springblade.transport.service.IInvoiceReceiptService;
import org.springblade.transport.service.IPaymentApplicationService;
import org.springblade.transport.service.IPreSettlementService;
import org.springblade.transport.service.IProjectApplyService;
-import org.springblade.transport.service.IWaybillService;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@@ -198,57 +205,6 @@ public final class MkProcessBizHandlers {
}
}
- @Component
- @TenantIgnore
- @RequiredArgsConstructor
- public static class WaybillHandler implements IMkProcessBizHandler {
- private final IWaybillService service;
-
- @Override
- public String bizType() {
- return "waybill-manage";
- }
-
- @Override
- public Object publicDetail(Long id) {
- return service.publicDetail(id);
- }
-
- @Override
- public boolean syncFromProcessInfo(Long id, Object processInfo) {
- Waybill entity = service.getById(id);
- if (entity == null) {
- return false;
- }
- String node = MkProcessNodeHelper.currentNode(processInfo);
- if (Func.isEmpty(node)) {
- return false;
- }
- entity.setCurrentProcessNode(node);
- return service.updateById(entity);
- }
-
- @Override
- public boolean approveFromProcess(Long id, String processorName) {
- Waybill entity = service.getById(id);
- if (entity == null) {
- return false;
- }
- entity.setCurrentProcessNode("审核通过");
- return service.updateById(entity);
- }
-
- @Override
- public boolean rejectFromProcess(Long id, String processorName) {
- Waybill entity = service.getById(id);
- if (entity == null) {
- return false;
- }
- entity.setCurrentProcessNode("审核不通过");
- return service.updateById(entity);
- }
- }
-
@Component
@TenantIgnore
@RequiredArgsConstructor
@@ -436,4 +392,229 @@ public final class MkProcessBizHandlers {
return service.updateById(entity);
}
}
+
+ @Component
+ @TenantIgnore
+ @RequiredArgsConstructor
+ public static class InvoiceApplicationHandler implements IMkProcessBizHandler {
+ private final IInvoiceApplicationService service;
+
+ @Override
+ public String bizType() {
+ return "invoice-application";
+ }
+
+ @Override
+ public Object publicDetail(Long id) {
+ return service.detail(id);
+ }
+
+ @Override
+ public boolean syncFromProcessInfo(Long id, Object processInfo) {
+ InvoiceApplication entity = service.getById(id);
+ if (entity == null) {
+ return false;
+ }
+ String node = MkProcessNodeHelper.currentNode(processInfo);
+ String processor = MkProcessNodeHelper.currentProcessor(processInfo);
+ if (Func.isEmpty(node) && Func.isEmpty(processor)) {
+ return false;
+ }
+ if (Func.isNotEmpty(node)) {
+ entity.setCurrentNode(node);
+ }
+ if (Func.isNotEmpty(processor)) {
+ entity.setCurrentProcessor(processor);
+ }
+ entity.setApprovalStatus("reviewing");
+ return service.updateById(entity);
+ }
+
+ @Override
+ public boolean approveFromProcess(Long id, String processorName) {
+ InvoiceApplication entity = service.getById(id);
+ if (entity == null || "approved".equals(entity.getApprovalStatus())) {
+ return entity != null;
+ }
+ InvoiceApplicationStatusRequest request = new InvoiceApplicationStatusRequest();
+ request.setId(id);
+ service.approve(request);
+ InvoiceApplication updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean rejectFromProcess(Long id, String processorName) {
+ InvoiceApplication entity = service.getById(id);
+ if (entity == null || "returned".equals(entity.getApprovalStatus())) {
+ return entity != null;
+ }
+ InvoiceApplicationStatusRequest request = new InvoiceApplicationStatusRequest();
+ request.setId(id);
+ request.setReason("MK流程驳回");
+ service.returnBill(request);
+ InvoiceApplication updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核不通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
+ }
+ }
+
+ @Component
+ @TenantIgnore
+ @RequiredArgsConstructor
+ public static class InvoiceReceiptHandler implements IMkProcessBizHandler {
+ private final IInvoiceReceiptService service;
+
+ @Override
+ public String bizType() {
+ return "invoice-receipt";
+ }
+
+ @Override
+ public Object publicDetail(Long id) {
+ return service.detail(id);
+ }
+
+ @Override
+ public boolean syncFromProcessInfo(Long id, Object processInfo) {
+ InvoiceReceipt entity = service.getById(id);
+ if (entity == null) {
+ return false;
+ }
+ String node = MkProcessNodeHelper.currentNode(processInfo);
+ String processor = MkProcessNodeHelper.currentProcessor(processInfo);
+ if (Func.isEmpty(node) && Func.isEmpty(processor)) {
+ return false;
+ }
+ if (Func.isNotEmpty(node)) {
+ entity.setCurrentNode(node);
+ }
+ if (Func.isNotEmpty(processor)) {
+ entity.setCurrentProcessor(processor);
+ }
+ entity.setApprovalStatus("reviewing");
+ return service.updateById(entity);
+ }
+
+ @Override
+ public boolean approveFromProcess(Long id, String processorName) {
+ InvoiceReceipt entity = service.getById(id);
+ if (entity == null || "approved".equals(entity.getApprovalStatus())) {
+ return entity != null;
+ }
+ InvoiceReceiptStatusRequest request = new InvoiceReceiptStatusRequest();
+ request.setId(id);
+ service.approve(request);
+ InvoiceReceipt updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean rejectFromProcess(Long id, String processorName) {
+ InvoiceReceipt entity = service.getById(id);
+ if (entity == null || "returned".equals(entity.getApprovalStatus())) {
+ return entity != null;
+ }
+ InvoiceReceiptStatusRequest request = new InvoiceReceiptStatusRequest();
+ request.setId(id);
+ request.setReason("MK流程驳回");
+ service.returnBill(request);
+ InvoiceReceipt updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核不通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
+ }
+ }
+
+ @Component
+ @TenantIgnore
+ @RequiredArgsConstructor
+ public static class BillPaymentHandler implements IMkProcessBizHandler {
+ private final IBillPaymentService service;
+
+ @Override
+ public String bizType() {
+ return "bill-payment";
+ }
+
+ @Override
+ public Object publicDetail(Long id) {
+ return service.detail(id);
+ }
+
+ @Override
+ public boolean syncFromProcessInfo(Long id, Object processInfo) {
+ BillPayment entity = service.getById(id);
+ if (entity == null) {
+ return false;
+ }
+ String node = MkProcessNodeHelper.currentNode(processInfo);
+ String processor = MkProcessNodeHelper.currentProcessor(processInfo);
+ if (Func.isEmpty(node) && Func.isEmpty(processor)) {
+ return false;
+ }
+ if (Func.isNotEmpty(node)) {
+ entity.setCurrentNode(node);
+ }
+ if (Func.isNotEmpty(processor)) {
+ entity.setCurrentProcessor(processor);
+ }
+ entity.setApprovalStatus("reviewing");
+ return service.updateById(entity);
+ }
+
+ @Override
+ public boolean approveFromProcess(Long id, String processorName) {
+ BillPayment entity = service.getById(id);
+ if (entity == null || "approved".equals(entity.getApprovalStatus())) {
+ return entity != null;
+ }
+ BillPaymentStatusRequest request = new BillPaymentStatusRequest();
+ request.setId(id);
+ service.approve(request);
+ BillPayment updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean rejectFromProcess(Long id, String processorName) {
+ BillPayment entity = service.getById(id);
+ if (entity == null || "returned".equals(entity.getApprovalStatus())) {
+ return entity != null;
+ }
+ BillPaymentStatusRequest request = new BillPaymentStatusRequest();
+ request.setId(id);
+ request.setReason("MK流程驳回");
+ service.returnBill(request);
+ BillPayment updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核不通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
+ }
+ }
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java
index 7ca1ad3d..9443faa8 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java
@@ -10,7 +10,6 @@ import org.springblade.core.tool.utils.StringUtil;
import org.springblade.process.feign.IBusinessProcessClient;
import org.springframework.stereotype.Service;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -129,14 +128,12 @@ public class MkProcessMessageService {
if (!"20".equals(firstText(info, "fdProcessStatus"))) {
return false;
}
- if (hasItems(info.get("currentHandlers"))) {
+ Object historyHandlerInfos = asMap(info.get("fdTaskInfo")).get("historyHandlerInfos");
+ if (!(historyHandlerInfos instanceof List> historyList) || historyList.isEmpty()) {
return false;
}
- return !hasItems(asMap(info.get("fdTaskInfo")).get("handlerInfos"));
- }
-
- private static boolean hasItems(Object value) {
- return value instanceof Collection> collection && !collection.isEmpty();
+ Object lastHandler = historyList.get(historyList.size() - 1);
+ return "N6".equals(firstText(asMap(lastHandler), "nodeNumber"));
}
private void applyProcessResult(IMkProcessBizHandler handler, String bizType, String bizIdText,
@@ -154,8 +151,18 @@ public class MkProcessMessageService {
} else {
handler.rejectFromProcess(bizId, processorName);
}
+ try {
+ FR markResult = businessProcessClient.markProcessFinished(processId, approved);
+ log.info("公开页标记业务流程完结 bizType={} processId={} approved={} result={}",
+ bizType, processId, approved, JSON.toJSONString(markResult));
+ } catch (Exception markException) {
+ log.error("公开页标记业务流程完结失败 bizType={} processId={} approved={}",
+ bizType, processId, approved, markException);
+ }
} catch (NumberFormatException e) {
log.warn("流程{}但业务id格式不正确 bizType={} id={}", action, bizType, bizIdText);
+ } catch (Exception e) {
+ log.error("流程{}同步业务状态失败 bizType={} id={} processId={}", action, bizType, bizIdText, processId, e);
}
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java
index 92c253a5..b8a16346 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java
@@ -19,6 +19,7 @@ public interface IVoucherManageService extends BaseService {
VoucherManageVO detail(Long id);
IPage folderPage(IPage> page, Long voucherId, String plateNo, Integer matched);
VoucherFolderVO folderDetail(Long voucherId, String plateNo);
+ List listWaybillFolders(Long waybillId);
void replaceFolder(Long voucherId, String plateNo, MultipartFile file);
void replaceFolderByObject(Long voucherId, String plateNo, String objectKey, String fileName, Long size, String contentType);
void removeFolder(Long voucherId, String plateNo);
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillImportBatchService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillImportBatchService.java
index a06dcd40..58c60733 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillImportBatchService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillImportBatchService.java
@@ -7,12 +7,14 @@ import org.springblade.transport.pojo.dto.WaybillImportBatchRequest;
import org.springblade.transport.pojo.entity.WaybillImportBatch;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.WaybillImportBatchVO;
+import org.springblade.transport.pojo.vo.WaybillImportValidateResultVO;
/** 运单批次服务。 */
public interface IWaybillImportBatchService extends BaseService {
WaybillImportBatch saveDraft(WaybillImportBatchRequest request);
- void validate(WaybillImportBatchRequest request, HttpServletResponse response);
- void confirm(WaybillImportBatchRequest request, HttpServletResponse response);
+ WaybillImportValidateResultVO validate(WaybillImportBatchRequest request);
+ void exportFailures(WaybillImportBatchRequest request, HttpServletResponse response);
+ WaybillImportBatchVO confirm(WaybillImportBatchRequest request);
IPage page(IPage page, WaybillImportBatchRequest request);
BusinessRemoveResultVO removeBatches(String ids);
String nextBatchNo();
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/AccidentRecordServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/AccidentRecordServiceImpl.java
index 70b04017..81155e0b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/AccidentRecordServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/AccidentRecordServiceImpl.java
@@ -61,6 +61,8 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl ACCIDENT_NATURES = Set.of("重大事故", "一般事故", "轻微事故", "其他");
@@ -69,7 +71,12 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl selectAccidentRecordPage(IPage page, AccidentRecordVO accidentRecord) {
List records = baseMapper.selectAccidentRecordPage(page, accidentRecord);
- records.forEach(record -> record.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser())));
+ records.forEach(record -> {
+ record.setDirectEconomicLoss(normalizeEmptyAmount(record.getDirectEconomicLoss()));
+ record.setInsuranceClaimAmount(normalizeEmptyAmount(record.getInsuranceClaimAmount()));
+ record.setCreateUserName(UserCache.getUserRealName(record.getCreateUser()));
+ record.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser()));
+ });
return page.setRecords(records);
}
@@ -86,7 +93,7 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl importAccidentRecord(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
List errorList = new ArrayList<>();
List accidentRecordList = new ArrayList<>();
@@ -111,8 +118,9 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl exportAccidentRecord(Wrapper queryWrapper) {
return list(queryWrapper).stream().map(accidentRecord -> {
AccidentRecordExportExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(accidentRecord, AccidentRecordExportExcel.class));
- excel.setDirectEconomicLoss(nonNegative(accidentRecord.getDirectEconomicLoss()));
- excel.setInsuranceClaimAmount(nonNegative(accidentRecord.getInsuranceClaimAmount()));
+ excel.setDirectEconomicLoss(normalizeEmptyAmount(accidentRecord.getDirectEconomicLoss()));
+ excel.setInsuranceClaimAmount(normalizeEmptyAmount(accidentRecord.getInsuranceClaimAmount()));
+ excel.setCreateUserName(UserCache.getUserRealName(accidentRecord.getCreateUser()));
excel.setUpdateUserName(UserCache.getUserRealName(accidentRecord.getUpdateUser()));
return excel;
}).toList();
@@ -168,6 +177,9 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl= 0) {
- return value;
- }
- return BigDecimal.ZERO;
+ private BigDecimal normalizeEmptyAmount(BigDecimal value) {
+ return Func.isNotEmpty(value) && value.compareTo(EMPTY_AMOUNT_SENTINEL) == 0 ? null : value;
}
private String normalizeVehicleType(String vehicleType) {
@@ -255,7 +264,7 @@ public class AccidentRecordServiceImpl extends BaseServiceImpl PASSENGER_TYPE_LEVELS = Set.of("一级", "二级", "其他");
@@ -70,7 +72,11 @@ public class AnnualInspectionRecordServiceImpl extends BaseServiceImpl selectAnnualInspectionRecordPage(IPage page, AnnualInspectionRecordVO annualInspectionRecord) {
List records = baseMapper.selectAnnualInspectionRecordPage(page, annualInspectionRecord);
- records.forEach(record -> record.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser())));
+ records.forEach(record -> {
+ record.setFee(normalizeEmptyAmount(record.getFee()));
+ record.setCreateUserName(UserCache.getUserRealName(record.getCreateUser()));
+ record.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser()));
+ });
return page.setRecords(records);
}
@@ -105,7 +111,7 @@ public class AnnualInspectionRecordServiceImpl extends BaseServiceImpl importAnnualInspectionRecord(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
List errorList = new ArrayList<>();
List annualInspectionRecordList = new ArrayList<>();
@@ -131,8 +137,9 @@ public class AnnualInspectionRecordServiceImpl extends BaseServiceImpl= 0) {
- return value;
- }
- return BigDecimal.ZERO;
+ private BigDecimal normalizeEmptyAmount(BigDecimal value) {
+ return Func.isNotEmpty(value) && value.compareTo(EMPTY_AMOUNT_SENTINEL) == 0 ? null : value;
}
private String normalizeVehicleType(String vehicleType) {
@@ -285,7 +292,7 @@ public class AnnualInspectionRecordServiceImpl extends BaseServiceImpl {
CommonAddressExportExcel excel = Objects.requireNonNull(BeanUtil.copyProperties(address, CommonAddressExportExcel.class));
excel.setSiteCodeDisplay(TYPE_NORMAL.equals(address.getAddressType()) ? NORMAL_SITE_CODE : address.getSiteCode());
+ excel.setCreateUserName(UserCache.getUserRealName(address.getCreateUser()));
return excel;
}).toList();
}
private void prepareQuery(CommonAddressVO address) {
- if (Objects.equals(address.getAllDept(), 1)) {
- if (!AuthUtil.isAdministrator()) {
- throw new ServiceException("无权查看全部组织常用地址");
- }
- } else {
- address.setDeptId(currentDeptId());
- }
+ List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(address.getAllDept(), address.getDeptId(), "常用地址");
+ address.setDeptId(Func.isEmpty(deptIdList) ? null : deptIdList.get(0));
address.setAddressName(trimToNull(address.getAddressName()));
address.setAddressType(trimToNull(address.getAddressType()));
address.setDetailAddress(trimToNull(address.getDetailAddress()));
@@ -300,14 +297,14 @@ public class CommonAddressServiceImpl extends BaseServiceImpl importCommonCargo(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
+ // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
+ // 注意:本方法虽已标注 @Transactional,但循环内把异常吞掉后正常 return,
+ // Spring 会认为一切成功并提交事务——注解救不了被吞掉的异常。
+ // 必须把错误明细收集完后抛异常,回滚才会真正发生。
List failureList = new ArrayList<>();
for (int index = 0; index < data.size(); index++) {
CommonCargoExcel excel = data.get(index);
@@ -168,14 +171,23 @@ public class CommonCargoServiceImpl extends BaseServiceImpl buildQuery(CommonCargoVO commonCargo) {
- TransportBusinessSupport.validateAllDept(commonCargo.getAllDept(), "常用货物");
LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(CommonCargo::getIsDeleted, 0);
- if (!Objects.equals(commonCargo.getAllDept(), 1)) {
- queryWrapper.eq(CommonCargo::getDeptId, TransportBusinessSupport.currentDeptId("常用货物"));
- } else if (Func.isNotEmpty(commonCargo.getDeptId())) {
- queryWrapper.eq(CommonCargo::getDeptId, commonCargo.getDeptId());
+ List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonCargo.getAllDept(), commonCargo.getDeptId(), "常用货物");
+ if (Func.isNotEmpty(deptIdList)) {
+ if (deptIdList.size() == 1) {
+ queryWrapper.eq(CommonCargo::getDeptId, deptIdList.get(0));
+ } else {
+ queryWrapper.in(CommonCargo::getDeptId, deptIdList);
+ }
}
if (Func.isNotEmpty(commonCargo.getFirstCargoTypeId())) {
queryWrapper.eq(CommonCargo::getFirstCargoTypeId, commonCargo.getFirstCargoTypeId());
@@ -301,14 +315,15 @@ public class CommonCargoServiceImpl extends BaseServiceImpl importCommonRoute(List data) {
if (Func.isEmpty(data)) {
- throw new ServiceException("导入数据不能为空");
+ throw new ServiceException("未读取到数据行,请确认表头下方已填写内容");
}
+ // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
+ // 注意:本方法虽已标注 @Transactional,但循环内把异常吞掉后正常 return,
+ // Spring 会认为一切成功并提交事务——注解救不了被吞掉的异常。
+ // 必须把错误明细收集完后抛异常,回滚才会真正发生。
List failureList = new ArrayList<>();
for (int index = 0; index < data.size(); index++) {
CommonRouteImportExcel excel = data.get(index);
@@ -142,16 +147,19 @@ public class CommonRouteServiceImpl extends BaseServiceImpl buildQuery(CommonRouteVO commonRoute) {
- TransportBusinessSupport.validateAllDept(commonRoute.getAllDept(), "常用线路");
LambdaQueryWrapper