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,237 @@
|
||||
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.PmtBankEvaluation;
|
||||
import com.kaidi.oa.domain.PmtBankRelation;
|
||||
import com.kaidi.oa.repository.PmtBankEvaluationRepository;
|
||||
import com.kaidi.oa.repository.PmtBankRelationRepository;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 金融机构定期评价管理(金融办·融资管理·机构综合评分与分级)。
|
||||
* 覆盖 Gap-1:「机构『定期评价』无专属评价记录工作流(当前 rating 字段为手填文本),
|
||||
* 机构综合评分与分级管理无流程承载」。
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. 评价记录 CRUD(四维打分→综合评分→自动定级 A/B/C/D)。
|
||||
* 2. 查询指定机构的评价历史(时序分析)。
|
||||
* 3. 评价完成后自动回写 PmtBankRelation.rating 字段(保持主档评级最新)。
|
||||
* 4. 分机构综合评分趋势摘要(用于「分级统计与对比」)。
|
||||
* <p>
|
||||
* 写口通过 AuthInterceptor FINANCE_PREFIXES (/api/oa/pmt-bank-evals) 保护,ADMIN/APPROVER 方可录入。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/oa/pmt-bank-evals")
|
||||
public class PmtBankEvalController {
|
||||
|
||||
private final PmtBankEvaluationRepository evalRepo;
|
||||
private final PmtBankRelationRepository bankRelRepo;
|
||||
|
||||
public PmtBankEvalController(PmtBankEvaluationRepository evalRepo,
|
||||
PmtBankRelationRepository bankRelRepo) {
|
||||
this.evalRepo = evalRepo;
|
||||
this.bankRelRepo = bankRelRepo;
|
||||
}
|
||||
|
||||
// ---------- CRUD ----------
|
||||
|
||||
@GetMapping
|
||||
public ApiResp<List<PmtBankEvaluation>> list(
|
||||
@RequestParam(required = false) Long bankRelId,
|
||||
@RequestParam(required = false) String status) {
|
||||
if (bankRelId != null) {
|
||||
return ApiResp.ok(evalRepo.findByBankRelIdOrderByEvalDateDesc(bankRelId));
|
||||
}
|
||||
if (status != null && !status.isBlank()) {
|
||||
return ApiResp.ok(evalRepo.findByStatus(status));
|
||||
}
|
||||
return ApiResp.ok(evalRepo.findAll());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResp<PmtBankEvaluation> get(@PathVariable Long id) {
|
||||
return ApiResp.ok(evalRepo.findById(id)
|
||||
.orElseThrow(() -> new NotFoundException("评价记录不存在: " + id)));
|
||||
}
|
||||
|
||||
public record EvalRequest(
|
||||
Long bankRelId, String evalPeriod, String evalDate,
|
||||
Integer scoreApprovalSpeed, Integer scoreServiceQuality,
|
||||
Integer scoreRateCompetitiveness, Integer scoreCooperation,
|
||||
String remark, String evaluator, String status) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建评价记录:四维打分,后端自动计算综合评分(均值)和评级(A≥80/B≥60/C≥40/D<40)。
|
||||
* 评价状态「已完成」时自动回写 PmtBankRelation.rating 字段,更新主档评级。
|
||||
*/
|
||||
@PostMapping
|
||||
@Transactional
|
||||
public ApiResp<PmtBankEvaluation> create(@RequestBody EvalRequest req) {
|
||||
if (req.bankRelId() == null) {
|
||||
throw new ApiException(400, "bankRelId 不能为空,请指定关联的金融机构档案");
|
||||
}
|
||||
PmtBankRelation rel = bankRelRepo.findById(req.bankRelId())
|
||||
.orElseThrow(() -> new NotFoundException("金融机构档案不存在: " + req.bankRelId()));
|
||||
|
||||
PmtBankEvaluation ev = new PmtBankEvaluation();
|
||||
ev.setBankRelId(req.bankRelId());
|
||||
ev.setInstitutionName(rel.getInstitutionName());
|
||||
ev.setEvalPeriod(req.evalPeriod() == null ? currentPeriod() : req.evalPeriod());
|
||||
ev.setEvalDate(req.evalDate() == null ? LocalDate.now().toString() : req.evalDate());
|
||||
ev.setScoreApprovalSpeed(clampScore(req.scoreApprovalSpeed()));
|
||||
ev.setScoreServiceQuality(clampScore(req.scoreServiceQuality()));
|
||||
ev.setScoreRateCompetitiveness(clampScore(req.scoreRateCompetitiveness()));
|
||||
ev.setScoreCooperation(clampScore(req.scoreCooperation()));
|
||||
int total = calcTotal(ev);
|
||||
ev.setTotalScore(total);
|
||||
ev.setGrade(calcGrade(total));
|
||||
ev.setRemark(req.remark());
|
||||
ev.setEvaluator(req.evaluator());
|
||||
ev.setStatus(req.status() == null || req.status().isBlank() ? "已完成" : req.status());
|
||||
ev.setCreatedAt(Instant.now());
|
||||
PmtBankEvaluation saved = evalRepo.save(ev);
|
||||
|
||||
// 评价完成后自动回写机构档案评级
|
||||
if ("已完成".equals(saved.getStatus())) {
|
||||
rel.setRating(saved.getGrade());
|
||||
bankRelRepo.save(rel);
|
||||
}
|
||||
return ApiResp.ok(saved);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
@Transactional
|
||||
public ApiResp<PmtBankEvaluation> update(@PathVariable Long id, @RequestBody EvalRequest req) {
|
||||
PmtBankEvaluation ev = evalRepo.findById(id)
|
||||
.orElseThrow(() -> new NotFoundException("评价记录不存在: " + id));
|
||||
if (req.evalPeriod() != null) ev.setEvalPeriod(req.evalPeriod());
|
||||
if (req.evalDate() != null) ev.setEvalDate(req.evalDate());
|
||||
if (req.scoreApprovalSpeed() != null) ev.setScoreApprovalSpeed(clampScore(req.scoreApprovalSpeed()));
|
||||
if (req.scoreServiceQuality() != null) ev.setScoreServiceQuality(clampScore(req.scoreServiceQuality()));
|
||||
if (req.scoreRateCompetitiveness() != null) ev.setScoreRateCompetitiveness(clampScore(req.scoreRateCompetitiveness()));
|
||||
if (req.scoreCooperation() != null) ev.setScoreCooperation(clampScore(req.scoreCooperation()));
|
||||
// 重算评分
|
||||
int total = calcTotal(ev);
|
||||
ev.setTotalScore(total);
|
||||
ev.setGrade(calcGrade(total));
|
||||
if (req.remark() != null) ev.setRemark(req.remark());
|
||||
if (req.evaluator() != null) ev.setEvaluator(req.evaluator());
|
||||
if (req.status() != null && !req.status().isBlank()) ev.setStatus(req.status());
|
||||
PmtBankEvaluation saved = evalRepo.save(ev);
|
||||
|
||||
// 如果评价已完成,同步回写机构档案评级
|
||||
if ("已完成".equals(saved.getStatus()) && saved.getBankRelId() != null) {
|
||||
bankRelRepo.findById(saved.getBankRelId()).ifPresent(rel -> {
|
||||
rel.setRating(saved.getGrade());
|
||||
bankRelRepo.save(rel);
|
||||
});
|
||||
}
|
||||
return ApiResp.ok(saved);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Transactional
|
||||
public ApiResp<Void> delete(@PathVariable Long id) {
|
||||
if (!evalRepo.existsById(id)) {
|
||||
throw new NotFoundException("评价记录不存在: " + id);
|
||||
}
|
||||
evalRepo.deleteById(id);
|
||||
return ApiResp.ok(null);
|
||||
}
|
||||
|
||||
// ---------- 评分趋势与分级统计 ----------
|
||||
|
||||
public record EvalSummaryRow(Long bankRelId, String institutionName,
|
||||
String currentGrade, int evalCount,
|
||||
double avgTotalScore, String trend) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 各机构综合评分摘要(用于分级管理与对比)。
|
||||
* 输出:每个机构的当前评级、历史评价次数、平均综合评分、
|
||||
* 趋势(最近2次:上升/下降/持平/仅1次)。
|
||||
*/
|
||||
@GetMapping("/summary")
|
||||
public ApiResp<List<EvalSummaryRow>> summary() {
|
||||
List<PmtBankRelation> relations = bankRelRepo.findAll();
|
||||
List<PmtBankEvaluation> allEvals = evalRepo.findAll();
|
||||
|
||||
// 按机构分组评价记录
|
||||
Map<Long, List<PmtBankEvaluation>> byRel = allEvals.stream()
|
||||
.filter(e -> e.getBankRelId() != null)
|
||||
.collect(Collectors.groupingBy(PmtBankEvaluation::getBankRelId));
|
||||
|
||||
List<EvalSummaryRow> rows = relations.stream().map(rel -> {
|
||||
List<PmtBankEvaluation> evals = byRel.getOrDefault(rel.getId(), List.of());
|
||||
// 按日期倒序
|
||||
List<PmtBankEvaluation> sorted = evals.stream()
|
||||
.sorted((a, b) -> nvl(b.getEvalDate()).compareTo(nvl(a.getEvalDate())))
|
||||
.toList();
|
||||
double avg = sorted.stream()
|
||||
.mapToInt(e -> e.getTotalScore() == null ? 0 : e.getTotalScore())
|
||||
.average().orElse(0);
|
||||
String trend = "无评价记录";
|
||||
if (sorted.size() == 1) {
|
||||
trend = "仅1次评价";
|
||||
} else if (sorted.size() >= 2) {
|
||||
int latest = sorted.get(0).getTotalScore() == null ? 0 : sorted.get(0).getTotalScore();
|
||||
int prev = sorted.get(1).getTotalScore() == null ? 0 : sorted.get(1).getTotalScore();
|
||||
trend = latest > prev ? "上升" : latest < prev ? "下降" : "持平";
|
||||
}
|
||||
return new EvalSummaryRow(rel.getId(), rel.getInstitutionName(),
|
||||
rel.getRating() == null ? "-" : rel.getRating(),
|
||||
sorted.size(), avg, trend);
|
||||
}).toList();
|
||||
|
||||
return ApiResp.ok(rows);
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private static int clampScore(Integer v) {
|
||||
if (v == null) return 0;
|
||||
return Math.max(0, Math.min(100, v));
|
||||
}
|
||||
|
||||
private static int calcTotal(PmtBankEvaluation ev) {
|
||||
int s1 = ev.getScoreApprovalSpeed() == null ? 0 : ev.getScoreApprovalSpeed();
|
||||
int s2 = ev.getScoreServiceQuality() == null ? 0 : ev.getScoreServiceQuality();
|
||||
int s3 = ev.getScoreRateCompetitiveness() == null ? 0 : ev.getScoreRateCompetitiveness();
|
||||
int s4 = ev.getScoreCooperation() == null ? 0 : ev.getScoreCooperation();
|
||||
return (s1 + s2 + s3 + s4) / 4;
|
||||
}
|
||||
|
||||
private static String calcGrade(int total) {
|
||||
if (total >= 80) return "A";
|
||||
if (total >= 60) return "B";
|
||||
if (total >= 40) return "C";
|
||||
return "D";
|
||||
}
|
||||
|
||||
private static String currentPeriod() {
|
||||
java.time.YearMonth ym = java.time.YearMonth.now();
|
||||
return ym.getYear() + "Q" + ((ym.getMonthValue() - 1) / 3 + 1);
|
||||
}
|
||||
|
||||
private static String nvl(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user