From 8dc2e4f3e7dc7948a50545e2f3cdbccdcf35a18c Mon Sep 17 00:00:00 2001 From: huangrh Date: Mon, 16 Mar 2026 16:37:15 +0800 Subject: [PATCH] Add audit report API --- interface-checklist.md | 2 +- .../task/controller/TaskController.java | 27 +++++++++ .../modules/task/service/TaskService.java | 59 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/interface-checklist.md b/interface-checklist.md index ee63d8f..4a2a2ef 100644 --- a/interface-checklist.md +++ b/interface-checklist.md @@ -15,7 +15,7 @@ | 9 | GET | `/api/reports/{id}/preview` | 报告预览(图片/历史) | `src/api/report.js` | ✅ 已修正 | 返回 `ocr_result.API核验/org_exists/cma_exists`,并附带 `pages/history` 与 `status` | | 10 | POST | `/api/tasks` | 创建识别任务 | `src/api/report.js` | ✅ 已修正 | 支持 `product_name/testing_date/contact_phone` 等表单字段 | | 11 | POST | `/api/reports/{id}/submit` | 用户确认提交 | `src/api/report.js` | ✅ 已修正 | OCR 通过后将状态置为 `pending` 并记录历史 | -| 12 | POST | `/api/reports/{id}/audit` | 审核提交 | `src/api/report.js` | | | +| 12 | POST | `/api/reports/{id}/audit` | 审核提交 | `src/api/report.js` | ✅ 已修正 | 支持 `status/opinion/files`,保存附件并记录历史 | | 13 | DELETE | `/api/reports/{id}` | 删除报告 | `src/api/report.js` | | | | 14 | POST | `/api/validate-cma` | CMA 校验 | `src/api/report.js` | | | | 15 | GET | `/api/reports/{id}/pdf` | PDF 预览 | `src/views/shibieneirong/shenhe/index.vue` | | | diff --git a/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/controller/TaskController.java b/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/controller/TaskController.java index 0c59a91..e4e0584 100644 --- a/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/controller/TaskController.java +++ b/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/controller/TaskController.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Map; import java.time.LocalDate; import java.time.ZoneId; +import java.util.List; @RestController @RequestMapping("/api") @@ -209,6 +210,32 @@ public class TaskController { } } + @PostMapping("/reports/{id}/audit") + @SaCheckRole(value = { "ADMIN", "AUDITOR" }, mode = SaMode.OR) + public ResponseEntity auditReport( + @PathVariable("id") String id, + @RequestParam("status") String status, + @RequestParam(value = "opinion", required = false) String opinion, + @RequestParam(value = "files", required = false) List files) { + try { + taskService.auditReport(id, status, opinion, files); + Map resp = new HashMap<>(); + resp.put("code", 0); + resp.put("msg", "Audited"); + return ResponseEntity.ok(resp); + } catch (IllegalArgumentException e) { + Map resp = new HashMap<>(); + resp.put("code", 1); + resp.put("msg", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(resp); + } catch (Exception e) { + Map resp = new HashMap<>(); + resp.put("code", 500); + resp.put("msg", e.getMessage()); + return ResponseEntity.internalServerError().body(resp); + } + } + @GetMapping("/statistics") @SaCheckRole(value = { "ADMIN", "AUDITOR", "USER" }, mode = SaMode.OR) public ResponseEntity getStatistics() { diff --git a/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/service/TaskService.java b/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/service/TaskService.java index 5f7b21a..4e50ab0 100644 --- a/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/service/TaskService.java +++ b/src/main/java/com/chinaweal/youfool/reportdetect/modules/task/service/TaskService.java @@ -65,6 +65,9 @@ public class TaskService { @Value("${app.file.preview-dir}") private String previewDir; + @Value("${app.file.attachment-dir}") + private String attachmentDir; + @Value("${app.ocr.async.enabled:false}") private boolean asyncOcrEnabled; @@ -559,6 +562,62 @@ public class TaskService { taskRepository.save(task); } + @Transactional + public void auditReport(String approvalId, String status, String opinion, List files) + throws IOException { + if (approvalId == null || approvalId.isBlank()) { + throw new IllegalArgumentException("approval_id is required"); + } + if (!"compliant".equals(status) && !"non-compliant".equals(status)) { + throw new IllegalArgumentException("invalid status"); + } + Task task = taskRepository.findByApprovalId(approvalId); + if (task == null) { + throw new IllegalArgumentException("Report not found"); + } + + task.setStatus(status); + task.setAuditOpinion(opinion); + + if (files != null && !files.isEmpty()) { + File baseDir = new File(attachmentDir, approvalId); + if (!baseDir.exists()) { + baseDir.mkdirs(); + } + if (task.getAttachments() == null) { + task.setAttachments(new java.util.ArrayList<>()); + } + for (org.springframework.web.multipart.MultipartFile file : files) { + if (file == null || file.isEmpty()) { + continue; + } + String originalFilename = file.getOriginalFilename(); + String safeName = (originalFilename == null || originalFilename.isBlank()) + ? (java.util.UUID.randomUUID().toString() + ".bin") + : originalFilename; + Path target = Paths.get(baseDir.getAbsolutePath(), safeName); + Files.copy(file.getInputStream(), target); + + AuditAttachment attachment = new AuditAttachment(); + attachment.setFilename(safeName); + attachment.setFilepath(target.toString()); + attachment.setTask(task); + task.getAttachments().add(attachment); + } + } + + AuditHistory history = new AuditHistory(); + history.setAction("compliant".equals(status) ? "审核通过" : "审核不通过"); + history.setOpinion(opinion == null ? "" : opinion); + history.setTask(task); + if (task.getHistories() == null) { + task.setHistories(new java.util.ArrayList<>()); + } + task.getHistories().add(history); + + taskRepository.save(task); + } + public Map getStatistics() { List pendingStatuses = List.of("pending", "ocr_completed"); List auditedStatuses = List.of("compliant", "non-compliant");