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>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user