Files
ERP/oa-backend/src/main/java/com/kaidi/oa/web/LegalRiskMeasureController.java
T
QiufengandClaude Opus 4.8 5e51dc3f56 SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(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>
2026-06-15 19:19:15 +08:00

218 lines
9.1 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.LegalRiskMeasure;
import com.kaidi.oa.repository.LegalRiskMeasureRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 风险应对措施执行跟踪(法务合规·风险控制与评估)。
* 针对中高风险制定的具体应对措施清单,分配责任人/部门/完成时限,跟踪执行状态。
* 提供逾期预警接口(/overdue),列出 dueDate < today 且 status 非已完成/已取消 的超期措施。
* 状态流转:待执行→执行中→已完成(或随时取消)。
*
* 读口含责任人/措施描述等内部治理信息,需认证。
*/
@RestController
@RequestMapping("/api/oa/legal-risk-measures")
public class LegalRiskMeasureController {
private static final List<String> CLOSED_STATUSES = List.of("已完成", "已取消");
private final LegalRiskMeasureRepository repo;
public LegalRiskMeasureController(LegalRiskMeasureRepository repo) {
this.repo = repo;
}
@GetMapping
public ApiResp<List<LegalRiskMeasure>> list(
@RequestParam(required = false) String status,
@RequestParam(required = false) Long riskAssessId,
@RequestParam(required = false) String owner,
@RequestParam(required = false) String ownerDept) {
if (riskAssessId != null) {
return ApiResp.ok(repo.findByRiskAssessId(riskAssessId));
}
if (status != null && !status.isBlank()) {
return ApiResp.ok(repo.findByStatus(status));
}
if (owner != null && !owner.isBlank()) {
return ApiResp.ok(repo.findByOwner(owner));
}
if (ownerDept != null && !ownerDept.isBlank()) {
return ApiResp.ok(repo.findByOwnerDept(ownerDept));
}
return ApiResp.ok(repo.findAll());
}
@GetMapping("/{id}")
public ApiResp<LegalRiskMeasure> get(@PathVariable Long id) {
return ApiResp.ok(repo.findById(id)
.orElseThrow(() -> new NotFoundException("风险应对措施不存在: " + id)));
}
public record MeasureRequest(
Long riskAssessId, String measureNo, String title, String description,
String owner, String ownerDept, String dueDate, String status,
String completedDate, String result) {
}
@PostMapping
@Transactional
public ApiResp<LegalRiskMeasure> create(@RequestBody MeasureRequest req) {
if (req.title() == null || req.title().isBlank()) {
throw new ApiException(400, "措施标题(title) 不能为空");
}
if (req.dueDate() == null || req.dueDate().isBlank()) {
throw new ApiException(400, "完成时限(dueDate) 不能为空");
}
LegalRiskMeasure m = new LegalRiskMeasure();
m.setRiskAssessId(req.riskAssessId());
m.setMeasureNo(req.measureNo() == null || req.measureNo().isBlank()
? "FXCS-" + (repo.count() + 1) : req.measureNo());
m.setTitle(req.title());
m.setDescription(req.description());
m.setOwner(req.owner());
m.setOwnerDept(req.ownerDept());
m.setDueDate(req.dueDate());
m.setStatus(req.status() == null || req.status().isBlank() ? "待执行" : req.status());
m.setCompletedDate(req.completedDate());
m.setResult(req.result());
m.setCreatedAt(Instant.now());
return ApiResp.ok(repo.save(m));
}
@PatchMapping("/{id}")
@Transactional
public ApiResp<LegalRiskMeasure> update(@PathVariable Long id, @RequestBody MeasureRequest req) {
LegalRiskMeasure m = repo.findById(id)
.orElseThrow(() -> new NotFoundException("风险应对措施不存在: " + id));
if (req.title() != null && !req.title().isBlank()) m.setTitle(req.title());
if (req.description() != null) m.setDescription(req.description());
if (req.owner() != null) m.setOwner(req.owner());
if (req.ownerDept() != null) m.setOwnerDept(req.ownerDept());
if (req.dueDate() != null && !req.dueDate().isBlank()) m.setDueDate(req.dueDate());
if (req.status() != null && !req.status().isBlank()) m.setStatus(req.status());
if (req.completedDate() != null) m.setCompletedDate(req.completedDate());
if (req.result() != null) m.setResult(req.result());
return ApiResp.ok(repo.save(m));
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp<Void> delete(@PathVariable Long id) {
if (!repo.existsById(id)) {
throw new NotFoundException("风险应对措施不存在: " + id);
}
repo.deleteById(id);
return ApiResp.ok(null);
}
// ---------- 状态流转 ----------
@PostMapping("/{id}/start")
@Transactional
public ApiResp<LegalRiskMeasure> start(@PathVariable Long id) {
LegalRiskMeasure m = repo.findById(id)
.orElseThrow(() -> new NotFoundException("风险应对措施不存在: " + id));
if (!"待执行".equals(m.getStatus())) {
throw new ApiException(409, "只有「待执行」状态的措施可以启动");
}
m.setStatus("执行中");
return ApiResp.ok(repo.save(m));
}
public record CompleteRequest(String result) {
}
@PostMapping("/{id}/complete")
@Transactional
public ApiResp<LegalRiskMeasure> complete(@PathVariable Long id, @RequestBody CompleteRequest req) {
LegalRiskMeasure m = repo.findById(id)
.orElseThrow(() -> new NotFoundException("风险应对措施不存在: " + id));
if ("已完成".equals(m.getStatus()) || "已取消".equals(m.getStatus())) {
throw new ApiException(409, "该措施已处于终态,无法再次标记完成");
}
m.setStatus("已完成");
m.setCompletedDate(LocalDate.now().toString());
if (req.result() != null && !req.result().isBlank()) {
m.setResult(req.result());
}
return ApiResp.ok(repo.save(m));
}
@PostMapping("/{id}/cancel")
@Transactional
public ApiResp<LegalRiskMeasure> cancel(@PathVariable Long id) {
LegalRiskMeasure m = repo.findById(id)
.orElseThrow(() -> new NotFoundException("风险应对措施不存在: " + id));
if ("已完成".equals(m.getStatus())) {
throw new ApiException(409, "已完成的措施不可取消");
}
m.setStatus("已取消");
return ApiResp.ok(repo.save(m));
}
// ---------- 逾期预警 ----------
/**
* 返回 dueDate < 今日 且 status 不是「已完成」/「已取消」 的超期措施。
* 供法务每日检查应对措施跟踪进度。
*/
@GetMapping("/overdue")
public ApiResp<List<LegalRiskMeasure>> overdue() {
String today = LocalDate.now().toString();
List<LegalRiskMeasure> all = repo.findAll();
return ApiResp.ok(all.stream()
.filter(m -> m.getDueDate() != null
&& m.getDueDate().compareTo(today) < 0
&& !CLOSED_STATUSES.contains(m.getStatus()))
.toList());
}
// ---------- 统计 ----------
public record MeasureStats(int total, int pending, int inProgress, int completed, int cancelled,
int overdueCount, Map<String, Integer> byDept) {
}
@GetMapping("/stats")
public ApiResp<MeasureStats> stats() {
String today = LocalDate.now().toString();
List<LegalRiskMeasure> all = repo.findAll();
int pending = 0, inProgress = 0, completed = 0, cancelled = 0, overdueCount = 0;
Map<String, Integer> byDept = new LinkedHashMap<>();
for (LegalRiskMeasure m : all) {
String s = m.getStatus() == null ? "待执行" : m.getStatus();
if ("待执行".equals(s)) pending++;
else if ("执行中".equals(s)) inProgress++;
else if ("已完成".equals(s)) completed++;
else if ("已取消".equals(s)) cancelled++;
if (m.getDueDate() != null && m.getDueDate().compareTo(today) < 0
&& !CLOSED_STATUSES.contains(s)) {
overdueCount++;
}
byDept.merge(m.getOwnerDept() == null ? "未分配" : m.getOwnerDept(), 1, Integer::sum);
}
return ApiResp.ok(new MeasureStats(all.size(), pending, inProgress, completed, cancelled, overdueCount, byDept));
}
}