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 字段为手填文本), * 机构综合评分与分级管理无流程承载」。 *

* 功能: * 1. 评价记录 CRUD(四维打分→综合评分→自动定级 A/B/C/D)。 * 2. 查询指定机构的评价历史(时序分析)。 * 3. 评价完成后自动回写 PmtBankRelation.rating 字段(保持主档评级最新)。 * 4. 分机构综合评分趋势摘要(用于「分级统计与对比」)。 *

* 写口通过 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( @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 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 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 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 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> summary() { List relations = bankRelRepo.findAll(); List allEvals = evalRepo.findAll(); // 按机构分组评价记录 Map> byRel = allEvals.stream() .filter(e -> e.getBankRelId() != null) .collect(Collectors.groupingBy(PmtBankEvaluation::getBankRelId)); List rows = relations.stream().map(rel -> { List evals = byRel.getOrDefault(rel.getId(), List.of()); // 按日期倒序 List 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; } }