469 lines
19 KiB
Java
469 lines
19 KiB
Java
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<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";
|
||
}
|
||
}
|