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:
Qiufeng
2026-06-15 19:19:15 +08:00
co-authored by Claude Opus 4.8
commit 5e51dc3f56
10584 changed files with 2501339 additions and 0 deletions
@@ -0,0 +1,469 @@
package com.kaidi.oa.seed;
import com.kaidi.oa.common.Money;
import com.kaidi.oa.domain.Financing;
import com.kaidi.oa.domain.FinancingPolicy;
import com.kaidi.oa.domain.PmtBankEvaluation;
import com.kaidi.oa.domain.PmtBankRelation;
import com.kaidi.oa.domain.RepaymentPlan;
import com.kaidi.oa.repository.FinancingPolicyRepository;
import com.kaidi.oa.repository.FinancingRepository;
import com.kaidi.oa.repository.PmtBankEvaluationRepository;
import com.kaidi.oa.repository.PmtBankRelationRepository;
import com.kaidi.oa.repository.RepaymentPlanRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.Instant;
/**
* 融资域演示数据(金融办)。
* 补建 Gap-1/2/5 的活体演示数据:金融机构档案、融资台账、机构定期评价、融资政策规则、还款计划。
* 只在对应表为空时执行,防止重复。@Order(20) 在 DataSeeder(@Order(1)) 之后运行。
*/
@Order(20)
@Component
public class FinancingDomainSeeder implements CommandLineRunner {
private final PmtBankRelationRepository bankRelRepo;
private final FinancingRepository financingRepo;
private final RepaymentPlanRepository planRepo;
private final PmtBankEvaluationRepository evalRepo;
private final FinancingPolicyRepository policyRepo;
public FinancingDomainSeeder(PmtBankRelationRepository bankRelRepo,
FinancingRepository financingRepo,
RepaymentPlanRepository planRepo,
PmtBankEvaluationRepository evalRepo,
FinancingPolicyRepository policyRepo) {
this.bankRelRepo = bankRelRepo;
this.financingRepo = financingRepo;
this.planRepo = planRepo;
this.evalRepo = evalRepo;
this.policyRepo = policyRepo;
}
@Override
public void run(String... args) {
seedBankRelations();
seedFinancings();
seedPolicies();
seedEvaluations();
}
private void seedBankRelations() {
if (bankRelRepo.count() > 0) return;
PmtBankRelation r1 = new PmtBankRelation();
r1.setInstitutionName("工商银行长沙分行");
r1.setInstitutionType("商业银行");
r1.setBranch("长沙分行公司业务部");
r1.setAccountManager("张磊");
r1.setManagerPhone("0731-88882001");
r1.setTotalCreditLimit(Money.of(200000000.0));
r1.setCooperationStartDate("2019-03-01");
r1.setRating("A");
r1.setAvgApprovalDays(7);
r1.setRemark("主要合作银行,综合授信2亿,流贷+固贷均有合作历史。");
r1.setStatus("合作中");
r1.setCreatedBy("admin");
r1.setCreatedAt(Instant.now());
bankRelRepo.save(r1);
PmtBankRelation r2 = new PmtBankRelation();
r2.setInstitutionName("建设银行株洲支行");
r2.setInstitutionType("商业银行");
r2.setBranch("株洲高新区支行");
r2.setAccountManager("李梅");
r2.setManagerPhone("0733-77773001");
r2.setTotalCreditLimit(Money.of(100000000.0));
r2.setCooperationStartDate("2020-06-15");
r2.setRating("B");
r2.setAvgApprovalDays(10);
r2.setRemark("次要银行,主要为子公司株洲项目配套贷款。");
r2.setStatus("合作中");
r2.setCreatedBy("admin");
r2.setCreatedAt(Instant.now());
bankRelRepo.save(r2);
PmtBankRelation r3 = new PmtBankRelation();
r3.setInstitutionName("国家开发银行湖南省分行");
r3.setInstitutionType("政策性银行");
r3.setBranch("湖南省分行基础设施部");
r3.setAccountManager("王强");
r3.setManagerPhone("0731-66661001");
r3.setTotalCreditLimit(Money.of(500000000.0));
r3.setCooperationStartDate("2021-01-20");
r3.setRating("A");
r3.setAvgApprovalDays(15);
r3.setRemark("政策性长期资金来源,主要用于环保固定资产投资项目贷款,利率优惠。");
r3.setStatus("合作中");
r3.setCreatedBy("admin");
r3.setCreatedAt(Instant.now());
bankRelRepo.save(r3);
PmtBankRelation r4 = new PmtBankRelation();
r4.setInstitutionName("中信银行长沙分行");
r4.setInstitutionType("商业银行");
r4.setBranch("长沙分行企业金融部");
r4.setAccountManager("陈晓");
r4.setManagerPhone("0731-55550801");
r4.setTotalCreditLimit(Money.of(80000000.0));
r4.setCooperationStartDate("2022-09-01");
r4.setRating("B");
r4.setAvgApprovalDays(8);
r4.setRemark("近两年新增合作银行,流动资金贷款为主,银行承兑汇票业务活跃。");
r4.setStatus("合作中");
r4.setCreatedBy("admin");
r4.setCreatedAt(Instant.now());
bankRelRepo.save(r4);
}
private void seedFinancings() {
if (financingRepo.count() > 0) return;
Financing f1 = new Financing();
f1.setCode("RZ-2024-001");
f1.setLender("工商银行长沙分行");
f1.setFinancingType("银行贷款");
f1.setCreditLimit(Money.of(50000000.0));
f1.setAmount(Money.of(50000000.0));
f1.setCurrency("人民币");
f1.setRate(3.85);
f1.setStartDate("2024-01-15");
f1.setEndDate("2026-01-14");
f1.setTermMonths(24);
f1.setRepayMethod("按期付息到期还本");
f1.setStatus("还款中");
f1.setCompanySubject("凯迪科技股份有限公司");
f1.setPurpose("补充流动资金");
f1.setOwner("admin");
f1.setCreatedAt(Instant.now());
Financing sf1 = financingRepo.save(f1);
// 为 f1 生成还款计划(按期付息到期还本,24期,月息)
seedRepaymentPlan(sf1, 24, 3.85, "按期付息到期还本", "2024-01-15");
Financing f2 = new Financing();
f2.setCode("RZ-2024-002");
f2.setLender("国家开发银行湖南省分行");
f2.setFinancingType("银行贷款");
f2.setCreditLimit(Money.of(100000000.0));
f2.setAmount(Money.of(80000000.0));
f2.setCurrency("人民币");
f2.setRate(3.20);
f2.setStartDate("2024-03-01");
f2.setEndDate("2029-02-28");
f2.setTermMonths(60);
f2.setRepayMethod("等额本金");
f2.setStatus("还款中");
f2.setCompanySubject("凯迪科技股份有限公司");
f2.setPurpose("污水处理厂固定资产投资");
f2.setOwner("admin");
f2.setCreatedAt(Instant.now());
Financing sf2 = financingRepo.save(f2);
seedRepaymentPlan(sf2, 60, 3.20, "等额本金", "2024-03-01");
Financing f3 = new Financing();
f3.setCode("RZ-2024-003");
f3.setLender("建设银行株洲支行");
f3.setFinancingType("银行贷款");
f3.setCreditLimit(Money.of(20000000.0));
f3.setAmount(Money.of(18000000.0));
f3.setCurrency("人民币");
f3.setRate(4.35);
f3.setStartDate("2024-06-01");
f3.setEndDate("2025-05-31");
f3.setTermMonths(12);
f3.setRepayMethod("到期一次还本付息");
f3.setStatus("还款中");
f3.setCompanySubject("株洲凯迪环保科技有限公司");
f3.setPurpose("补充流动资金");
f3.setOwner("admin");
f3.setCreatedAt(Instant.now());
Financing sf3 = financingRepo.save(f3);
seedRepaymentPlan(sf3, 12, 4.35, "到期一次还本付息", "2024-06-01");
Financing f4 = new Financing();
f4.setCode("RZ-2025-001");
f4.setLender("中信银行长沙分行");
f4.setFinancingType("银行贷款");
f4.setCreditLimit(Money.of(30000000.0));
f4.setAmount(Money.of(30000000.0));
f4.setCurrency("人民币");
f4.setRate(4.10);
f4.setStartDate("2025-02-01");
f4.setEndDate("2026-01-31");
f4.setTermMonths(12);
f4.setRepayMethod("等额本息");
f4.setStatus("还款中");
f4.setCompanySubject("凯迪科技股份有限公司");
f4.setPurpose("采购原材料及备品备件");
f4.setOwner("admin");
f4.setCreatedAt(Instant.now());
Financing sf4 = financingRepo.save(f4);
seedRepaymentPlan(sf4, 12, 4.10, "等额本息", "2025-02-01");
// 一笔申请中
Financing f5 = new Financing();
f5.setCode("RZ-2025-002");
f5.setLender("工商银行长沙分行");
f5.setFinancingType("银行承兑汇票");
f5.setCreditLimit(Money.of(20000000.0));
f5.setAmount(Money.of(20000000.0));
f5.setCurrency("人民币");
f5.setRate(2.50);
f5.setStartDate("2025-07-01");
f5.setEndDate("2026-06-30");
f5.setTermMonths(12);
f5.setRepayMethod("到期一次还本付息");
f5.setStatus("申请中");
f5.setCompanySubject("凯迪科技股份有限公司");
f5.setPurpose("支付设备采购款(银承)");
f5.setOwner("admin");
f5.setCreatedAt(Instant.now());
financingRepo.save(f5);
// 一笔已结清
Financing f6 = new Financing();
f6.setCode("RZ-2023-003");
f6.setLender("建设银行株洲支行");
f6.setFinancingType("银行贷款");
f6.setCreditLimit(Money.of(10000000.0));
f6.setAmount(Money.of(10000000.0));
f6.setCurrency("人民币");
f6.setRate(4.60);
f6.setStartDate("2023-01-01");
f6.setEndDate("2024-12-31");
f6.setTermMonths(24);
f6.setRepayMethod("等额本息");
f6.setStatus("已结清");
f6.setCompanySubject("株洲凯迪环保科技有限公司");
f6.setPurpose("经营周转资金");
f6.setOwner("admin");
f6.setCreatedAt(Instant.now());
financingRepo.save(f6);
}
private void seedRepaymentPlan(Financing f, int term, double annualRate, String method, String startDate) {
if (planRepo.findByFinancingIdOrderByPeriodNoAsc(f.getId()).size() > 0) return;
double monthlyRate = annualRate / 100.0 / 12.0;
double principal = Money.nz(f.getAmount()).doubleValue();
java.time.LocalDate start;
try {
start = java.time.LocalDate.parse(startDate);
} catch (Exception e) {
start = java.time.LocalDate.now();
}
if ("到期一次还本付息".equals(method)) {
double interest = principal * monthlyRate * term;
RepaymentPlan p = buildPlan(f, 1, start.plusMonths(term).toString(),
new BigDecimal(principal).setScale(2, java.math.RoundingMode.HALF_UP),
Money.of(interest), "未还");
planRepo.save(p);
return;
}
if ("按期付息到期还本".equals(method)) {
BigDecimal totalPrincipal = Money.nz(f.getAmount());
for (int i = 1; i <= term; i++) {
double interest = principal * monthlyRate;
BigDecimal prin = (i == term) ? totalPrincipal : BigDecimal.ZERO;
RepaymentPlan p = buildPlan(f, i, start.plusMonths(i).toString(),
Money.nz(prin), Money.of(interest), "未还");
planRepo.save(p);
}
return;
}
if ("等额本金".equals(method)) {
BigDecimal totalPrincipal = Money.nz(f.getAmount());
BigDecimal perPrincipal = totalPrincipal.divide(BigDecimal.valueOf(term), 2, java.math.RoundingMode.HALF_UP);
double balance = principal;
BigDecimal acc = BigDecimal.ZERO;
for (int i = 1; i <= term; i++) {
double interest = balance * monthlyRate;
BigDecimal prin = (i == term) ? Money.sub(totalPrincipal, acc) : perPrincipal;
acc = Money.add(acc, prin);
RepaymentPlan p = buildPlan(f, i, start.plusMonths(i).toString(),
Money.nz(prin), Money.of(interest), "未还");
planRepo.save(p);
balance -= prin.doubleValue();
}
return;
}
// 等额本息
BigDecimal totalPrincipal = Money.nz(f.getAmount());
double installment;
if (monthlyRate == 0) {
installment = principal / term;
} else {
double pow = Math.pow(1 + monthlyRate, term);
installment = principal * monthlyRate * pow / (pow - 1);
}
double balance = principal;
BigDecimal acc = BigDecimal.ZERO;
for (int i = 1; i <= term; i++) {
double interest = balance * monthlyRate;
double prinD = installment - interest;
BigDecimal prin = (i == term) ? Money.sub(totalPrincipal, acc) : Money.of(prinD);
acc = Money.add(acc, prin);
RepaymentPlan p = buildPlan(f, i, start.plusMonths(i).toString(),
Money.nz(prin), Money.of(interest), "未还");
planRepo.save(p);
balance -= prin.doubleValue();
}
}
private RepaymentPlan buildPlan(Financing f, int no, String dueDate,
BigDecimal principal, BigDecimal interest, String status) {
RepaymentPlan p = new RepaymentPlan();
p.setFinancingId(f.getId());
p.setFinancingCode(f.getCode());
p.setPeriodNo(no);
p.setDueDate(dueDate);
p.setPrincipal(Money.nz(principal));
p.setInterest(Money.nz(interest));
p.setStatus(status);
p.setCreatedAt(Instant.now());
return p;
}
private void seedPolicies() {
if (policyRepo.count() > 0) return;
FinancingPolicy p1 = new FinancingPolicy();
p1.setRuleName("单笔流动资金贷款额度上限");
p1.setRuleType("额度上限");
p1.setLimitValue(Money.of(100000000.0));
p1.setScope("全集团");
p1.setHardBlock(true);
p1.setDescription("集团融资政策:单笔流动资金贷款不超过 1 亿元,超限须报董事会审批。");
p1.setStatus("启用");
p1.setCreatedBy("admin");
p1.setCreatedAt(Instant.now());
policyRepo.save(p1);
FinancingPolicy p2 = new FinancingPolicy();
p2.setRuleName("融资综合利率上限");
p2.setRuleType("利率上限");
p2.setLimitValue(Money.of(6.0));
p2.setScope("全集团");
p2.setHardBlock(false);
p2.setDescription("集团融资政策:融资年化利率超过 6% 视为高成本融资,需在申请中说明替代方案比选结果,硬拒以警告为主不阻断。");
p2.setStatus("启用");
p2.setCreatedBy("admin");
p2.setCreatedAt(Instant.now());
policyRepo.save(p2);
FinancingPolicy p3 = new FinancingPolicy();
p3.setRuleName("流动资金贷款期限上限");
p3.setRuleType("期限上限");
p3.setLimitValue(Money.of(12.0));
p3.setScope("流动资金");
p3.setHardBlock(true);
p3.setDescription("流动资金贷款期限原则上不超过 12 个月(1年),超限需升级审批至财务总监。");
p3.setStatus("启用");
p3.setCreatedBy("admin");
p3.setCreatedAt(Instant.now());
policyRepo.save(p3);
FinancingPolicy p4 = new FinancingPolicy();
p4.setRuleName("允许融资品种限制");
p4.setRuleType("品种限制");
p4.setAllowedValues("银行贷款,融资租赁,银行承兑汇票,信用证");
p4.setScope("全集团");
p4.setHardBlock(true);
p4.setDescription("集团融资政策:仅允许银行贷款、融资租赁、银行承兑汇票、信用证四种融资品种,其他品种须专项论证并报批。");
p4.setStatus("启用");
p4.setCreatedBy("admin");
p4.setCreatedAt(Instant.now());
policyRepo.save(p4);
FinancingPolicy p5 = new FinancingPolicy();
p5.setRuleName("固定资产贷款期限上限");
p5.setRuleType("期限上限");
p5.setLimitValue(Money.of(120.0));
p5.setScope("固定资产投资");
p5.setHardBlock(true);
p5.setDescription("固定资产贷款期限不超过 10 年(120 个月),与项目建设及投资回收期匹配。");
p5.setStatus("启用");
p5.setCreatedBy("admin");
p5.setCreatedAt(Instant.now());
policyRepo.save(p5);
}
private void seedEvaluations() {
if (evalRepo.count() > 0) return;
// 需要 bankRelRepo 已有数据
java.util.List<PmtBankRelation> rels = bankRelRepo.findAll();
if (rels.isEmpty()) return;
Instant now = Instant.now();
for (PmtBankRelation rel : rels) {
// 每个机构建 2 条历史评价(2024Q4 和 2025Q2
PmtBankEvaluation ev1 = new PmtBankEvaluation();
ev1.setBankRelId(rel.getId());
ev1.setInstitutionName(rel.getInstitutionName());
ev1.setEvalPeriod("2024Q4");
ev1.setEvalDate("2024-12-31");
int s1a = baseScore(rel.getRating()) - 5;
int s1b = baseScore(rel.getRating());
int s1c = baseScore(rel.getRating()) + 3;
int s1d = baseScore(rel.getRating()) - 2;
ev1.setScoreApprovalSpeed(clamp(s1a));
ev1.setScoreServiceQuality(clamp(s1b));
ev1.setScoreRateCompetitiveness(clamp(s1c));
ev1.setScoreCooperation(clamp(s1d));
int total1 = (clamp(s1a) + clamp(s1b) + clamp(s1c) + clamp(s1d)) / 4;
ev1.setTotalScore(total1);
ev1.setGrade(grade(total1));
ev1.setRemark("2024年四季度综合评价,审批效率和服务质量符合预期。");
ev1.setEvaluator("admin");
ev1.setStatus("已完成");
ev1.setCreatedAt(now);
evalRepo.save(ev1);
PmtBankEvaluation ev2 = new PmtBankEvaluation();
ev2.setBankRelId(rel.getId());
ev2.setInstitutionName(rel.getInstitutionName());
ev2.setEvalPeriod("2025Q2");
ev2.setEvalDate("2025-06-30");
int s2a = baseScore(rel.getRating()) + 2;
int s2b = baseScore(rel.getRating()) + 1;
int s2c = baseScore(rel.getRating());
int s2d = baseScore(rel.getRating()) + 4;
ev2.setScoreApprovalSpeed(clamp(s2a));
ev2.setScoreServiceQuality(clamp(s2b));
ev2.setScoreRateCompetitiveness(clamp(s2c));
ev2.setScoreCooperation(clamp(s2d));
int total2 = (clamp(s2a) + clamp(s2b) + clamp(s2c) + clamp(s2d)) / 4;
ev2.setTotalScore(total2);
ev2.setGrade(grade(total2));
ev2.setRemark("2025年二季度综合评价,各维度较上期有所提升。");
ev2.setEvaluator("admin");
ev2.setStatus("已完成");
ev2.setCreatedAt(now);
evalRepo.save(ev2);
}
}
private static int baseScore(String rating) {
if ("A".equals(rating)) return 85;
if ("B".equals(rating)) return 70;
if ("C".equals(rating)) return 55;
return 40;
}
private static int clamp(int v) {
return Math.max(0, Math.min(100, v));
}
private static String grade(int total) {
if (total >= 80) return "A";
if (total >= 60) return "B";
if (total >= 40) return "C";
return "D";
}
}