恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。 == 此快照内容 == - 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091) - 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static) - 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB) - 交接文档 go.md + go-code-reference/endpoints/entities/database.md - 多代理建设脚本 .claude/wf-*.js == 状态 == - 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板) - 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用 - W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成) - 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456 == 排除(gitignore, 可再生) == node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui) 完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules) 时间戳: 20260615-191517 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
184 lines
7.6 KiB
Java
184 lines
7.6 KiB
Java
package com.kaidi.oa.web;
|
|
|
|
import com.kaidi.oa.common.ApiException;
|
|
import com.kaidi.oa.common.ApiResp;
|
|
import com.kaidi.oa.common.NotFoundException;
|
|
import com.kaidi.oa.domain.AuditEvidence;
|
|
import com.kaidi.oa.domain.AuditProject;
|
|
import com.kaidi.oa.repository.AuditEvidenceRepository;
|
|
import com.kaidi.oa.repository.AuditProjectRepository;
|
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
import org.springframework.web.bind.annotation.PathVariable;
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
import org.springframework.web.bind.annotation.PutMapping;
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
import java.time.Instant;
|
|
import java.time.LocalDate;
|
|
import java.time.Year;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* 审计证据管理(内控部·审计监察部·模块2)。
|
|
*
|
|
* 补全 PARTIAL 缺口:证据分类/标签/全文检索(证据管理),专用证据库而非依赖档案关键词检索。
|
|
*
|
|
* 功能:
|
|
* - POST / 新增证据记录(关联项目+底稿+发现);
|
|
* - GET / 按项目/底稿/发现/分类过滤列表;
|
|
* - GET /search?q= 关键词检索(title/keywords/tags);
|
|
* - PUT /{id} 编辑证据信息;
|
|
* - DELETE /{id} 删除证据;
|
|
* - GET /categories 证据类别统计。
|
|
*
|
|
* 路径 /api/oa/audit-evidence 纳入 SENSITIVE_READ_PREFIXES(含审计证据敏感数据)。
|
|
*/
|
|
@RestController
|
|
@RequestMapping("/api/oa/audit-evidence")
|
|
public class AuditEvidenceController {
|
|
|
|
private final AuditEvidenceRepository evidenceRepo;
|
|
private final AuditProjectRepository projectRepo;
|
|
|
|
public AuditEvidenceController(AuditEvidenceRepository evidenceRepo,
|
|
AuditProjectRepository projectRepo) {
|
|
this.evidenceRepo = evidenceRepo;
|
|
this.projectRepo = projectRepo;
|
|
}
|
|
|
|
@GetMapping
|
|
public ApiResp<List<AuditEvidence>> list(
|
|
@RequestParam(required = false) Long projectId,
|
|
@RequestParam(required = false) Long workPaperId,
|
|
@RequestParam(required = false) Long findingId,
|
|
@RequestParam(required = false) String category) {
|
|
if (projectId != null) {
|
|
return ApiResp.ok(evidenceRepo.findByProjectId(projectId));
|
|
}
|
|
if (workPaperId != null) {
|
|
return ApiResp.ok(evidenceRepo.findByWorkPaperId(workPaperId));
|
|
}
|
|
if (findingId != null) {
|
|
return ApiResp.ok(evidenceRepo.findByFindingId(findingId));
|
|
}
|
|
if (category != null && !category.isBlank()) {
|
|
return ApiResp.ok(evidenceRepo.findByCategory(category));
|
|
}
|
|
return ApiResp.ok(evidenceRepo.findByOrderByIdDesc());
|
|
}
|
|
|
|
@GetMapping("/{id}")
|
|
public ApiResp<AuditEvidence> get(@PathVariable Long id) {
|
|
return ApiResp.ok(load(id));
|
|
}
|
|
|
|
/** 关键词检索(title/keywords/tags)。 */
|
|
@GetMapping("/search")
|
|
public ApiResp<List<AuditEvidence>> search(@RequestParam String q) {
|
|
if (q == null || q.isBlank()) {
|
|
return ApiResp.ok(List.of());
|
|
}
|
|
return ApiResp.ok(
|
|
evidenceRepo.findByTitleContainingOrKeywordsContainingOrTagsContaining(q, q, q));
|
|
}
|
|
|
|
/** 证据类别统计(各类别数量)。 */
|
|
@GetMapping("/categories")
|
|
public ApiResp<Object> categories() {
|
|
List<AuditEvidence> all = evidenceRepo.findAll();
|
|
java.util.Map<String, Long> byCategory = new java.util.LinkedHashMap<>();
|
|
for (AuditEvidence e : all) {
|
|
String cat = e.getCategory() == null ? "其他" : e.getCategory();
|
|
byCategory.merge(cat, 1L, Long::sum);
|
|
}
|
|
return ApiResp.ok(byCategory);
|
|
}
|
|
|
|
public record CreateRequest(
|
|
Long projectId, Long workPaperId, Long findingId,
|
|
String title, String category, String tags, String keywords,
|
|
String fileRef, String fileName, String fileType, String fileHash,
|
|
Boolean keyEvidence, String watermark, String description,
|
|
String collectedBy, String collectDate) {
|
|
}
|
|
|
|
@PostMapping
|
|
public ApiResp<AuditEvidence> create(@RequestBody CreateRequest req) {
|
|
if (req.title() == null || req.title().isBlank()) {
|
|
throw new ApiException(400, "证据标题不能为空");
|
|
}
|
|
if (req.projectId() == null) {
|
|
throw new ApiException(400, "请选择关联审计项目");
|
|
}
|
|
AuditEvidence e = new AuditEvidence();
|
|
e.setEvidenceNo(nextEvidenceNo());
|
|
e.setProjectId(req.projectId());
|
|
projectRepo.findById(req.projectId()).ifPresent(p -> e.setProjectName(p.getName()));
|
|
e.setWorkPaperId(req.workPaperId());
|
|
e.setFindingId(req.findingId());
|
|
e.setTitle(req.title());
|
|
e.setCategory(req.category() == null || req.category().isBlank() ? "其他" : req.category());
|
|
e.setTags(req.tags());
|
|
e.setKeywords(req.keywords());
|
|
e.setFileRef(req.fileRef());
|
|
e.setFileName(req.fileName());
|
|
e.setFileType(req.fileType());
|
|
e.setFileHash(req.fileHash());
|
|
e.setKeyEvidence(req.keyEvidence() != null && req.keyEvidence());
|
|
e.setWatermark(req.watermark());
|
|
e.setDescription(req.description());
|
|
e.setCollectedBy(req.collectedBy());
|
|
e.setCollectDate(req.collectDate() != null && !req.collectDate().isBlank()
|
|
? req.collectDate() : LocalDate.now().toString());
|
|
e.setCreatedAt(Instant.now());
|
|
return ApiResp.ok(evidenceRepo.save(e));
|
|
}
|
|
|
|
public record UpdateRequest(
|
|
String title, String category, String tags, String keywords,
|
|
String fileRef, String fileName, String fileType, String fileHash,
|
|
Boolean keyEvidence, String watermark, String description) {
|
|
}
|
|
|
|
@PutMapping("/{id}")
|
|
public ApiResp<AuditEvidence> update(@PathVariable Long id, @RequestBody UpdateRequest req) {
|
|
AuditEvidence e = load(id);
|
|
if (req.title() != null && !req.title().isBlank()) e.setTitle(req.title());
|
|
if (req.category() != null) e.setCategory(req.category());
|
|
if (req.tags() != null) e.setTags(req.tags());
|
|
if (req.keywords() != null) e.setKeywords(req.keywords());
|
|
if (req.fileRef() != null) e.setFileRef(req.fileRef());
|
|
if (req.fileName() != null) e.setFileName(req.fileName());
|
|
if (req.fileType() != null) e.setFileType(req.fileType());
|
|
if (req.fileHash() != null) e.setFileHash(req.fileHash());
|
|
if (req.keyEvidence() != null) e.setKeyEvidence(req.keyEvidence());
|
|
if (req.watermark() != null) e.setWatermark(req.watermark());
|
|
if (req.description() != null) e.setDescription(req.description());
|
|
return ApiResp.ok(evidenceRepo.save(e));
|
|
}
|
|
|
|
@DeleteMapping("/{id}")
|
|
public ApiResp<Void> delete(@PathVariable Long id) {
|
|
if (!evidenceRepo.existsById(id)) {
|
|
throw new NotFoundException("审计证据不存在: " + id);
|
|
}
|
|
evidenceRepo.deleteById(id);
|
|
return ApiResp.ok(null);
|
|
}
|
|
|
|
private AuditEvidence load(Long id) {
|
|
return evidenceRepo.findById(id)
|
|
.orElseThrow(() -> new NotFoundException("审计证据不存在: " + id));
|
|
}
|
|
|
|
private String nextEvidenceNo() {
|
|
String year = String.valueOf(Year.now().getValue());
|
|
long seq = evidenceRepo.count() + 1;
|
|
return String.format("AE-%s-%03d", year, seq);
|
|
}
|
|
}
|