diff --git a/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java b/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java index 2723ee62..571eaced 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java +++ b/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java @@ -62,8 +62,12 @@ public class CoDelController { @PostMapping("/previewExcel") public R previewExcel(@RequestParam(value = "file") MultipartFile file, @ModelAttribute EcssCoDelNotifyHeaderData data){ - List> previewData = coDelService.previewExcel(file, data); - return R.ok().put("data", previewData); + Map result = coDelService.previewExcel(file, data); + return R.ok() + .put("data", result.get("previewList")) + .put("sheetErrors", result.get("sheetErrors")) + .put("hasErrors", result.get("hasErrors")) + .put("errorCount", result.get("errorCount")); } @PostMapping("/saveEcssCoDelNotifyByExcel") diff --git a/src/main/java/com/xujie/sys/modules/ecss/dto/SheetErrorInfo.java b/src/main/java/com/xujie/sys/modules/ecss/dto/SheetErrorInfo.java new file mode 100644 index 00000000..d7fb4d9f --- /dev/null +++ b/src/main/java/com/xujie/sys/modules/ecss/dto/SheetErrorInfo.java @@ -0,0 +1,93 @@ +package com.xujie.sys.modules.ecss.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Sheet错误信息DTO + * + *

用于收集Excel导入时各个Sheet的错误信息,而不中断整个导入流程

+ * + * @author System + * @since 2025-01-01 + */ +@Data +public class SheetErrorInfo { + + /** + * Sheet名称 + */ + private String sheetName; + + /** + * Sheet索引(从0开始) + */ + private Integer sheetIndex; + + /** + * 错误类型 + * SKIP_EMPTY: 跳过空Sheet + * SKIP_NO_CARGO_DATE: 跳过没有Cargo Ready Date列的Sheet + * SKIP_INTERNAL_SALE: 跳过包含内销的Sheet + * MISSING_COLUMNS: 缺少必填列 + * INVALID_DATA: 数据验证失败 + * PARSE_ERROR: 解析错误 + */ + private String errorType; + + /** + * 错误描述 + */ + private String errorMessage; + + /** + * 错误详情列表(如:具体哪些行有错误) + */ + private List errorDetails; + + /** + * 是否跳过该Sheet(true=跳过,false=处理失败) + */ + private Boolean skipped; + + /** + * 构造方法:创建跳过类型的错误信息 + */ + public static SheetErrorInfo createSkipped(String sheetName, Integer sheetIndex, String reason) { + SheetErrorInfo error = new SheetErrorInfo(); + error.setSheetName(sheetName); + error.setSheetIndex(sheetIndex); + error.setErrorType("SKIP"); + error.setErrorMessage(reason); + error.setSkipped(true); + error.setErrorDetails(new ArrayList<>()); + return error; + } + + /** + * 构造方法:创建错误类型的错误信息 + */ + public static SheetErrorInfo createError(String sheetName, Integer sheetIndex, String errorType, String errorMessage) { + SheetErrorInfo error = new SheetErrorInfo(); + error.setSheetName(sheetName); + error.setSheetIndex(sheetIndex); + error.setErrorType(errorType); + error.setErrorMessage(errorMessage); + error.setSkipped(false); + error.setErrorDetails(new ArrayList<>()); + return error; + } + + /** + * 添加错误详情 + */ + public void addErrorDetail(String detail) { + if (this.errorDetails == null) { + this.errorDetails = new ArrayList<>(); + } + this.errorDetails.add(detail); + } +} + diff --git a/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java b/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java index b2fc0a93..a63f8379 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java +++ b/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java @@ -23,7 +23,7 @@ public interface CoDelService { PageUtils searchEcssCoDelNotifyHeaderForCK(EcssCoDelNotifyHeaderData data); PageUtils searchEcssCoDelNotifyHeaderForDanZheng(EcssCoDelNotifyHeaderData data); List searchEcssCoDelNotifyDetail(EcssCoDelNotifyHeaderData data); - List> previewExcel(MultipartFile file, EcssCoDelNotifyHeaderData data); + Map previewExcel(MultipartFile file, EcssCoDelNotifyHeaderData data); Map> saveEcssCoDelNotifyByExcel(MultipartFile file, EcssCoDelNotifyHeaderData data, String deletedInvoices, HttpServletRequest request); void updateEcssDelHeader(EcssCoDelNotifyHeaderData data); diff --git a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java index 4e043a93..0fa570db 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java @@ -14,6 +14,7 @@ import com.aspose.cells.PageOrientationType; import com.aspose.cells.PaperSizeType; import com.xujie.sys.modules.attrbute.entity.PropertyModelHeader; import com.xujie.sys.modules.ecss.data.*; +import com.xujie.sys.modules.ecss.dto.SheetErrorInfo; import com.xujie.sys.modules.ecss.entity.*; import com.xujie.sys.modules.ecss.mapper.CoDelMapper; import com.xujie.sys.modules.ecss.service.CoDelService; @@ -100,14 +101,15 @@ public class CoDelServiceImpl implements CoDelService { } @Override - public List> previewExcel(MultipartFile file, EcssCoDelNotifyHeaderData inData) { + public Map previewExcel(MultipartFile file, EcssCoDelNotifyHeaderData inData) { SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); String site = coDelMapper.getSiteByBu(inData.getBuNo()); List excelList = new ArrayList<>(); + List sheetErrors = new ArrayList<>(); try (InputStream is = file.getInputStream()) { XSSFWorkbook workbook = new XSSFWorkbook(is); - importNotifyExcel(inData, workbook, site, currentUser, excelList); + importNotifyExcel(inData, workbook, site, currentUser, excelList, sheetErrors); } catch (Exception e) { throw new RuntimeException("文件解析失败:" + e.getMessage()); } @@ -164,7 +166,14 @@ public class CoDelServiceImpl implements CoDelService { previewList.add(summary); }); - return previewList; + // 构建返回结果,包含预览数据和Sheet错误信息 + Map result = new HashMap<>(); + result.put("previewList", previewList); + result.put("sheetErrors", sheetErrors); + result.put("hasErrors", !sheetErrors.isEmpty()); + result.put("errorCount", sheetErrors.size()); + + return result; } @Override @@ -173,10 +182,11 @@ public class CoDelServiceImpl implements CoDelService { SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); String site = coDelMapper.getSiteByBu(inData.getBuNo()); List excelList = new ArrayList<>(); + List sheetErrors = new ArrayList<>(); try (InputStream is = file.getInputStream()) { XSSFWorkbook workbook = new XSSFWorkbook(is); - importNotifyExcel(inData, workbook, site, currentUser, excelList); + importNotifyExcel(inData, workbook, site, currentUser, excelList, sheetErrors); } catch (NullPointerException e) { log.error("导入失败:{}", e.getMessage()); throw new RuntimeException("导入失败:网络错误,请重新上传文档"); @@ -223,6 +233,27 @@ public class CoDelServiceImpl implements CoDelService { List successList = new ArrayList<>(); List failList = new ArrayList<>(); + // 添加Sheet错误信息到失败列表 + for (SheetErrorInfo error : sheetErrors) { + if (error.getSkipped()) { + // 跳过类型的错误,作为警告信息 + failList.add("Sheet [" + error.getSheetName() + "] 已跳过:" + error.getErrorMessage()); + } else { + // 验证失败类型的错误 + failList.add("Sheet [" + error.getSheetName() + "] 错误:" + error.getErrorMessage()); + if (error.getErrorDetails() != null && !error.getErrorDetails().isEmpty()) { + // 限制显示前5个错误详情 + int limit = Math.min(5, error.getErrorDetails().size()); + for (int i = 0; i < limit; i++) { + failList.add(" - " + error.getErrorDetails().get(i)); + } + if (error.getErrorDetails().size() > 5) { + failList.add(" - ... 还有 " + (error.getErrorDetails().size() - 5) + " 个错误"); + } + } + } + } + // 添加物料不存在的发票到失败列表 for (Map.Entry> entry : invalidMaterialsByInvoice.entrySet()) { String invoice = entry.getKey(); @@ -418,12 +449,24 @@ public class CoDelServiceImpl implements CoDelService { return columnIndexMap; } + /** + * 导入通知单Excel + * + * @param inData 输入数据 + * @param workbook Excel工作簿 + * @param site 站点 + * @param currentUser 当前用户 + * @param excelList 解析后的数据列表 + * @param sheetErrors Sheet错误信息列表(收集所有错误) + */ private void importNotifyExcel(EcssCoDelNotifyHeaderData inData, XSSFWorkbook workbook, String site, - SysUserEntity currentUser, List excelList) { + SysUserEntity currentUser, List excelList, + List sheetErrors) { for (int s = 0; s < workbook.getNumberOfSheets(); s++) { // 读取工作表 XSSFSheet sheet = workbook.getSheetAt(s); String sheetName = sheet.getSheetName(); + SheetErrorInfo currentSheetError = null; // 当前Sheet的错误信息 try { // 剔除空行 @@ -471,7 +514,11 @@ public class CoDelServiceImpl implements CoDelService { } if (!missingColumns.isEmpty()) { - throw new RuntimeException("Sheet [" + sheetName + "] 表头缺少必填列: " + String.join(", ", missingColumns)); + currentSheetError = SheetErrorInfo.createError(sheetName, s, "MISSING_COLUMNS", + "表头缺少必填列: " + String.join(", ", missingColumns)); + sheetErrors.add(currentSheetError); + log.error("Sheet [{}] 表头缺少必填列,跳过该Sheet", sheetName); + continue; // 不抛异常,记录错误后跳过 } // 扫描整个 sheet,如果发现"内销"则跳过该 sheet @@ -511,6 +558,19 @@ public class CoDelServiceImpl implements CoDelService { continue; } + // 创建当前Sheet的错误收集器(如果还没创建的话) + if (currentSheetError == null) { + currentSheetError = new SheetErrorInfo(); + currentSheetError.setSheetName(sheetName); + currentSheetError.setSheetIndex(s); + currentSheetError.setErrorType("INVALID_DATA"); + currentSheetError.setErrorMessage("数据验证失败"); + currentSheetError.setSkipped(false); + currentSheetError.setErrorDetails(new ArrayList<>()); + } + + boolean hasRowError = false; // 标记当前Sheet是否有行错误 + // 遍历每一行(从第四行开始,即索引3) for (int j = 3; j < rows; j++) { // 获得该行 @@ -535,40 +595,53 @@ public class CoDelServiceImpl implements CoDelService { } // 必填字段验证 - Cargo Ready Date已经在上面检查过了 - Integer poIdx = columnMap.get("PO#"); if (row.getCell(poIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [PO#] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [PO#] 列不能为空"); + hasRowError = true; + continue; // 跳过这一行,继续处理下一行 } Integer pnIdx = columnMap.get("PN"); if (row.getCell(pnIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [PN] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [PN] 列不能为空"); + hasRowError = true; + continue; } Integer qtyIdx = columnMap.get("Qty (pcs)"); if (row.getCell(qtyIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [Qty (pcs)] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [Qty (pcs)] 列不能为空"); + hasRowError = true; + continue; } Integer destinationIdx = columnMap.get("Destination"); if (row.getCell(destinationIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [Destination] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [Destination] 列不能为空"); + hasRowError = true; + continue; } Integer shippingModeIdx = columnMap.get("Shipping Mode"); if (row.getCell(shippingModeIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [Shipping Mode] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [Shipping Mode] 列不能为空"); + hasRowError = true; + continue; } Integer currencyIdx = columnMap.get("Currency"); if (row.getCell(currencyIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [Currency] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [Currency] 列不能为空"); + hasRowError = true; + continue; } Integer tpIdx = columnMap.get("TP"); if (row.getCell(tpIdx) == null) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [TP] 列不能为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [TP] 列不能为空"); + hasRowError = true; + continue; } // 创建对象并为对象赋值 @@ -583,7 +656,9 @@ public class CoDelServiceImpl implements CoDelService { String formatted = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); readDate = DateUtils.getDateByParten(formatted, "yyyy-MM-dd"); } catch (Exception e) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [Cargo Ready Date] 列格式有误! " + e.getMessage()); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [Cargo Ready Date] 列格式有误: " + e.getMessage()); + hasRowError = true; + continue; } task.setReadyDate(readDate); @@ -606,7 +681,9 @@ public class CoDelServiceImpl implements CoDelService { boolean isShippingNumberValid = isValidCellValue(shippingNumberValue); if (!isCmcInvoiceValid && !isShippingNumberValid) { - throw new RuntimeException("Sheet [" + sheetName + "] 第" + (j+1) + "行的 [CMC Invoice] 和 [Shipping Number] 列不能同时为空!"); + currentSheetError.addErrorDetail("第" + (j+1) + "行的 [CMC Invoice] 和 [Shipping Number] 列不能同时为空"); + hasRowError = true; + continue; } // 如果CMC Invoice无效(空/0/错误/超过20位),则使用Shipping Number @@ -666,18 +743,25 @@ public class CoDelServiceImpl implements CoDelService { excelList.add(task); } + // 在Sheet处理结束时,如果有行错误,添加到错误列表 + if (hasRowError && currentSheetError != null) { + sheetErrors.add(currentSheetError); + log.error("Sheet [{}] 处理完成,发现 {} 个数据错误", sheetName, currentSheetError.getErrorDetails().size()); + } + } catch (Exception e) { - // 捕获并重新抛出异常,确保包含Sheet信息 + // 捕获异常并记录,不中断整个流程 String errorMsg = e.getMessage(); - if (errorMsg != null && errorMsg.contains("Sheet [")) { - // 异常消息已包含Sheet信息,直接抛出 - throw e; - } else { - // 异常消息不包含Sheet信息,添加Sheet信息后抛出 - throw new RuntimeException("Sheet [" + sheetName + "] 处理失败: " + errorMsg, e); - } + SheetErrorInfo errorInfo = SheetErrorInfo.createError(sheetName, s, "PARSE_ERROR", + "Sheet解析失败: " + (errorMsg != null ? errorMsg : e.getClass().getSimpleName())); + sheetErrors.add(errorInfo); + log.error("Sheet [{}] 处理异常: {}", sheetName, errorMsg, e); + // 继续处理下一个Sheet } } + + // 汇总日志 + log.info("=== Excel导入完成 === 成功处理 {} 条数据,发现 {} 个Sheet存在问题", excelList.size(), sheetErrors.size()); } public static LocalDate parseDateCell(Cell cell) { @@ -1106,12 +1190,22 @@ public class CoDelServiceImpl implements CoDelService { SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); String site = coDelMapper.getSiteByBu(inData.getBuNo()); List excelList = new ArrayList<>(); + List sheetErrors = new ArrayList<>(); try { // 转流 InputStream is = file.getInputStream(); // 读取工作簿 XSSFWorkbook workbook = new XSSFWorkbook(is); - importNotifyExcel(inData, workbook, site, currentUser, excelList); + importNotifyExcel(inData, workbook, site, currentUser, excelList, sheetErrors); + + // 如果有Sheet错误,抛出异常提示用户 + if (!sheetErrors.isEmpty()) { + StringBuilder errorMsg = new StringBuilder("以下Sheet存在问题:\n"); + for (SheetErrorInfo error : sheetErrors) { + errorMsg.append("Sheet [").append(error.getSheetName()).append("]: ").append(error.getErrorMessage()).append("\n"); + } + throw new RuntimeException(errorMsg.toString()); + } } catch (NullPointerException e) { throw new RuntimeException("导入失败:网络错误,请重新上传文档"); } catch (Exception e) { diff --git a/src/main/resources/mapper/ecss/EcssCommonMapper.xml b/src/main/resources/mapper/ecss/EcssCommonMapper.xml index f732db2f..79a1467a 100644 --- a/src/main/resources/mapper/ecss/EcssCommonMapper.xml +++ b/src/main/resources/mapper/ecss/EcssCommonMapper.xml @@ -33,7 +33,8 @@