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 java.math.BigDecimal; import java.time.Instant; /** * 融资域演示数据(金融办)。 * 补建 Gap-1/2/5 的活体演示数据:金融机构档案、融资台账、机构定期评价、融资政策规则、还款计划。 * 只在对应表为空时执行,防止重复。@Order(20) 在 DataSeeder(@Order(1)) 之后运行。 */ @Order(20) @DemoSeed 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(BigDecimal.valueOf(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(BigDecimal.valueOf(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(BigDecimal.valueOf(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(BigDecimal.valueOf(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(BigDecimal.valueOf(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(BigDecimal.valueOf(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 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"; } }