🐛 修复运输对账导入无回滚与运输计划明细不全(批次4)
本批是 16 个模块收尾,两个特殊模块: 1) 运输对账(importVehicles / importCargoes)——最严重的一例: 既无 setRollbackOnly 也不抛异常。方法开头 resetExternal(id) 清空既有外部账单, 循环末尾无条件插入本批正确行,然后 return failures。一旦有失败行: 旧外部账单已被清空、新账单只插入了部分——数据损坏且不可恢复。 改法:任一行失败时抛 ImportFailureException(rows),让 resetExternal 清空 与本批插入在同一事务内一起回滚。另把失败原因写回原行(异常携带原表 rows, 只写 Vehicle/CargoReconciliationFailureExcel 副本会丢原因)。 控制器改 catch 异常导出原表全量(用 export 保留按列标红)。 2) 运输计划(importTransportPlan)——失败明细只返回 errorMap.values()(仅错误行)。 改为抛 ImportFailureException(data) 携带原表全量;控制器 catch 后导出。 验证:mvn install -DskipTests 通过;全项目 12 个导入回归脚本 379 项断言全绿。 全项目已无 TransactionAspectSupport...setRollbackOnly() 调用残留。 注:运输对账需先建对账草稿(多张前置表)才能端到端回归, 本批仅完成代码改造与编译验证,回归用例待补(见后续说明)。
This commit is contained in:
+13
-4
@@ -147,10 +147,19 @@ public class TransportPlanController extends BladeController {
|
||||
public R importTransportPlan(MultipartFile file, @RequestParam Long projectId, @RequestParam String projectName,
|
||||
@RequestParam Long contractId, @RequestParam String contractName, @RequestParam String customerName,
|
||||
HttpServletResponse response) {
|
||||
List<TransportPlanImportExcel> failureList = transportPlanService.importTransportPlan(
|
||||
ExcelUtil.read(file, TransportPlanImportExcel.class), projectId, projectName, contractId, contractName, customerName);
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TransportPlanImportExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
transportPlanService.importTransportPlan(
|
||||
ExcelUtil.read(file, TransportPlanImportExcel.class), projectId, projectName, contractId, contractName, customerName);
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), TransportPlanImportExcel.class);
|
||||
return null;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
|
||||
+31
-12
@@ -19,6 +19,7 @@ import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.transport.excel.CargoReconciliationExcel;
|
||||
import org.springblade.transport.excel.CargoReconciliationFeeReader;
|
||||
@@ -126,12 +127,21 @@ public class TransportReconciliationController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入整车总额外部账单")
|
||||
public R importVehicle(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
|
||||
List<VehicleReconciliationExcel> rows = ExcelUtil.read(file, VehicleReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = VehicleReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
List<VehicleReconciliationFailureExcel> failures = reconciliationService.importVehicles(id, rows);
|
||||
if (!failures.isEmpty()) {
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", failures, VehicleReconciliationFailureExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
List<VehicleReconciliationExcel> rows = ExcelUtil.read(file, VehicleReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = VehicleReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
reconciliationService.importVehicles(id, rows);
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), VehicleReconciliationExcel.class);
|
||||
return null;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
@@ -141,12 +151,21 @@ public class TransportReconciliationController extends BladeController {
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导入货物明细外部账单")
|
||||
public R importCargo(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
|
||||
List<CargoReconciliationExcel> rows = ExcelUtil.read(file, CargoReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = CargoReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
List<CargoReconciliationFailureExcel> failures = reconciliationService.importCargoes(id, rows);
|
||||
if (!failures.isEmpty()) {
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", failures, CargoReconciliationFailureExcel.class);
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase();
|
||||
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
|
||||
return R.fail("请上传 .xls,.xlsx 标准格式文件");
|
||||
}
|
||||
try {
|
||||
List<CargoReconciliationExcel> rows = ExcelUtil.read(file, CargoReconciliationExcel.class);
|
||||
List<Map<String, BigDecimal>> feeItems = CargoReconciliationFeeReader.read(file);
|
||||
for (int index = 0; index < rows.size() && index < feeItems.size(); index++) rows.get(index).setFeeItems(feeItems.get(index));
|
||||
reconciliationService.importCargoes(id, rows);
|
||||
} catch (org.springblade.common.excel.ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误(含按列标红)。
|
||||
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), CargoReconciliationExcel.class);
|
||||
return null;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
|
||||
+3
-2
@@ -294,8 +294,9 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
|
||||
// 如果有校验错误,回滚事务
|
||||
if (Func.isNotEmpty(errorMap)) {
|
||||
org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return new ArrayList<>(errorMap.values());
|
||||
// 任一行失败即整批回滚:抛异常触发事务回滚(替代旧的 setRollbackOnly),
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||
}
|
||||
|
||||
// 第二阶段:批量导入
|
||||
|
||||
+28
-6
@@ -300,22 +300,33 @@ public class TransportReconciliationServiceImpl
|
||||
VehicleReconciliationExcel row = rows.get(index);
|
||||
List<String> validationErrors = validateImportVehicle(row);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
// 原因同时写回原行:异常携带的是原表 rows,
|
||||
// 导出工具从行对象的 errorMessage 字段取原因,只写副本会丢原因。
|
||||
row.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
VehicleReconciliationFailureExcel failure = new VehicleReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
failure.setErrorMessage(row.getErrorMessage());
|
||||
failures.add(failure);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
validExternals.add(buildVehicleExternal(id, index, row));
|
||||
} catch (Exception exception) {
|
||||
String message = org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败"));
|
||||
row.setErrorMessage(message);
|
||||
VehicleReconciliationFailureExcel failure = new VehicleReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败")));
|
||||
failure.setErrorMessage(message);
|
||||
failures.add(failure);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failures)) {
|
||||
// 任一行失败即整批回滚:抛异常触发事务回滚,使前置的 resetExternal
|
||||
// 清空与本批正确行的插入一并退掉;否则会出现「旧外部账单被清空、
|
||||
// 正确行却已入库」的数据损坏。
|
||||
throw new org.springblade.common.excel.ImportFailureException(rows);
|
||||
}
|
||||
for (TransportReconciliationExternal external : validExternals) {
|
||||
if (externalMapper.insert(external) <= 0) throw new ServiceException("外部账单保存失败");
|
||||
}
|
||||
@@ -418,22 +429,33 @@ public class TransportReconciliationServiceImpl
|
||||
CargoReconciliationExcel row = rows.get(index);
|
||||
List<String> validationErrors = validateImportCargo(row);
|
||||
if (Func.isNotEmpty(validationErrors)) {
|
||||
// 原因同时写回原行:异常携带的是原表 rows,
|
||||
// 导出工具从行对象的 errorMessage 字段取原因,只写副本会丢原因。
|
||||
row.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors));
|
||||
failure.setErrorMessage(row.getErrorMessage());
|
||||
failures.add(failure);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
validExternals.add(buildCargoExternal(id, index, row));
|
||||
} catch (Exception exception) {
|
||||
String message = org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败"));
|
||||
row.setErrorMessage(message);
|
||||
CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel();
|
||||
BeanUtil.copyProperties(row, failure);
|
||||
failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of(
|
||||
exception instanceof ServiceException ? exception.getMessage() : "导入失败")));
|
||||
failure.setErrorMessage(message);
|
||||
failures.add(failure);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failures)) {
|
||||
// 任一行失败即整批回滚:抛异常触发事务回滚,使前置的 resetExternal
|
||||
// 清空与本批正确行的插入一并退掉;否则会出现「旧外部账单被清空、
|
||||
// 正确行却已入库」的数据损坏。
|
||||
throw new org.springblade.common.excel.ImportFailureException(rows);
|
||||
}
|
||||
for (TransportReconciliationExternal external : validExternals) {
|
||||
if (externalMapper.insert(external) <= 0) throw new ServiceException("外部账单保存失败");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user