diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParser.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParser.java index 1d1f8f3..cb54b3e 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParser.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParser.java @@ -33,6 +33,11 @@ public class TestReportLedgerExcelParser { private static final DataFormatter FORMATTER = new DataFormatter(); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final List REQUIRED_SHEETS = Arrays.asList( + TestReportConst.LEDGER_GUIDE_SHEET_NAME, + TestReportConst.LEDGER_POINT_SHEET_NAME, + TestReportConst.LEDGER_DEVICE_SHEET_NAME, + TestReportConst.LEDGER_CLIENT_SHEET_NAME); private static final List POINT_HEADERS = Arrays.asList( "分组号", "变电站", "监测点名称", "检测设备编号", "委托单位名称", "Excel附件名称", "Word附件名称"); private static final List DEVICE_HEADERS = Arrays.asList( @@ -42,17 +47,44 @@ public class TestReportLedgerExcelParser { public ParsedLedger parse(MultipartFile summaryFile, Map attachments) { String fileName = summaryFile == null ? null : StrUtil.trimToNull(summaryFile.getOriginalFilename()); - if (!TestReportConst.LEDGER_SUMMARY_FILE_NAME.equals(fileName)) { + Set attachmentNames = attachments == null ? Collections.emptySet() : attachments.keySet(); + try (InputStream inputStream = summaryFile.getInputStream()) { + return parse(fileName, inputStream, attachmentNames); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:读取台账汇总excel失败"); + } + } + + public ParsedLedger parse(String fileName, InputStream inputStream, Set attachmentNames) { + ParsedLedger ledger = parseForValidate(fileName, inputStream); + ensureRequiredSheetsPresent(ledger); + validateReferencedAttachments(ledger.getPoints(), attachmentNames); + return ledger; + } + + public ParsedLedger parseForValidate(String fileName, InputStream inputStream) { + String normalizedFileName = StrUtil.trimToNull(fileName); + if (!TestReportConst.LEDGER_SUMMARY_FILE_NAME.equals(normalizedFileName)) { throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:台账汇总文件名必须为台账信息汇总.xlsx"); } - try (InputStream inputStream = summaryFile.getInputStream(); - Workbook workbook = WorkbookFactory.create(inputStream)) { + try (Workbook workbook = WorkbookFactory.create(inputStream)) { ParsedLedger ledger = new ParsedLedger(); - ledger.setSummaryFileName(fileName); - ledger.setPoints(parsePointSheet(requireSheet(workbook, TestReportConst.LEDGER_POINT_SHEET_NAME), attachments)); - ledger.setDevices(parseDeviceSheet(workbook.getSheet(TestReportConst.LEDGER_DEVICE_SHEET_NAME))); - ledger.setClients(parseClientSheet(workbook.getSheet(TestReportConst.LEDGER_CLIENT_SHEET_NAME))); - validateReferencedAttachments(ledger.getPoints(), attachments); + ledger.setSummaryFileName(TestReportConst.LEDGER_SUMMARY_FILE_NAME); + ledger.getRequiredSheets().addAll(REQUIRED_SHEETS); + collectSheets(ledger, workbook); + + Sheet pointSheet = workbook.getSheet(TestReportConst.LEDGER_POINT_SHEET_NAME); + if (pointSheet != null) { + ledger.setPoints(parsePointSheet(pointSheet)); + } + Sheet deviceSheet = workbook.getSheet(TestReportConst.LEDGER_DEVICE_SHEET_NAME); + if (deviceSheet != null) { + ledger.setDevices(parseDeviceSheet(deviceSheet)); + } + Sheet clientSheet = workbook.getSheet(TestReportConst.LEDGER_CLIENT_SHEET_NAME); + if (clientSheet != null) { + ledger.setClients(parseClientSheet(clientSheet)); + } return ledger; } catch (BusinessException exception) { throw exception; @@ -63,15 +95,25 @@ public class TestReportLedgerExcelParser { } } - private Sheet requireSheet(Workbook workbook, String sheetName) { - Sheet sheet = workbook.getSheet(sheetName); - if (sheet == null) { - throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:缺少" + sheetName + "sheet"); + private void collectSheets(ParsedLedger ledger, Workbook workbook) { + for (String sheetName : REQUIRED_SHEETS) { + Sheet sheet = workbook.getSheet(sheetName); + if (sheet == null) { + ledger.getMissingSheets().add(sheetName); + continue; + } + ledger.getExistingSheets().add(sheetName); } - return sheet; } - private List parsePointSheet(Sheet sheet, Map attachments) { + private void ensureRequiredSheetsPresent(ParsedLedger ledger) { + if (!ledger.getMissingSheets().isEmpty()) { + throw new BusinessException(CommonResponseEnum.FAIL, + "校验文件阶段:缺少" + String.join("、", ledger.getMissingSheets()) + "sheet"); + } + } + + private List parsePointSheet(Sheet sheet) { validateHeaders(sheet, POINT_HEADERS, "监测点信息"); List points = new ArrayList(); Set pointKeys = new HashSet(); @@ -95,12 +137,6 @@ public class TestReportLedgerExcelParser { "第" + (rowIndex + 1) + "行Excel附件扩展名仅允许xls/xlsx"); validateExtension(point.getWordAttachmentName(), Arrays.asList(".doc", ".docx"), "第" + (rowIndex + 1) + "行Word附件扩展名仅允许doc/docx"); - if (!attachments.containsKey(point.getExcelAttachmentName())) { - throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:第" + (rowIndex + 1) + "行缺少Excel附件"); - } - if (!attachments.containsKey(point.getWordAttachmentName())) { - throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:第" + (rowIndex + 1) + "行缺少Word附件"); - } String pointKey = point.getSubstationName() + "|" + point.getPointName(); if (!pointKeys.add(pointKey)) { throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:监测点信息中存在重复点位"); @@ -120,9 +156,6 @@ public class TestReportLedgerExcelParser { } private List parseDeviceSheet(Sheet sheet) { - if (sheet == null) { - return Collections.emptyList(); - } validateHeaders(sheet, DEVICE_HEADERS, "检测设备"); List devices = new ArrayList(); Set deviceNos = new HashSet(); @@ -147,9 +180,6 @@ public class TestReportLedgerExcelParser { } private List parseClientSheet(Sheet sheet) { - if (sheet == null) { - return Collections.emptyList(); - } validateHeaders(sheet, CLIENT_HEADERS, "委托单位"); List clients = new ArrayList(); Set clientNames = new HashSet(); @@ -172,13 +202,25 @@ public class TestReportLedgerExcelParser { return clients; } - private void validateReferencedAttachments(List points, Map attachments) { + private void validateReferencedAttachments(List points, Set attachmentNames) { Set referencedNames = new HashSet(); for (ParsedPointRow point : points) { - referencedNames.add(point.getExcelAttachmentName()); - referencedNames.add(point.getWordAttachmentName()); + if (StrUtil.isNotBlank(point.getExcelAttachmentName())) { + referencedNames.add(point.getExcelAttachmentName()); + } + if (StrUtil.isNotBlank(point.getWordAttachmentName())) { + referencedNames.add(point.getWordAttachmentName()); + } } - for (String attachmentName : attachments.keySet()) { + for (ParsedPointRow point : points) { + if (!attachmentNames.contains(point.getExcelAttachmentName())) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:第" + point.getRowNo() + "行缺少Excel附件"); + } + if (!attachmentNames.contains(point.getWordAttachmentName())) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:第" + point.getRowNo() + "行缺少Word附件"); + } + } + for (String attachmentName : attachmentNames) { if (!referencedNames.contains(attachmentName)) { throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:存在未在监测点信息sheet中引用的附件 " + attachmentName); } @@ -263,6 +305,9 @@ public class TestReportLedgerExcelParser { @Data public static class ParsedLedger { private String summaryFileName; + private List requiredSheets = new ArrayList(); + private List existingSheets = new ArrayList(); + private List missingSheets = new ArrayList(); private List points = new ArrayList(); private List devices = new ArrayList(); private List clients = new ArrayList(); diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportService.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportService.java index fcc092b..1b31c2d 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportService.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportService.java @@ -23,9 +23,14 @@ import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO; import com.njcn.gather.aireport.testreport.pojo.po.TestReportPointPO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportStageVO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateAttachmentVO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidatePointVO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +import java.io.InputStream; +import java.nio.file.Files; import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; @@ -42,6 +47,7 @@ import java.util.stream.Collectors; public class TestReportLedgerImportService { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String BATCH_STATUS_VALIDATED = "validated"; private final TestReportMapper testReportMapper; private final TestReportPointMapper testReportPointMapper; @@ -67,36 +73,315 @@ public class TestReportLedgerImportService { this.testReportStorageService = testReportStorageService; } - public TestReportLedgerImportResultVO importLedger(TestReportPO report, MultipartFile[] files, String userId) { + public TestReportLedgerValidateResultVO validateLedgerSession(String sessionId, MultipartFile[] files) { Map fileMap = normalizeFileMap(files); - MultipartFile summaryFile = fileMap.remove(TestReportConst.LEDGER_SUMMARY_FILE_NAME); - if (summaryFile == null) { - throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:缺少台账信息汇总.xlsx"); + TestReportStorageService.UploadedBatch uploadedBatch = testReportStorageService.saveValidateSessionFiles(sessionId, fileMap); + List sessionFileNames = testReportStorageService.listValidateSessionFileNames(sessionId); + Set sessionFileNameSet = new HashSet(sessionFileNames); + TestReportLedgerValidateResultVO result = new TestReportLedgerValidateResultVO(); + result.setSessionId(sessionId); + result.setTempStoragePath(uploadedBatch.getTempStoragePath()); + result.setUploadedFileCount(sessionFileNames.size()); + result.setCanContinueUpload(Boolean.TRUE); + result.setSummaryFileName(TestReportConst.LEDGER_SUMMARY_FILE_NAME); + result.setSummaryFilePresent(sessionFileNameSet.contains(TestReportConst.LEDGER_SUMMARY_FILE_NAME)); + if (!Boolean.TRUE.equals(result.getSummaryFilePresent())) { + result.setSuccess(Boolean.FALSE); + result.getFailReasons().add("鏍¢獙鏂囦欢闃舵锛氱己灏戝彴璐︿俊鎭眹鎬?xlsx"); + persistValidateSessionResult(sessionId, result); + return result; } - TestReportLedgerExcelParser.ParsedLedger ledger = testReportLedgerExcelParser.parse(summaryFile, fileMap); + try (InputStream inputStream = Files.newInputStream( + testReportStorageService.resolveValidateSessionFile(sessionId, TestReportConst.LEDGER_SUMMARY_FILE_NAME))) { + TestReportLedgerExcelParser.ParsedLedger ledger = testReportLedgerExcelParser.parseForValidate( + TestReportConst.LEDGER_SUMMARY_FILE_NAME, inputStream); + Map dbDeviceMap = loadExistingDevices(collectDeviceNos(ledger)); + Map dbClientMap = loadExistingClients(collectClientNames(ledger)); + result.getRequiredSheets().addAll(ledger.getRequiredSheets()); + result.getExistingSheets().addAll(ledger.getExistingSheets()); + result.getMissingSheets().addAll(ledger.getMissingSheets()); + fillPointValidationResult(result, ledger, sessionFileNameSet, dbDeviceMap, dbClientMap); + result.getOrphanAttachmentNames().addAll(resolveOrphanAttachmentNames(ledger, sessionFileNameSet)); + result.setSuccess(result.getMissingSheets().isEmpty() && result.getMissingAttachmentCount() != null + && result.getMissingAttachmentCount() == 0 && !hasReferenceErrors(result.getPoints())); + if (!result.getMissingSheets().isEmpty()) { + result.getFailReasons().add("鏍¢獙鏂囦欢闃舵锛歋heet涓嶄笉榻? " + String.join("銆?", result.getMissingSheets())); + } + if (result.getMissingAttachmentCount() != null && result.getMissingAttachmentCount() > 0) { + result.getFailReasons().add("鏍¢獙鏂囦欢闃舵锛氬瓨鍦ㄧ己澶遍檮浠讹紝鍙户缁笂浼犲悗鍐嶆鏍¢獙"); + } + appendReferenceFailReasons(result); + persistValidateSessionResult(sessionId, result); + return result; + } catch (BusinessException exception) { + throw exception; + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏍¢獙鏂囦欢闃舵锛氳鍙栧彴璐︽眹鎬籩xcel澶辫触"); + } + } + + public TestReportLedgerImportResultVO validateLedger(TestReportPO report, MultipartFile[] files, String userId) { + Map fileMap = normalizeFileMap(files); + String batchId = UUID.randomUUID().toString(); + TestReportStorageService.UploadedBatch uploadedBatch = testReportStorageService.saveTempBatchFiles(report.getId(), batchId, fileMap); + try { + MultipartFile summaryFile = fileMap.get(TestReportConst.LEDGER_SUMMARY_FILE_NAME); + if (summaryFile == null) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:缺少台账信息汇总.xlsx"); + } + TestReportLedgerExcelParser.ParsedLedger ledger = parseLedgerFromBatch(report.getId(), batchId, fileMap.keySet()); + Map dbDeviceMap = loadExistingDevices(collectDeviceNos(ledger)); + Map dbClientMap = loadExistingClients(collectClientNames(ledger)); + validatePointReferences(ledger.getPoints(), dbDeviceMap, dbClientMap, ledger.toDeviceMap(), ledger.toClientMap()); + writeValidatedBatchMeta(report.getId(), batchId, uploadedBatch, ledger, userId); + return buildValidateSuccessResult(batchId, uploadedBatch.getTempStoragePath(), ledger, fileMap.size()); + } catch (RuntimeException exception) { + throw exception; + } + } + + public TestReportLedgerImportResultVO importLedger(TestReportPO report, String batchId, String userId) { + BatchMeta batchMeta = loadValidatedBatchMeta(report.getId(), batchId); + TestReportLedgerExcelParser.ParsedLedger ledger = parseLedgerFromBatch(report.getId(), batchId, batchMeta.toFileNameSet()); Map dbDeviceMap = loadExistingDevices(collectDeviceNos(ledger)); Map dbClientMap = loadExistingClients(collectClientNames(ledger)); - Map sheetDeviceMap = ledger.toDeviceMap(); - Map sheetClientMap = ledger.toClientMap(); - validatePointReferences(ledger.getPoints(), dbDeviceMap, dbClientMap, sheetDeviceMap, sheetClientMap); - BaseDataSummary baseDataSummary = maintainBaseData(ledger, dbDeviceMap, dbClientMap, userId); + List oldPoints = loadNormalPoints(report.getId()); List oldGroupReports = loadNormalGroupReports(report.getId()); deleteOldFiles(oldPoints, oldGroupReports, report.getData()); removeOldRows(report.getId()); - String summaryStoragePath = testReportStorageService.saveLedgerSummaryExcel(report.getId(), summaryFile); - int groupCount = saveNewPoints(report.getId(), ledger.getPoints(), fileMap, userId); + String summaryStoragePath = testReportStorageService.saveLedgerSummaryFromTemp(report.getId(), batchId, + batchMeta.getSummaryFileName()); + int groupCount = saveNewPoints(report.getId(), batchId, ledger.getPoints(), userId); LocalDateTime now = LocalDateTime.now(); report.setData(buildSnapshotJson(ledger, summaryStoragePath, baseDataSummary, groupCount)); report.setReportGenerateState(TestReportConst.REPORT_GENERATE_STATE_PENDING); report.setUpdateBy(userId); report.setUpdateTime(now); testReportMapper.updateById(report); + testReportStorageService.deleteTempBatchQuietly(report.getId(), batchId); + return buildImportSuccessResult(batchId, batchMeta.getTempStoragePath(), ledger, baseDataSummary, + batchMeta.getUploadedFileCount(), groupCount); + } - return buildSuccessResult(ledger, baseDataSummary, fileMap.size() + 1, groupCount); + private TestReportLedgerExcelParser.ParsedLedger parseLedgerFromBatch(String reportId, String batchId, Collection fileNames) { + try (InputStream inputStream = java.nio.file.Files.newInputStream( + testReportStorageService.resolveTempBatchFile(reportId, batchId, TestReportConst.LEDGER_SUMMARY_FILE_NAME))) { + return testReportLedgerExcelParser.parse(TestReportConst.LEDGER_SUMMARY_FILE_NAME, inputStream, + buildAttachmentNameSet(fileNames)); + } catch (java.io.IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:读取台账汇总excel失败"); + } + } + + private Set buildAttachmentNameSet(Collection fileNames) { + Set attachmentNames = new HashSet(); + if (fileNames == null || fileNames.isEmpty()) { + return attachmentNames; + } + for (String fileName : fileNames) { + if (StrUtil.isBlank(fileName) || TestReportConst.LEDGER_SUMMARY_FILE_NAME.equals(fileName)) { + continue; + } + attachmentNames.add(fileName); + } + return attachmentNames; + } + + private void fillPointValidationResult(TestReportLedgerValidateResultVO result, + TestReportLedgerExcelParser.ParsedLedger ledger, + Set sessionFileNameSet, + Map dbDeviceMap, + Map dbClientMap) { + int missingAttachmentCount = 0; + Map sheetDeviceMap = ledger.toDeviceMap(); + Map sheetClientMap = ledger.toClientMap(); + for (TestReportLedgerExcelParser.ParsedPointRow pointRow : ledger.getPoints()) { + TestReportLedgerValidatePointVO pointVO = new TestReportLedgerValidatePointVO(); + pointVO.setRowNo(pointRow.getRowNo()); + pointVO.setSubstationName(pointRow.getSubstationName()); + pointVO.setPointName(pointRow.getPointName()); + pointVO.setPointKey(pointRow.getSubstationName() + "|" + pointRow.getPointName()); + + TestReportLedgerValidateAttachmentVO excelAttachment = buildAttachmentRow( + pointRow.getExcelAttachmentName(), "excel", sessionFileNameSet.contains(pointRow.getExcelAttachmentName())); + TestReportLedgerValidateAttachmentVO wordAttachment = buildAttachmentRow( + pointRow.getWordAttachmentName(), "word", sessionFileNameSet.contains(pointRow.getWordAttachmentName())); + + pointVO.getAttachments().add(excelAttachment); + pointVO.getAttachments().add(wordAttachment); + + int pointMissingCount = 0; + if (Boolean.TRUE.equals(excelAttachment.getMissing())) { + pointMissingCount++; + } + if (Boolean.TRUE.equals(wordAttachment.getMissing())) { + pointMissingCount++; + } + pointVO.setMissingAttachmentCount(pointMissingCount); + fillReferenceValidationResult(pointVO, pointRow, dbDeviceMap, dbClientMap, sheetDeviceMap, sheetClientMap); + pointVO.setComplete(pointMissingCount == 0 + && !Boolean.FALSE.equals(pointVO.getTestDeviceNoValid()) + && !Boolean.FALSE.equals(pointVO.getClientUnitNameValid())); + missingAttachmentCount += pointMissingCount; + result.getPoints().add(pointVO); + } + result.setPointCount(result.getPoints().size()); + result.setMissingAttachmentCount(missingAttachmentCount); + } + + private void fillReferenceValidationResult(TestReportLedgerValidatePointVO pointVO, + TestReportLedgerExcelParser.ParsedPointRow pointRow, + Map dbDeviceMap, + Map dbClientMap, + Map sheetDeviceMap, + Map sheetClientMap) { + if (StrUtil.isBlank(pointRow.getTestDeviceNo())) { + pointVO.setTestDeviceNoValid(Boolean.TRUE); + pointVO.setTestDeviceNoMessage("检测设备编号为空,跳过校验"); + } else if (dbDeviceMap.containsKey(pointRow.getTestDeviceNo()) || sheetDeviceMap.containsKey(pointRow.getTestDeviceNo())) { + pointVO.setTestDeviceNoValid(Boolean.TRUE); + pointVO.setTestDeviceNoMessage("检测设备编号校验通过"); + } else { + pointVO.setTestDeviceNoValid(Boolean.FALSE); + pointVO.setTestDeviceNoMessage("第" + pointRow.getRowNo() + "行检测设备编号在数据库和检测设备sheet中都不存在"); + } + if (StrUtil.isBlank(pointRow.getClientUnitName())) { + pointVO.setClientUnitNameValid(Boolean.TRUE); + pointVO.setClientUnitNameMessage("委托单位名称为空,跳过校验"); + } else if (dbClientMap.containsKey(pointRow.getClientUnitName()) || sheetClientMap.containsKey(pointRow.getClientUnitName())) { + pointVO.setClientUnitNameValid(Boolean.TRUE); + pointVO.setClientUnitNameMessage("委托单位名称校验通过"); + } else { + pointVO.setClientUnitNameValid(Boolean.FALSE); + pointVO.setClientUnitNameMessage("第" + pointRow.getRowNo() + "行委托单位名称在数据库和委托单位sheet中都不存在"); + } + } + + private TestReportLedgerValidateAttachmentVO buildAttachmentRow(String attachmentName, String attachmentType, + boolean uploaded) { + TestReportLedgerValidateAttachmentVO attachmentVO = new TestReportLedgerValidateAttachmentVO(); + attachmentVO.setAttachmentName(attachmentName); + attachmentVO.setAttachmentType(attachmentType); + attachmentVO.setReferenced(Boolean.TRUE); + attachmentVO.setUploaded(uploaded); + attachmentVO.setMissing(!uploaded); + attachmentVO.setReuploadAllowed(Boolean.TRUE); + attachmentVO.setMessage(uploaded ? "宸蹭笂浼?" : "缂哄皯闄勪欢锛屽彲缁х画涓婁紶"); + return attachmentVO; + } + + private List resolveOrphanAttachmentNames(TestReportLedgerExcelParser.ParsedLedger ledger, Set sessionFileNameSet) { + Set referencedNames = new HashSet(); + for (TestReportLedgerExcelParser.ParsedPointRow pointRow : ledger.getPoints()) { + if (StrUtil.isNotBlank(pointRow.getExcelAttachmentName())) { + referencedNames.add(pointRow.getExcelAttachmentName()); + } + if (StrUtil.isNotBlank(pointRow.getWordAttachmentName())) { + referencedNames.add(pointRow.getWordAttachmentName()); + } + } + List orphanAttachmentNames = new java.util.ArrayList(); + for (String fileName : sessionFileNameSet) { + if (TestReportConst.LEDGER_SUMMARY_FILE_NAME.equals(fileName)) { + continue; + } + if (!referencedNames.contains(fileName)) { + orphanAttachmentNames.add(fileName); + } + } + return orphanAttachmentNames; + } + + private void persistValidateSessionResult(String sessionId, TestReportLedgerValidateResultVO result) { + try { + testReportStorageService.writeValidateSessionResult(sessionId, OBJECT_MAPPER.writeValueAsString(result)); + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏍¢獙鏂囦欢闃舵锛氫繚瀛樻牎楠岀粨鏋滃け璐?"); + } + } + + private boolean hasReferenceErrors(List points) { + if (points == null || points.isEmpty()) { + return false; + } + for (TestReportLedgerValidatePointVO point : points) { + if (point == null) { + continue; + } + if (Boolean.FALSE.equals(point.getTestDeviceNoValid()) || Boolean.FALSE.equals(point.getClientUnitNameValid())) { + return true; + } + } + return false; + } + + private void appendReferenceFailReasons(TestReportLedgerValidateResultVO result) { + if (result == null || result.getPoints() == null || result.getPoints().isEmpty()) { + return; + } + boolean hasDeviceError = false; + boolean hasClientError = false; + for (TestReportLedgerValidatePointVO point : result.getPoints()) { + if (point == null) { + continue; + } + if (Boolean.FALSE.equals(point.getTestDeviceNoValid())) { + hasDeviceError = true; + } + if (Boolean.FALSE.equals(point.getClientUnitNameValid())) { + hasClientError = true; + } + } + if (hasDeviceError) { + result.getFailReasons().add("监测点信息中存在检测设备编号未匹配到数据库或检测设备sheet的记录"); + } + if (hasClientError) { + result.getFailReasons().add("监测点信息中存在委托单位名称未匹配到数据库或委托单位sheet的记录"); + } + } + + private void writeValidatedBatchMeta(String reportId, String batchId, TestReportStorageService.UploadedBatch uploadedBatch, + TestReportLedgerExcelParser.ParsedLedger ledger, String userId) { + BatchMeta batchMeta = new BatchMeta(); + batchMeta.setBatchId(batchId); + batchMeta.setReportId(reportId); + batchMeta.setStatus(BATCH_STATUS_VALIDATED); + batchMeta.setSummaryFileName(ledger.getSummaryFileName()); + batchMeta.setTempStoragePath(uploadedBatch.getTempStoragePath()); + batchMeta.setUploadedFileCount(uploadedBatch.getStoredFileMap().size()); + batchMeta.setUploadedFileNames(uploadedBatch.getStoredFileMap().keySet().stream().collect(Collectors.toList())); + batchMeta.setValidatedBy(userId); + batchMeta.setValidatedTime(LocalDateTime.now().toString()); + try { + testReportStorageService.writeTempBatchMeta(reportId, batchId, OBJECT_MAPPER.writeValueAsString(batchMeta)); + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:保存批次元数据失败"); + } + } + + private BatchMeta loadValidatedBatchMeta(String reportId, String batchId) { + if (StrUtil.isBlank(batchId)) { + throw new BusinessException(CommonResponseEnum.FAIL, "导入批次ID不能为空"); + } + String metaText = testReportStorageService.readTempBatchMeta(reportId, batchId); + if (StrUtil.isBlank(metaText)) { + throw new BusinessException(CommonResponseEnum.FAIL, "导入批次不存在或未通过校验"); + } + try { + BatchMeta batchMeta = OBJECT_MAPPER.readValue(metaText, BatchMeta.class); + if (!reportId.equals(batchMeta.getReportId()) || !BATCH_STATUS_VALIDATED.equals(batchMeta.getStatus())) { + throw new BusinessException(CommonResponseEnum.FAIL, "导入批次不存在或未通过校验"); + } + return batchMeta; + } catch (BusinessException exception) { + throw exception; + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "读取导入批次信息失败"); + } } private Map normalizeFileMap(MultipartFile[] files) { @@ -160,7 +445,7 @@ public class TestReportLedgerImportService { String typeId = typeNameIdMap.get(deviceRow.getTypeName()); if (StrUtil.isBlank(typeId)) { throw new BusinessException(CommonResponseEnum.FAIL, - "基础资料维护阶段:检测设备sheet第" + deviceRow.getRowNo() + "行设备型号不存在"); + "基础信息维护阶段:检测设备sheet第" + deviceRow.getRowNo() + "行设备型号不存在"); } TestDevicePO device = new TestDevicePO(); device.setId(UUID.randomUUID().toString()); @@ -203,14 +488,12 @@ public class TestReportLedgerImportService { return summary; } - private int saveNewPoints(String reportId, List points, - Map fileMap, String userId) { + private int saveNewPoints(String reportId, String batchId, List points, + String userId) { LocalDateTime now = LocalDateTime.now(); Set groupNos = new HashSet(); int sortNo = 1; for (TestReportLedgerExcelParser.ParsedPointRow pointRow : points) { - MultipartFile excelFile = fileMap.get(pointRow.getExcelAttachmentName()); - MultipartFile wordFile = fileMap.get(pointRow.getWordAttachmentName()); TestReportPointPO point = new TestReportPointPO(); point.setId(UUID.randomUUID().toString()); point.setReportId(reportId); @@ -220,9 +503,11 @@ public class TestReportLedgerImportService { point.setTestDeviceNo(pointRow.getTestDeviceNo()); point.setClientUnitName(pointRow.getClientUnitName()); point.setExcelAttachmentName(pointRow.getExcelAttachmentName()); - point.setExcelStoragePath(testReportStorageService.savePointExcelAttachment(reportId, excelFile)); + point.setExcelStoragePath(testReportStorageService.savePointExcelFromTemp(reportId, batchId, + pointRow.getExcelAttachmentName())); point.setWordAttachmentName(pointRow.getWordAttachmentName()); - point.setWordStoragePath(testReportStorageService.savePointWordAttachment(reportId, wordFile)); + point.setWordStoragePath(testReportStorageService.savePointWordFromTemp(reportId, batchId, + pointRow.getWordAttachmentName())); point.setSortNo(sortNo++); point.setDeleted(TestReportConst.DELETED_NO); point.setCreateBy(userId); @@ -297,13 +582,37 @@ public class TestReportLedgerImportService { return root.toString(); } - private TestReportLedgerImportResultVO buildSuccessResult(TestReportLedgerExcelParser.ParsedLedger ledger, - BaseDataSummary summary, int totalFileCount, int groupCount) { + private TestReportLedgerImportResultVO buildValidateSuccessResult(String batchId, String tempStoragePath, + TestReportLedgerExcelParser.ParsedLedger ledger, + int uploadedFileCount) { TestReportLedgerImportResultVO result = new TestReportLedgerImportResultVO(); + result.setBatchId(batchId); + result.setCurrentStage("文件校验"); + result.setSuccess(Boolean.TRUE); + result.setSummaryFileName(ledger.getSummaryFileName()); + result.setTempStoragePath(tempStoragePath); + result.setUploadedFileCount(uploadedFileCount); + result.setTotalFileCount(uploadedFileCount); + result.setPointCount(ledger.getPoints().size()); + result.setExcelAttachmentCount(ledger.getPoints().size()); + result.setWordAttachmentCount(ledger.getPoints().size()); + result.getStages().add(buildStage("文件选择", "文件选择完成")); + result.getStages().add(buildStage("文件校验", "文件校验通过")); + return result; + } + + private TestReportLedgerImportResultVO buildImportSuccessResult(String batchId, String tempStoragePath, + TestReportLedgerExcelParser.ParsedLedger ledger, + BaseDataSummary summary, int uploadedFileCount, + int groupCount) { + TestReportLedgerImportResultVO result = new TestReportLedgerImportResultVO(); + result.setBatchId(batchId); result.setCurrentStage("完成"); result.setSuccess(Boolean.TRUE); result.setSummaryFileName(ledger.getSummaryFileName()); - result.setTotalFileCount(totalFileCount); + result.setTempStoragePath(tempStoragePath); + result.setUploadedFileCount(uploadedFileCount); + result.setTotalFileCount(uploadedFileCount); result.setPointCount(ledger.getPoints().size()); result.setExcelAttachmentCount(ledger.getPoints().size()); result.setWordAttachmentCount(ledger.getPoints().size()); @@ -312,10 +621,10 @@ public class TestReportLedgerImportService { result.setClientAddedCount(summary.getClientAddedCount()); result.setClientReuseCount(summary.getClientReuseCount()); result.setGroupCount(groupCount); - result.getStages().add(buildStage("选择文件", "文件选择完成")); - result.getStages().add(buildStage("校验文件", "文件校验通过")); - result.getStages().add(buildStage("文件上传", "文件上传完成")); - result.getStages().add(buildStage("基础资料维护", "基础资料维护完成")); + result.getStages().add(buildStage("文件选择", "文件选择完成")); + result.getStages().add(buildStage("文件校验", "已复用已校验批次")); + result.getStages().add(buildStage("文件导入", "正式文件和点位数据已导入")); + result.getStages().add(buildStage("基础信息维护", "基础信息维护完成")); result.getStages().add(buildStage("完成", "导入完成")); return result; } @@ -425,7 +734,7 @@ public class TestReportLedgerImportService { return TestDeviceConst.STATE_RETIRED; } throw new BusinessException(CommonResponseEnum.FAIL, - "基础资料维护阶段:检测设备sheet第" + rowNo + "行设备状态不正确"); + "基础信息维护阶段:检测设备sheet第" + rowNo + "行设备状态不正确"); } private String defaultEmpty(String value) { @@ -433,7 +742,8 @@ public class TestReportLedgerImportService { } private String normalizeFileName(String fileName) { - return fileName == null ? null : fileName.trim(); + String normalized = fileName == null ? null : fileName.trim(); + return StrUtil.isBlank(normalized) ? null : normalized; } private static class BaseDataSummary { @@ -474,4 +784,92 @@ public class TestReportLedgerImportService { this.clientReuseCount = clientReuseCount; } } + + public static class BatchMeta { + private String batchId; + private String reportId; + private String status; + private String summaryFileName; + private String tempStoragePath; + private Integer uploadedFileCount; + private List uploadedFileNames; + private String validatedBy; + private String validatedTime; + + public Set toFileNameSet() { + return uploadedFileNames == null ? Collections.emptySet() : new HashSet(uploadedFileNames); + } + + public String getBatchId() { + return batchId; + } + + public void setBatchId(String batchId) { + this.batchId = batchId; + } + + public String getReportId() { + return reportId; + } + + public void setReportId(String reportId) { + this.reportId = reportId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getSummaryFileName() { + return summaryFileName; + } + + public void setSummaryFileName(String summaryFileName) { + this.summaryFileName = summaryFileName; + } + + public String getTempStoragePath() { + return tempStoragePath; + } + + public void setTempStoragePath(String tempStoragePath) { + this.tempStoragePath = tempStoragePath; + } + + public Integer getUploadedFileCount() { + return uploadedFileCount; + } + + public void setUploadedFileCount(Integer uploadedFileCount) { + this.uploadedFileCount = uploadedFileCount; + } + + public List getUploadedFileNames() { + return uploadedFileNames; + } + + public void setUploadedFileNames(List uploadedFileNames) { + this.uploadedFileNames = uploadedFileNames; + } + + public String getValidatedBy() { + return validatedBy; + } + + public void setValidatedBy(String validatedBy) { + this.validatedBy = validatedBy; + } + + public String getValidatedTime() { + return validatedTime; + } + + public void setValidatedTime(String validatedTime) { + this.validatedTime = validatedTime; + } + } } diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportStorageService.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportStorageService.java index 3d71570..fc1067f 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportStorageService.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/component/TestReportStorageService.java @@ -9,9 +9,15 @@ import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.UUID; @Component @@ -21,35 +27,146 @@ public class TestReportStorageService { private String filesRootPath; public String saveSourceExcel(String reportId, MultipartFile file) { - return saveMultipartFile(reportId, TestReportConst.STORAGE_SOURCE_EXCEL_DIR, file, "保存原始Excel失败"); + return saveMultipartFile(reportId, TestReportConst.STORAGE_SOURCE_EXCEL_DIR, file, "淇濆瓨鍘熷Excel澶辫触"); } public String saveLedgerSummaryExcel(String reportId, MultipartFile file) { - return saveMultipartFile(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR, file, "保存台账汇总Excel失败"); + return saveMultipartFile(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR, file, "淇濆瓨鍙拌处姹囨€籈xcel澶辫触"); } public String savePointExcelAttachment(String reportId, MultipartFile file) { - return saveMultipartFile(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR, file, "保存监测点Excel附件失败"); + return saveMultipartFile(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR, file, "淇濆瓨鐩戞祴鐐笶xcel闄勪欢澶辫触"); } public String savePointWordAttachment(String reportId, MultipartFile file) { - return saveMultipartFile(reportId, TestReportConst.STORAGE_POINT_WORD_DIR, file, "保存监测点Word附件失败"); + return saveMultipartFile(reportId, TestReportConst.STORAGE_POINT_WORD_DIR, file, "淇濆瓨鐩戞祴鐐筗ord闄勪欢澶辫触"); } - private String saveMultipartFile(String reportId, String childDir, MultipartFile file, String errorMessage) { - String originalFileName = file == null ? null : file.getOriginalFilename(); - if (file == null || file.isEmpty() || StrUtil.isBlank(originalFileName)) { + public UploadedBatch saveTempBatchFiles(String reportId, String batchId, Map fileMap) { + if (fileMap == null || fileMap.isEmpty()) { throw new BusinessException(CommonResponseEnum.FAIL, "文件不能为空"); } - String fileName = UUID.randomUUID().toString().replace("-", "") + "-" + Paths.get(originalFileName).getFileName(); - Path targetPath = resolveReportDir(reportId, childDir).resolve(fileName); - try { - Files.createDirectories(targetPath.getParent()); - file.transferTo(targetPath.toFile()); - } catch (IOException exception) { - throw new BusinessException(CommonResponseEnum.FAIL, errorMessage); + Map storedFileMap = new LinkedHashMap(); + for (Map.Entry entry : fileMap.entrySet()) { + String originalFileName = entry.getKey(); + MultipartFile file = entry.getValue(); + if (file == null || file.isEmpty() || StrUtil.isBlank(originalFileName)) { + throw new BusinessException(CommonResponseEnum.FAIL, "文件不能为空"); + } + String safeFileName = Paths.get(originalFileName).getFileName().toString(); + Path targetPath = resolveTempUploadDir(reportId, batchId).resolve(safeFileName); + try { + Files.createDirectories(targetPath.getParent()); + file.transferTo(targetPath.toFile()); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "文件上传阶段:保存临时文件失败"); + } + storedFileMap.put(originalFileName, buildTempUploadRelativePath(reportId, batchId, safeFileName)); } - return buildRelativePath(reportId, childDir, fileName); + UploadedBatch uploadedBatch = new UploadedBatch(); + uploadedBatch.setBatchId(batchId); + uploadedBatch.setTempStoragePath(buildTempBatchRelativePath(reportId, batchId)); + uploadedBatch.setStoredFileMap(storedFileMap); + return uploadedBatch; + } + + public UploadedBatch saveValidateSessionFiles(String sessionId, Map fileMap) { + if (fileMap == null || fileMap.isEmpty()) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏂囦欢涓嶈兘涓虹┖"); + } + Map storedFileMap = new LinkedHashMap(); + for (Map.Entry entry : fileMap.entrySet()) { + String originalFileName = entry.getKey(); + MultipartFile file = entry.getValue(); + if (file == null || file.isEmpty() || StrUtil.isBlank(originalFileName)) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏂囦欢涓嶈兘涓虹┖"); + } + String safeFileName = Paths.get(originalFileName).getFileName().toString(); + Path targetPath = resolveValidateSessionUploadDir(sessionId).resolve(safeFileName); + try { + Files.createDirectories(targetPath.getParent()); + file.transferTo(targetPath.toFile()); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏂囦欢涓婁紶闃舵锛氫繚瀛樻牎楠屼細璇濇枃浠跺け璐?"); + } + storedFileMap.put(originalFileName, buildValidateSessionUploadRelativePath(sessionId, safeFileName)); + } + UploadedBatch uploadedBatch = new UploadedBatch(); + uploadedBatch.setBatchId(sessionId); + uploadedBatch.setTempStoragePath(buildValidateSessionRelativePath(sessionId)); + uploadedBatch.setStoredFileMap(storedFileMap); + return uploadedBatch; + } + + public Path resolveTempBatchFile(String reportId, String batchId, String fileName) { + return resolveTempUploadDir(reportId, batchId).resolve(Paths.get(fileName).getFileName().toString()).normalize(); + } + + public void writeTempBatchMeta(String reportId, String batchId, String content) { + Path metaPath = resolveTempBatchMetaPath(reportId, batchId); + try { + Files.createDirectories(metaPath.getParent()); + Files.write(metaPath, content.getBytes(StandardCharsets.UTF_8)); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段:保存批次元数据失败"); + } + } + + public void writeValidateSessionResult(String sessionId, String content) { + Path resultPath = resolveValidateSessionResultPath(sessionId); + try { + Files.createDirectories(resultPath.getParent()); + Files.write(resultPath, content.getBytes(StandardCharsets.UTF_8)); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏍¢獙鏂囦欢闃舵锛氫繚瀛樻牎楠岀粨鏋滃け璐?"); + } + } + + public Path resolveValidateSessionFile(String sessionId, String fileName) { + return resolveValidateSessionUploadDir(sessionId).resolve(Paths.get(fileName).getFileName().toString()).normalize(); + } + + public List listValidateSessionFileNames(String sessionId) { + Path uploadDir = resolveValidateSessionUploadDir(sessionId); + if (!Files.exists(uploadDir) || !Files.isDirectory(uploadDir)) { + return new ArrayList(); + } + try { + List result = new ArrayList(); + Files.list(uploadDir) + .filter(Files::isRegularFile) + .forEach(path -> result.add(path.getFileName().toString())); + return result; + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "鏍¢獙鏂囦欢闃舵锛氳鍙栦細璇濇枃浠跺垪琛ㄥけ璐?"); + } + } + + public String readTempBatchMeta(String reportId, String batchId) { + Path metaPath = resolveTempBatchMetaPath(reportId, batchId); + if (!Files.exists(metaPath) || !Files.isRegularFile(metaPath)) { + return null; + } + try { + return new String(Files.readAllBytes(metaPath), StandardCharsets.UTF_8); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "璇诲彇瀵煎叆鎵规淇℃伅澶辫触"); + } + } + + public String saveLedgerSummaryFromTemp(String reportId, String batchId, String fileName) { + return savePathFile(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR, + resolveTempBatchFile(reportId, batchId, fileName), fileName, "淇濆瓨鍙拌处姹囨€籈xcel澶辫触"); + } + + public String savePointExcelFromTemp(String reportId, String batchId, String fileName) { + return savePathFile(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR, + resolveTempBatchFile(reportId, batchId, fileName), fileName, "淇濆瓨鐩戞祴鐐笶xcel闄勪欢澶辫触"); + } + + public String savePointWordFromTemp(String reportId, String batchId, String fileName) { + return savePathFile(reportId, TestReportConst.STORAGE_POINT_WORD_DIR, + resolveTempBatchFile(reportId, batchId, fileName), fileName, "淇濆瓨鐩戞祴鐐筗ord闄勪欢澶辫触"); } public String saveGeneratedPlaceholder(String reportId, String fileName, byte[] content) { @@ -58,7 +175,7 @@ public class TestReportStorageService { Files.createDirectories(targetPath.getParent()); Files.write(targetPath, content); } catch (IOException exception) { - throw new BusinessException(CommonResponseEnum.FAIL, "保存生成报告失败"); + throw new BusinessException(CommonResponseEnum.FAIL, "淇濆瓨鐢熸垚鎶ュ憡澶辫触"); } return buildRelativePath(reportId, TestReportConst.STORAGE_GENERATED_REPORT_DIR, fileName); } @@ -74,7 +191,85 @@ public class TestReportStorageService { try { Files.deleteIfExists(resolveBusinessPath(relativePath)); } catch (IOException ignored) { - // 文件清理失败不影响主流程 + // 鏂囦欢娓呯悊澶辫触涓嶅奖鍝嶄富娴佺▼ + } + } + + public void deleteTempBatchQuietly(String reportId, String batchId) { + if (StrUtil.isBlank(reportId) || StrUtil.isBlank(batchId)) { + return; + } + deletePathQuietly(resolveTempBatchDir(reportId, batchId)); + } + + private String saveMultipartFile(String reportId, String childDir, MultipartFile file, String errorMessage) { + String originalFileName = file == null ? null : file.getOriginalFilename(); + if (file == null || file.isEmpty() || StrUtil.isBlank(originalFileName)) { + throw new BusinessException(CommonResponseEnum.FAIL, "文件不能为空"); + } + return savePathFile(reportId, childDir, toTempTransferredPath(file), originalFileName, errorMessage); + } + + private Path toTempTransferredPath(MultipartFile file) { + try { + Path tempFile = Files.createTempFile("test-report-", ".upload"); + file.transferTo(tempFile.toFile()); + return tempFile; + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, "文件上传失败"); + } + } + + private String savePathFile(String reportId, String childDir, Path sourcePath, String originalFileName, String errorMessage) { + String normalizedFileName = originalFileName == null ? null : Paths.get(originalFileName).getFileName().toString(); + if (sourcePath == null || StrUtil.isBlank(normalizedFileName) || !Files.exists(sourcePath)) { + throw new BusinessException(CommonResponseEnum.FAIL, "文件不能为空"); + } + String fileName = UUID.randomUUID().toString().replace("-", "") + "-" + normalizedFileName; + Path targetPath = resolveReportDir(reportId, childDir).resolve(fileName); + try { + Files.createDirectories(targetPath.getParent()); + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException exception) { + throw new BusinessException(CommonResponseEnum.FAIL, errorMessage); + } finally { + deletePathQuietlyIfTemp(sourcePath); + } + return buildRelativePath(reportId, childDir, fileName); + } + + private void deletePathQuietlyIfTemp(Path path) { + if (path == null) { + return; + } + String fileName = path.getFileName() == null ? null : path.getFileName().toString(); + if (fileName != null && fileName.startsWith("test-report-")) { + deletePathQuietly(path); + } + } + + private void deletePathQuietly(Path path) { + if (path == null || !Files.exists(path)) { + return; + } + try { + if (Files.isDirectory(path)) { + Files.walk(path) + .sorted((left, right) -> right.getNameCount() - left.getNameCount()) + .forEach(this::deleteSingleQuietly); + return; + } + Files.deleteIfExists(path); + } catch (IOException ignored) { + // 涓存椂鐩綍娓呯悊澶辫触涓嶅奖鍝嶄富娴佺▼ + } + } + + private void deleteSingleQuietly(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException ignored) { + // ignore } } @@ -82,7 +277,81 @@ public class TestReportStorageService { return Paths.get(filesRootPath, TestReportConst.STORAGE_DIR, reportId, childDir).normalize(); } + private Path resolveTempBatchDir(String reportId, String batchId) { + return Paths.get(filesRootPath, TestReportConst.STORAGE_DIR, reportId, + TestReportConst.STORAGE_IMPORT_TEMP_DIR, batchId).normalize(); + } + + private Path resolveTempUploadDir(String reportId, String batchId) { + return resolveTempBatchDir(reportId, batchId).resolve(TestReportConst.STORAGE_IMPORT_TEMP_UPLOAD_DIR); + } + + private Path resolveTempBatchMetaPath(String reportId, String batchId) { + return resolveTempBatchDir(reportId, batchId).resolve(TestReportConst.STORAGE_IMPORT_META_FILE_NAME); + } + + private Path resolveValidateSessionDir(String sessionId) { + return Paths.get(filesRootPath, TestReportConst.STORAGE_DIR, + TestReportConst.STORAGE_VALIDATE_SESSION_DIR, sessionId).normalize(); + } + + private Path resolveValidateSessionUploadDir(String sessionId) { + return resolveValidateSessionDir(sessionId).resolve(TestReportConst.STORAGE_IMPORT_TEMP_UPLOAD_DIR); + } + + private Path resolveValidateSessionResultPath(String sessionId) { + return resolveValidateSessionDir(sessionId).resolve(TestReportConst.STORAGE_VALIDATE_RESULT_FILE_NAME); + } + private String buildRelativePath(String reportId, String childDir, String fileName) { return reportId + "/" + childDir + "/" + fileName; } + + private String buildTempBatchRelativePath(String reportId, String batchId) { + return reportId + "/" + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId; + } + + private String buildTempUploadRelativePath(String reportId, String batchId, String fileName) { + return buildTempBatchRelativePath(reportId, batchId) + "/" + + TestReportConst.STORAGE_IMPORT_TEMP_UPLOAD_DIR + "/" + fileName; + } + + private String buildValidateSessionRelativePath(String sessionId) { + return TestReportConst.STORAGE_VALIDATE_SESSION_DIR + "/" + sessionId; + } + + private String buildValidateSessionUploadRelativePath(String sessionId, String fileName) { + return buildValidateSessionRelativePath(sessionId) + "/" + + TestReportConst.STORAGE_IMPORT_TEMP_UPLOAD_DIR + "/" + fileName; + } + + public static class UploadedBatch { + private String batchId; + private String tempStoragePath; + private Map storedFileMap = new LinkedHashMap(); + + public String getBatchId() { + return batchId; + } + + public void setBatchId(String batchId) { + this.batchId = batchId; + } + + public String getTempStoragePath() { + return tempStoragePath; + } + + public void setTempStoragePath(String tempStoragePath) { + this.tempStoragePath = tempStoragePath; + } + + public Map getStoredFileMap() { + return storedFileMap; + } + + public void setStoredFileMap(Map storedFileMap) { + this.storedFileMap = storedFileMap; + } + } } diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/controller/TestReportController.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/controller/TestReportController.java index 781bc21..d5b7135 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/controller/TestReportController.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/controller/TestReportController.java @@ -10,6 +10,7 @@ import com.njcn.gather.aireport.testreport.pojo.param.TestReportGroupSaveParam; import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportGroupReportVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO; import com.njcn.gather.aireport.testreport.service.TestReportService; @@ -123,12 +124,24 @@ public class TestReportController extends BaseController { } @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE) - @ApiOperation("导入台账汇总与监测点附件") + @ApiOperation("校验台账汇总与监测点附件") + @PostMapping("/{id}/ledger/validate") + public HttpResult validateLedger(@PathVariable("id") String id, + @RequestParam("files") MultipartFile[] files) { + String methodDescribe = getMethodDescribe("validateLedger"); + TestReportLedgerValidateResultVO result = testReportService.validateLedger(id, files); + return HttpResultUtil.assembleCommonResponseResult(Boolean.TRUE.equals(result.getSuccess()) + ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, + result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE) + @ApiOperation("导入已校验通过的台账批次") @PostMapping("/{id}/ledger/import") public HttpResult importLedger(@PathVariable("id") String id, - @RequestParam("files") MultipartFile[] files) { + @RequestBody @Validated TestReportParam.LedgerImportParam param) { String methodDescribe = getMethodDescribe("importLedger"); - TestReportLedgerImportResultVO result = testReportService.importLedger(id, files); + TestReportLedgerImportResultVO result = testReportService.importLedger(id, param); return HttpResultUtil.assembleCommonResponseResult(Boolean.TRUE.equals(result.getSuccess()) ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe); diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/constant/TestReportConst.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/constant/TestReportConst.java index 118b262..88b7cd5 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/constant/TestReportConst.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/constant/TestReportConst.java @@ -29,6 +29,11 @@ public final class TestReportConst { public static final String STORAGE_POINT_EXCEL_DIR = "point-excel"; public static final String STORAGE_POINT_WORD_DIR = "point-word"; public static final String STORAGE_GENERATED_REPORT_DIR = "generated-report"; + public static final String STORAGE_IMPORT_TEMP_DIR = "import-temp"; + public static final String STORAGE_IMPORT_TEMP_UPLOAD_DIR = "upload"; + public static final String STORAGE_IMPORT_META_FILE_NAME = "batch-meta.json"; + public static final String STORAGE_VALIDATE_SESSION_DIR = "validate-session"; + public static final String STORAGE_VALIDATE_RESULT_FILE_NAME = "validate-result.json"; public static final String LEDGER_SUMMARY_FILE_NAME = "台账信息汇总.xlsx"; public static final String LEDGER_GUIDE_SHEET_NAME = "填写说明"; diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/param/TestReportParam.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/param/TestReportParam.java index 3170f8e..d9c9459 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/param/TestReportParam.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/param/TestReportParam.java @@ -83,4 +83,11 @@ public class TestReportParam { @Size(max = 128, message = "\u5ba1\u6838\u610f\u89c1\u4e0d\u80fd\u8d85\u8fc7128\u4e2a\u5b57\u7b26") private String checkResult; } + + @Data + public static class LedgerImportParam { + @ApiModelProperty("\u6821\u9a8c\u6210\u529f\u7684\u5bfc\u5165\u6279\u6b21ID") + @NotBlank(message = "\u5bfc\u5165\u6279\u6b21ID\u4e0d\u80fd\u4e3a\u7a7a") + private String batchId; + } } diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerImportResultVO.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerImportResultVO.java index 4a158ff..c45561b 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerImportResultVO.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerImportResultVO.java @@ -8,10 +8,13 @@ import java.util.List; @Data public class TestReportLedgerImportResultVO { + private String batchId; private String currentStage; private Boolean success; private String summaryFileName; + private String tempStoragePath; private Integer totalFileCount; + private Integer uploadedFileCount; private Integer pointCount; private Integer excelAttachmentCount; private Integer wordAttachmentCount; diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidateAttachmentVO.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidateAttachmentVO.java new file mode 100644 index 0000000..520db68 --- /dev/null +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidateAttachmentVO.java @@ -0,0 +1,15 @@ +package com.njcn.gather.aireport.testreport.pojo.vo; + +import lombok.Data; + +@Data +public class TestReportLedgerValidateAttachmentVO { + + private String attachmentName; + private String attachmentType; + private Boolean referenced; + private Boolean uploaded; + private Boolean missing; + private Boolean reuploadAllowed; + private String message; +} diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidatePointVO.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidatePointVO.java new file mode 100644 index 0000000..c413bd8 --- /dev/null +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidatePointVO.java @@ -0,0 +1,22 @@ +package com.njcn.gather.aireport.testreport.pojo.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TestReportLedgerValidatePointVO { + + private Integer rowNo; + private String pointKey; + private String substationName; + private String pointName; + private Boolean complete; + private Integer missingAttachmentCount; + private Boolean testDeviceNoValid; + private String testDeviceNoMessage; + private Boolean clientUnitNameValid; + private String clientUnitNameMessage; + private List attachments = new ArrayList(); +} diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidateResultVO.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidateResultVO.java new file mode 100644 index 0000000..877a2cb --- /dev/null +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/pojo/vo/TestReportLedgerValidateResultVO.java @@ -0,0 +1,26 @@ +package com.njcn.gather.aireport.testreport.pojo.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TestReportLedgerValidateResultVO { + + private String sessionId; + private Boolean success; + private String summaryFileName; + private Boolean summaryFilePresent; + private String tempStoragePath; + private Integer uploadedFileCount; + private Integer pointCount; + private Integer missingAttachmentCount; + private Boolean canContinueUpload; + private List requiredSheets = new ArrayList(); + private List existingSheets = new ArrayList(); + private List missingSheets = new ArrayList(); + private List orphanAttachmentNames = new ArrayList(); + private List failReasons = new ArrayList(); + private List points = new ArrayList(); +} diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/TestReportService.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/TestReportService.java index 3489f56..735d21d 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/TestReportService.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/TestReportService.java @@ -7,6 +7,7 @@ import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam; import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportGroupReportVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO; import org.springframework.http.ResponseEntity; @@ -34,7 +35,9 @@ public interface TestReportService extends IService { ResponseEntity previewTemplate(String id); - TestReportLedgerImportResultVO importLedger(String id, MultipartFile[] files); + TestReportLedgerValidateResultVO validateLedger(String id, MultipartFile[] files); + + TestReportLedgerImportResultVO importLedger(String id, TestReportParam.LedgerImportParam param); ResponseEntity downloadLedgerTemplate(); diff --git a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImpl.java b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImpl.java index 94fa09e..907b26f 100644 --- a/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImpl.java +++ b/ai-report/test-report/src/main/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImpl.java @@ -29,6 +29,7 @@ import com.njcn.gather.aireport.testreport.pojo.po.TestReportPointPO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportExcelVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportGroupReportVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO; import com.njcn.gather.aireport.testreport.service.TestReportService; @@ -43,6 +44,7 @@ import com.njcn.web.factory.PageFactory; import com.njcn.web.utils.ExcelUtil; import com.njcn.web.utils.RequestUtil; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -70,6 +72,7 @@ import java.util.UUID; import java.util.stream.Collectors; @Service +@Slf4j public class TestReportServiceImpl extends ServiceImpl implements TestReportService { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @@ -108,6 +111,7 @@ public class TestReportServiceImpl extends ServiceImpl describeUploadedFiles(MultipartFile[] files) { + if (files == null || files.length == 0) { + return Collections.emptyList(); + } + List fileNames = new ArrayList(); + for (MultipartFile file : files) { + if (file == null) { + fileNames.add("null"); + continue; + } + String fileName = trimToNull(file.getOriginalFilename()); + fileNames.add(fileName == null ? "(blank)" : fileName); + } + return fileNames; + } + private String trimToNull(String value) { if (value == null) { return null; diff --git a/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParserTest.java b/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParserTest.java index d36a1fa..708f697 100644 --- a/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParserTest.java +++ b/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerExcelParserTest.java @@ -60,7 +60,7 @@ class TestReportLedgerExcelParserTest { } @Test - void shouldRejectDuplicatePointBySubstationDeviceAndPointName() throws Exception { + void shouldRejectDuplicatePointBySubstationAndPointName() throws Exception { TestReportLedgerExcelParser parser = new TestReportLedgerExcelParser(); MockMultipartFile summaryFile = new MockMultipartFile("files", TestReportConst.LEDGER_SUMMARY_FILE_NAME, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", buildDuplicatePointWorkbookBytes()); diff --git a/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportServiceTest.java b/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportServiceTest.java index b8b8c1b..6fca32a 100644 --- a/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportServiceTest.java +++ b/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/component/TestReportLedgerImportServiceTest.java @@ -2,16 +2,21 @@ package com.njcn.gather.aireport.testreport.component; import com.njcn.common.pojo.exception.BusinessException; import com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper; +import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO; import com.njcn.gather.aireport.testdevice.mapper.TestDeviceMapper; import com.njcn.gather.aireport.testdevice.pojo.constant.TestDeviceConst; +import com.njcn.gather.aireport.testdevice.pojo.po.TestDevicePO; import com.njcn.gather.aireport.testreport.mapper.TestReportGroupReportMapper; import com.njcn.gather.aireport.testreport.mapper.TestReportMapper; import com.njcn.gather.aireport.testreport.mapper.TestReportPointMapper; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; +import java.util.Collections; +import java.util.HashMap; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; @@ -47,7 +52,51 @@ class TestReportLedgerImportServiceTest { BusinessException exception = Assertions.assertThrows(BusinessException.class, () -> invokeResolveDeviceState(service, "停用", 4)); - Assertions.assertEquals("基础资料维护阶段:检测设备sheet第4行设备状态不正确", exception.getMessage()); + Assertions.assertEquals("基础信息维护阶段:检测设备sheet第4行设备状态不正确", exception.getMessage()); + } + + @Test + void normalizeFileMapShouldSkipEmptyFileItemsAndKeepTrimmedNames() { + TestReportLedgerImportService service = createService(); + MockMultipartFile validFile = new MockMultipartFile("files", " point-a.xlsx ", "application/octet-stream", + new byte[]{1}); + MockMultipartFile emptyFile = new MockMultipartFile("files", "empty.xlsx", "application/octet-stream", + new byte[0]); + + Map result = invokeNormalizeFileMap(service, new MultipartFile[]{emptyFile, validFile}); + + Assertions.assertEquals(1, result.size()); + Assertions.assertSame(validFile, result.get("point-a.xlsx")); + } + + @Test + void fillPointValidationResultShouldAppendReferenceErrorsToPointResult() { + TestReportLedgerImportService service = createService(); + TestReportLedgerExcelParser.ParsedPointRow pointRow = new TestReportLedgerExcelParser.ParsedPointRow(); + pointRow.setRowNo(2); + pointRow.setSubstationName("变电站A"); + pointRow.setPointName("监测点A"); + pointRow.setTestDeviceNo("DEV-001"); + pointRow.setClientUnitName("委托单位A"); + pointRow.setExcelAttachmentName("point-a.xlsx"); + pointRow.setWordAttachmentName("point-a.docx"); + + TestReportLedgerExcelParser.ParsedLedger ledger = new TestReportLedgerExcelParser.ParsedLedger(); + ledger.setPoints(Collections.singletonList(pointRow)); + + TestReportLedgerValidateResultVO result = new TestReportLedgerValidateResultVO(); + + invokeFillPointValidationResult(service, result, ledger, Collections.singleton("台账信息汇总.xlsx"), + new HashMap(), new HashMap()); + + Assertions.assertEquals(1, result.getPoints().size()); + Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getComplete())); + Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getTestDeviceNoValid())); + Assertions.assertEquals("第2行检测设备编号在数据库和检测设备sheet中都不存在", + result.getPoints().get(0).getTestDeviceNoMessage()); + Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getClientUnitNameValid())); + Assertions.assertEquals("第2行委托单位名称在数据库和委托单位sheet中都不存在", + result.getPoints().get(0).getClientUnitNameMessage()); } private TestReportLedgerImportService createService() { @@ -71,6 +120,18 @@ class TestReportLedgerImportServiceTest { new Class[]{String.class, Integer.class}, new Object[]{stateName, rowNo}); } + private void invokeFillPointValidationResult(TestReportLedgerImportService service, + TestReportLedgerValidateResultVO result, + TestReportLedgerExcelParser.ParsedLedger ledger, + java.util.Set sessionFileNameSet, + Map dbDeviceMap, + Map dbClientMap) { + invokePrivateMethod(service, "fillPointValidationResult", + new Class[]{TestReportLedgerValidateResultVO.class, TestReportLedgerExcelParser.ParsedLedger.class, + java.util.Set.class, Map.class, Map.class}, + new Object[]{result, ledger, sessionFileNameSet, dbDeviceMap, dbClientMap}); + } + private Object invokePrivateMethod(TestReportLedgerImportService service, String methodName, Class[] parameterTypes, Object[] args) { try { @@ -87,18 +148,4 @@ class TestReportLedgerImportServiceTest { throw new RuntimeException(exception); } } - - @Test - void normalizeFileMapShouldSkipEmptyFileItemsAndKeepTrimmedNames() { - TestReportLedgerImportService service = createService(); - MockMultipartFile validFile = new MockMultipartFile("files", " point-a.xlsx ", "application/octet-stream", - new byte[]{1}); - MockMultipartFile emptyFile = new MockMultipartFile("files", "empty.xlsx", "application/octet-stream", - new byte[0]); - - Map result = invokeNormalizeFileMap(service, new MultipartFile[]{emptyFile, validFile}); - - Assertions.assertEquals(1, result.size()); - Assertions.assertSame(validFile, result.get("point-a.xlsx")); - } } diff --git a/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImplTest.java b/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImplTest.java index ff1f508..be6adff 100644 --- a/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImplTest.java +++ b/ai-report/test-report/src/test/java/com/njcn/gather/aireport/testreport/service/impl/TestReportServiceImplTest.java @@ -15,6 +15,7 @@ import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam; import com.njcn.gather.aireport.testreport.pojo.po.TestReportGroupReportPO; import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO; import com.njcn.gather.aireport.testreport.pojo.po.TestReportPointPO; +import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO; import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO; import com.njcn.gather.comservice.filepreview.FilePreviewService; @@ -39,6 +40,7 @@ import java.util.Map; import java.util.UUID; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class TestReportServiceImplTest { @@ -81,7 +83,9 @@ class TestReportServiceImplTest { @Test void serviceShouldExposeGroupedApis() throws Exception { Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listTestReports", TestReportParam.QueryParam.class)); - Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("importLedger", String.class, MultipartFile[].class)); + Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("validateLedger", String.class, MultipartFile[].class)); + Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("importLedger", String.class, + TestReportParam.LedgerImportParam.class)); Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listPoints", String.class)); Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listGroupReports", String.class)); } @@ -114,11 +118,22 @@ class TestReportServiceImplTest { MockMultipartFile file = new MockMultipartFile("files", "sample.xls", "application/vnd.ms-excel", new byte[]{1}); BusinessException exception = Assertions.assertThrows(BusinessException.class, - () -> service.importLedger("report-001", new MultipartFile[]{file})); + () -> service.validateLedger("report-001", new MultipartFile[]{file})); Assertions.assertTrue(exception.getMessage().contains("未生成")); } + @Test + void importLedgerShouldRejectMissingBatchId() { + TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class)); + TestReportParam.LedgerImportParam param = new TestReportParam.LedgerImportParam(); + + BusinessException exception = Assertions.assertThrows(BusinessException.class, + () -> service.importLedger("report-001", param)); + + Assertions.assertNotNull(exception.getMessage()); + } + @Test void addTestReportShouldRejectMissingNo() { TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class)); diff --git a/tmp/generate_yilin_grade3_wordlist.js b/tmp/generate_yilin_grade3_wordlist.js new file mode 100644 index 0000000..3e0bb3b --- /dev/null +++ b/tmp/generate_yilin_grade3_wordlist.js @@ -0,0 +1,302 @@ +const fs = require("fs"); +const path = require("path"); + +const allUnits = [ + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 1 Hello", + entries: [ + ["hello", "/h\u0259\u02c8l\u0259\u028a/", "\u4f60\u597d"], + ["hi", "/ha\u026a/", "\u55e8\uff1b\u4f60\u597d"], + ["good morning", "/\u02cc\u0261\u028ad \u02c8m\u0254\u02d0n\u026a\u014b/", "\u65e9\u4e0a\u597d"], + ["Miss", "/m\u026as/", "\u5c0f\u59d0\uff1b\u8001\u5e08\u79f0\u547c"], + ["class", "/kl\u0251\u02d0s/", "\u540c\u5b66\u4eec\uff1b\u73ed\u7ea7"], + ["I", "/a\u026a/", "\u6211"], + ["I'm = I am", "/a\u026am/", "\u6211\u662f"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 2 I'm Liu Tao", + entries: [ + ["are", "/\u0251\u02d0(r)/", "\u662f"], + ["yes", "/jes/", "\u662f\uff1b\u5bf9"], + ["no", "/n\u0259\u028a/", "\u4e0d\uff1b\u4e0d\u662f"], + ["not", "/n\u0252t/", "\u4e0d\uff1b\u6ca1"], + ["you", "/ju\u02d0/", "\u4f60\uff1b\u4f60\u4eec"], + ["what", "/w\u0252t/", "\u4ec0\u4e48"], + ["your", "/j\u0254\u02d0(r)/", "\u4f60\u7684"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 3 My friends", + entries: [ + ["my", "/ma\u026a/", "\u6211\u7684"], + ["friend", "/frend/", "\u670b\u53cb"], + ["he", "/hi\u02d0/", "\u4ed6"], + ["she", "/\u0283i\u02d0/", "\u5979"], + ["this", "/\u00f0\u026as/", "\u8fd9\uff1b\u8fd9\u4e2a"], + ["is", "/\u026az/", "\u662f"], + ["too", "/tu\u02d0/", "\u4e5f"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 4 My family", + entries: [ + ["family", "/\u02c8f\u00e6m\u0259li/", "\u5bb6\u5ead\uff1b\u5bb6\u4eba"], + ["father", "/\u02c8f\u0251\u02d0\u00f0\u0259(r)/", "\u7236\u4eb2"], + ["mother", "/\u02c8m\u028c\u00f0\u0259(r)/", "\u6bcd\u4eb2"], + ["brother", "/\u02c8br\u028c\u00f0\u0259(r)/", "\u5144\uff1b\u5f1f"], + ["sister", "/\u02c8s\u026ast\u0259(r)/", "\u59d0\uff1b\u59b9"], + ["grandpa", "/\u02c8\u0261r\u00e6n(d)p\u0251\u02d0/", "\u7237\u7237\uff1b\u5916\u516c"], + ["grandma", "/\u02c8\u0261r\u00e6n(d)m\u0251\u02d0/", "\u5976\u5976\uff1b\u5916\u5a46"], + ["me", "/mi\u02d0/", "\u6211"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 5 Look at me", + entries: [ + ["look at", "/l\u028ak \u00e6t/", "\u770b"], + ["T-shirt", "/\u02c8ti\u02d0 \u0283\u025c\u02d0t/", "T\u6064\u886b"], + ["skirt", "/sk\u025c\u02d0t/", "\u88d9\u5b50"], + ["cap", "/k\u00e6p/", "\u5e3d\u5b50"], + ["jacket", "/\u02c8d\u0292\u00e6k\u026at/", "\u5939\u514b\u886b"], + ["nice", "/na\u026as/", "\u597d\u770b\u7684\uff1b\u597d\u7684"], + ["great", "/\u0261re\u026at/", "\u597d\u6781\u4e86"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 6 Colours", + entries: [ + ["red", "/red/", "\u7ea2\u8272"], + ["orange", "/\u02c8\u0252r\u026and\u0292/", "\u6a59\u8272"], + ["yellow", "/\u02c8jel\u0259\u028a/", "\u9ec4\u8272"], + ["green", "/\u0261ri\u02d0n/", "\u7eff\u8272"], + ["blue", "/blu\u02d0/", "\u84dd\u8272"], + ["brown", "/bra\u028an/", "\u68d5\u8272"], + ["white", "/wa\u026at/", "\u767d\u8272"], + ["black", "/bl\u00e6k/", "\u9ed1\u8272"], + ["and", "/\u00e6nd/", "\u548c"], + ["now", "/na\u028a/", "\u73b0\u5728"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 7 Would you like a pie", + entries: [ + ["would like", "/w\u028ad la\u026ak/", "\u60f3\u8981"], + ["pie", "/pa\u026a/", "\u9985\u997c"], + ["cake", "/ke\u026ak/", "\u86cb\u7cd5"], + ["egg", "/e\u0261/", "\u9e21\u86cb"], + ["sweet", "/swi\u02d0t/", "\u7cd6\u679c\uff1b\u751c\u7684"], + ["ice cream", "/\u02cca\u026as \u02c8kri\u02d0m/", "\u51b0\u6fc0\u51cc"], + ["hot dog", "/\u02c8h\u0252t d\u0252\u0261/", "\u70ed\u72d7"], + ["what about", "/w\u0252t \u0259\u02c8ba\u028at/", "\u2026\u2026\u600e\u4e48\u6837"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0a\u518c Unit 8 Happy New Year", + entries: [ + ["happy", "/\u02c8h\u00e6pi/", "\u9ad8\u5174\u7684\uff1b\u5feb\u4e50\u7684"], + ["new", "/nju\u02d0/", "\u65b0\u7684"], + ["year", "/j\u026a\u0259(r)/", "\u5e74"], + ["Happy New Year", "/\u02cch\u00e6pi nju\u02d0 \u02c8j\u026a\u0259(r)/", "\u65b0\u5e74\u5feb\u4e50"], + ["uncle", "/\u02c8\u028c\u014bkl/", "\u53d4\u53d4\uff1b\u8205\u8205"], + ["aunt", "/\u0251\u02d0nt/", "\u963f\u59e8\uff1b\u59d1\u5988"], + ["doll", "/d\u0252l/", "\u73a9\u5177\u5a03\u5a03"], + ["ball", "/b\u0254\u02d0l/", "\u7403"], + ["robot", "/\u02c8r\u0259\u028ab\u0252t/", "\u673a\u5668\u4eba"], + ["toy car", "/t\u0254\u026a k\u0251\u02d0(r)/", "\u73a9\u5177\u5c0f\u6c7d\u8f66"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 1 In class", + entries: [ + ["in class", "/\u026an kl\u0251\u02d0s/", "\u5728\u4e0a\u8bfe"], + ["open", "/\u02c8\u0259\u028ap\u0259n/", "\u6253\u5f00"], + ["the", "/\u00f0\u0259/", "\u8fd9\uff1b\u90a3\uff08\u5b9a\u51a0\u8bcd\uff09"], + ["door", "/d\u0254\u02d0(r)/", "\u95e8"], + ["window", "/\u02c8w\u026and\u0259\u028a/", "\u7a97\u6237"], + ["book", "/b\u028ak/", "\u4e66"], + ["stand up", "/st\u00e6nd \u028cp/", "\u8d77\u7acb"], + ["sit down", "/s\u026at da\u028an/", "\u5750\u4e0b"], + ["please", "/pli\u02d0z/", "\u8bf7"], + ["come in", "/k\u028cm \u026an/", "\u8fdb\u6765"], + ["close", "/kl\u0259\u028az/", "\u5173\u4e0a"], + ["blackboard", "/\u02c8bl\u00e6kb\u0254\u02d0d/", "\u9ed1\u677f"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 2 In the library", + entries: [ + ["library", "/\u02c8la\u026abr\u0259ri/", "\u56fe\u4e66\u9986"], + ["shout", "/\u0283a\u028at/", "\u558a\uff1b\u53eb"], + ["eat", "/i\u02d0t/", "\u5403"], + ["run", "/r\u028cn/", "\u8dd1"], + ["talk", "/t\u0254\u02d0k/", "\u8bf4\u8bdd"], + ["sleep", "/sli\u02d0p/", "\u7761\u89c9"], + ["drink", "/dr\u026a\u014bk/", "\u559d"], + ["here", "/h\u026a\u0259(r)/", "\u8fd9\u91cc"], + ["English", "/\u02c8\u026a\u014b\u0261l\u026a\u0283/", "\u82f1\u8bed"], + ["milk", "/m\u026alk/", "\u725b\u5976"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 3 Is this your pencil", + entries: [ + ["pencil", "/\u02c8pensl/", "\u94c5\u7b14"], + ["pen", "/pen/", "\u94a2\u7b14"], + ["ruler", "/\u02c8ru\u02d0l\u0259(r)/", "\u5c3a"], + ["rubber", "/\u02c8r\u028cb\u0259(r)/", "\u6a61\u76ae"], + ["crayon", "/\u02c8kre\u026a\u0259n/", "\u8721\u7b14"], + ["schoolbag", "/\u02c8sku\u02d0lb\u00e6\u0261/", "\u4e66\u5305"], + ["pencil case", "/\u02c8pensl ke\u026as/", "\u94c5\u7b14\u76d2"], + ["lunch box", "/\u02c8l\u028cnt\u0283 b\u0252ks/", "\u5348\u9910\u76d2"], + ["where", "/we\u0259(r)/", "\u5728\u54ea\u91cc"], + ["over there", "/\u02c8\u0259\u028av\u0259(r) \u00f0e\u0259(r)/", "\u5728\u90a3\u91cc"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 4 Where's the bird", + entries: [ + ["bird", "/b\u025c\u02d0d/", "\u9e1f"], + ["under", "/\u02c8\u028cnd\u0259(r)/", "\u5728\u2026\u2026\u4e0b\u9762"], + ["behind", "/b\u026a\u02c8ha\u026and/", "\u5728\u2026\u2026\u540e\u9762"], + ["on", "/\u0252n/", "\u5728\u2026\u2026\u4e0a\u9762"], + ["desk", "/desk/", "\u4e66\u684c"], + ["chair", "/t\u0283e\u0259(r)/", "\u6905\u5b50"], + ["in", "/\u026an/", "\u5728\u2026\u2026\u91cc\u9762"], + ["tree", "/tri\u02d0/", "\u6811"], + ["beautiful", "/\u02c8bju\u02d0t\u026afl/", "\u6f02\u4eae\u7684"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 5 How old are you", + entries: [ + ["one", "/w\u028cn/", "\u4e00"], + ["two", "/tu\u02d0/", "\u4e8c"], + ["three", "/\u03b8ri\u02d0/", "\u4e09"], + ["four", "/f\u0254\u02d0(r)/", "\u56db"], + ["five", "/fa\u026av/", "\u4e94"], + ["six", "/s\u026aks/", "\u516d"], + ["seven", "/\u02c8sevn/", "\u4e03"], + ["eight", "/e\u026at/", "\u516b"], + ["nine", "/na\u026an/", "\u4e5d"], + ["ten", "/ten/", "\u5341"], + ["how old", "/ha\u028a \u0259\u028ald/", "\u51e0\u5c81"], + ["right", "/ra\u026at/", "\u5bf9\u7684\uff1b\u6b63\u786e\u7684"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 6 What time is it", + entries: [ + ["what time", "/w\u0252t ta\u026am/", "\u51e0\u70b9"], + ["wake up", "/we\u026ak \u028cp/", "\u9192\uff1b\u9192\u6765"], + ["o'clock", "/\u0259\u02c8kl\u0252k/", "\u2026\u2026\u70b9\u949f"], + ["breakfast", "/\u02c8brekf\u0259st/", "\u65e9\u9910"], + ["lunch", "/l\u028cnt\u0283/", "\u5348\u9910"], + ["dinner", "/\u02c8d\u026an\u0259(r)/", "\u665a\u9910"], + ["class", "/kl\u0251\u02d0s/", "\u8bfe\uff1b\u4e0a\u8bfe"], + ["bed", "/bed/", "\u5e8a"], + ["OK", "/\u02cc\u0259\u028a\u02c8ke\u026a/", "\u597d\uff1b\u53ef\u4ee5\u4e86"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 7 On the farm", + entries: [ + ["farm", "/f\u0251\u02d0m/", "\u519c\u573a"], + ["they", "/\u00f0e\u026a/", "\u5b83\u4eec\uff1b\u4ed6\u4eec"], + ["pig", "/p\u026a\u0261/", "\u732a"], + ["cow", "/ka\u028a/", "\u5976\u725b"], + ["chicken", "/\u02c8t\u0283\u026ak\u026an/", "\u9e21"], + ["duck", "/d\u028ck/", "\u9e2d"], + ["pear", "/pe\u0259(r)/", "\u68a8"], + ["apple", "/\u02c8\u00e6pl/", "\u82f9\u679c"], + ["orange", "/\u02c8\u0252r\u026and\u0292/", "\u6a59\u5b50"], + ["those", "/\u00f0\u0259\u028az/", "\u90a3\u4e9b"] + ] + }, + { + title: "\u4e09\u5e74\u7ea7\u4e0b\u518c Unit 8 We're twins", + entries: [ + ["we", "/wi\u02d0/", "\u6211\u4eec"], + ["we're = we are", "/w\u026a\u0259(r)/", "\u6211\u4eec\u662f"], + ["twin", "/tw\u026an/", "\u53cc\u80de\u80ce\u4e4b\u4e00"], + ["man", "/m\u00e6n/", "\u7537\u4eba"], + ["woman", "/\u02c8w\u028am\u0259n/", "\u5973\u4eba"], + ["boy", "/b\u0254\u026a/", "\u7537\u5b69"], + ["girl", "/\u0261\u025c\u02d0l/", "\u5973\u5b69"], + ["baby", "/\u02c8be\u026abi/", "\u5a74\u513f"], + ["cousin", "/\u02c8k\u028czn/", "\u5802\uff08\u8868\uff09\u5144\u5f1f\u59d0\u59b9"], + ["name", "/ne\u026am/", "\u540d\u5b57"] + ] + } +]; + +function buildHtml(title, note, units) { + return ` + + + + + ${title} + + + +

${title}

+

${note}

+ ${units.map((unit) => ` +

${unit.title}

+ + ${unit.entries.map((entry) => ` + + + + + + `).join("")} +
${entry[0]}${entry[1]}${entry[2]}
+ `).join("")} + +`; +} + +function writeDoc(fileName, title, note, units) { + const outputPath = path.join(process.cwd(), fileName); + const html = buildHtml(title, note, units); + fs.writeFileSync(outputPath, "\uFEFF" + html, "utf8"); + return outputPath; +} + +const upperUnits = allUnits.filter((unit) => unit.title.includes("\u4e09\u5e74\u7ea7\u4e0a\u518c")); +const lowerUnits = allUnits.filter((unit) => unit.title.includes("\u4e09\u5e74\u7ea7\u4e0b\u518c")); + +const outputs = [ + writeDoc( + "\u6c5f\u82cf\u4e09\u5e74\u7ea7\u82f1\u8bed\u8bd1\u6797\u7248\u5355\u8bcd_\u4e0a\u4e0b\u518c_20260706.doc", + "\u6c5f\u82cf\u4e09\u5e74\u7ea7\u82f1\u8bed\u8bd1\u6797\u7248\u5355\u8bcd\u6c47\u603b", + "\u6574\u7406\u4e09\u5e74\u7ea7\u4e0a\u518c + \u4e0b\u518c\u3002\u82f1\u6587\u5355\u8bcd\u4e3a\u7ea2\u8272\uff0c\u97f3\u6807\u4e3a\u84dd\u8272\uff0c\u4e2d\u6587\u91ca\u4e49\u4e3a\u7eff\u8272\u3002", + allUnits + ), + writeDoc( + "\u6c5f\u82cf\u4e09\u5e74\u7ea7\u82f1\u8bed\u8bd1\u6797\u7248\u4e0a\u518c\u5355\u8bcd_20260706.doc", + "\u6c5f\u82cf\u4e09\u5e74\u7ea7\u82f1\u8bed\u8bd1\u6797\u7248\u4e0a\u518c\u5355\u8bcd", + "\u4ec5\u5305\u542b\u4e09\u5e74\u7ea7\u4e0a\u518c\u3002\u82f1\u6587\u5355\u8bcd\u4e3a\u7ea2\u8272\uff0c\u97f3\u6807\u4e3a\u84dd\u8272\uff0c\u4e2d\u6587\u91ca\u4e49\u4e3a\u7eff\u8272\u3002", + upperUnits + ), + writeDoc( + "\u6c5f\u82cf\u4e09\u5e74\u7ea7\u82f1\u8bed\u8bd1\u6797\u7248\u4e0b\u518c\u5355\u8bcd_20260706.doc", + "\u6c5f\u82cf\u4e09\u5e74\u7ea7\u82f1\u8bed\u8bd1\u6797\u7248\u4e0b\u518c\u5355\u8bcd", + "\u4ec5\u5305\u542b\u4e09\u5e74\u7ea7\u4e0b\u518c\u3002\u82f1\u6587\u5355\u8bcd\u4e3a\u7ea2\u8272\uff0c\u97f3\u6807\u4e3a\u84dd\u8272\uff0c\u4e2d\u6587\u91ca\u4e49\u4e3a\u7eff\u8272\u3002", + lowerUnits + ) +]; + +outputs.forEach((outputPath) => console.log(outputPath));