diff --git a/Masuit.Tools.Abstractions/Models/LoanModel.cs b/Masuit.Tools.Abstractions/Models/LoanModel.cs
index 123629c3..3b113780 100644
--- a/Masuit.Tools.Abstractions/Models/LoanModel.cs
+++ b/Masuit.Tools.Abstractions/Models/LoanModel.cs
@@ -15,185 +15,185 @@ namespace Masuit.Tools.Models;
/// 开始还款日
public record LoanModel(decimal Loan, decimal Rate, int Period, DateTime Start, LoanType LoanType = LoanType.EquivalentInterest)
{
- public Dictionary RateAdjustments { get; set; } = new();
-
- public List Prepayments { get; set; } = new();
-
- private static decimal CumIPMT(decimal rate, decimal loan, int period)
- {
- double interest = 0;
- for (int i = 1; i <= period; i++)
- {
- interest += Financial.IPmt((double)(rate / 12), i, period, (double)loan);
- }
-
- return interest.ToDecimal(2);
- }
-
- ///
- /// 生成还款计划
- ///
- ///
- public LoanResult Payment()
- {
- var result = LoanType == LoanType.EquivalentPrincipal ? PrepaymentPrincipal() : PrepaymentInterest();
- result.Plans[0].OriginRemainInterest = result.Plans[0].RemainInterest;
- for (var i = 1; i < result.Plans.Count; i++)
- {
- result.Plans[i].Period = i + 1;
- result.Plans[i].OriginRemainInterest = result.Plans[i - 1].RemainInterest - result.Plans[i].Interest;
- if (result.Plans[i].LoanType != result.Plans[i - 1].LoanType)
- {
- result.Plans[i].Repayment = result.Plans[i - 1].Balance - result.Plans[i].Balance - result.Plans[i].Amount;
- }
- }
-
- return result;
- }
-
- private LoanResult PrepaymentInterest()
- {
- var list = new List()
- {
- new()
- {
- Date = Start,
- LoanType = LoanType.EquivalentInterest
- }
- };
- var pmt = -Financial.Pmt((double)(Rate / 12), Period, (double)Loan);
- list[0].Rate = Rate;
- list[0].Period = 1;
- list[0].RemainPeriod = Period;
- list[0].Payment = pmt.ToDecimal(2);
- list[0].Interest = Math.Round(Loan * Rate / 12, 2, MidpointRounding.AwayFromZero);
- list[0].Amount = list[0].Payment - list[0].Interest;
- list[0].Balance = Loan - list[0].Amount;
- for (var i = 1; i < Period; i++)
- {
- var current = Start.AddMonths(i);
- var adj = RateAdjustments.FirstOrDefault(x => x.Key <= current && x.Key > current.AddMonths(-1));
- var newRate = adj.Value ?? list[i - 1].Rate;
- var prepayment = Prepayments.Find(x => x.Date <= current && x.Date > current.AddMonths(-1));
- if (prepayment?.ChangeType is LoanType.EquivalentPrincipal)
- {
- list.AddRange(new LoanModel(list[i - 1].Balance - prepayment.Amount, newRate, list[i - 1].RemainPeriod - 1, current, LoanType.EquivalentPrincipal)
- {
- Prepayments = Prepayments,
- RateAdjustments = RateAdjustments
- }.PrepaymentPrincipal().Plans);
- break;
- }
-
- list.Add(new PaymentPlan()
- {
- Period = i,
- Date = current,
- LoanType = LoanType.EquivalentInterest
- });
- list[i].Rate = newRate;
- list[i].Repayment = prepayment?.Amount ?? 0;
- if (Prepayments.FirstOrDefault(x => x.Date <= current.AddMonths(-1) && x.Date > current.AddMonths(-2))?.ReducePeriod == true)
- {
- var leftPeriod = (int)Math.Round(-Math.Log((double)(1 - (list[i - 1].Balance * list[i].Rate / 12) / list[i - 1].Payment)) / Math.Log((double)(1 + list[i].Rate / 12)));
- list[i].PeriodReduce = Period - list.Count + 1 - leftPeriod;
- list[i].RemainPeriod = leftPeriod;
- }
- else
- {
- list[i].RemainPeriod = list[i - 1].RemainPeriod - 1;
- }
- list[i].Payment = -Financial.Pmt((double)(list[i].Rate / 12), list[i].RemainPeriod, (double)list[i - 1].Balance).ToDecimal(2);
- if ((current - adj.Key).TotalDays > 0 && (current - adj.Key).TotalDays < 30)
- {
- var days = (decimal)(list[i].Date - list[i - 1].Date).TotalDays;
- list[i].Payment = list[i - 1].Payment / days * (decimal)Math.Abs((adj.Key - list[i - 1].Date).TotalDays) + list[i].Payment / days * (decimal)Math.Abs((current - adj.Key).TotalDays);
- }
- list[i].Interest = Math.Round(list[i - 1].Balance * list[i].Rate / 12, 2);
- list[i].Amount = Math.Round(list[i].Payment - list[i].Interest, 2);
- list[i].Balance = Math.Round(list[i - 1].Balance - list[i].Amount - list[i].Repayment, 2);
- if (list[i].Balance <= 0)
- {
- list[i].Payment += list[i].Balance;
- break;
- }
- }
-
- var totalInterest = -CumIPMT(Rate, Loan, Period);
- return new LoanResult(totalInterest, list);
- }
-
- private LoanResult PrepaymentPrincipal()
- {
- var list = new List()
- {
- new()
- {
- Date = Start,
- LoanType = LoanType.EquivalentPrincipal,
- RemainPeriod = Period
- }
- };
- list[0].Rate = Rate;
- list[0].Period = 1;
- list[0].Interest = Math.Round(Loan * Rate / 12, 2, MidpointRounding.AwayFromZero);
- list[0].Amount = Math.Round(Loan / Period, 2, MidpointRounding.AwayFromZero);
- list[0].Payment = Math.Round(list[0].Amount + list[0].Interest, 2, MidpointRounding.AwayFromZero);
- list[0].Balance = Math.Round(Loan - list[0].Amount, 2, MidpointRounding.AwayFromZero);
- for (var i = 1; i < Period; i++)
- {
- var current = Start.AddMonths(i);
- var adj = RateAdjustments.FirstOrDefault(x => x.Key <= current && x.Key > current.AddMonths(-1));
- var newRate = adj.Value ?? list[i - 1].Rate;
- var prepayment = Prepayments.Find(x => x.Date <= current && x.Date > current.AddMonths(-1));
- if (prepayment?.ChangeType is LoanType.EquivalentInterest)
- {
- list.AddRange(new LoanModel(list[i - 1].Balance - prepayment.Amount, newRate, list[i - 1].RemainPeriod, current)
- {
- Prepayments = Prepayments,
- RateAdjustments = RateAdjustments
- }.PrepaymentInterest().Plans);
- break;
- }
-
- list.Add(new PaymentPlan()
- {
- Period = i,
- Date = current,
- LoanType = LoanType.EquivalentPrincipal
- });
- list[i].Rate = newRate;
- list[i].Repayment = prepayment?.Amount ?? 0;
- list[i].Interest = Math.Round(list[i - 1].Balance * list[i].Rate / 12, 2, MidpointRounding.AwayFromZero);
- if ((current - adj.Key).TotalDays > 0 && (current - adj.Key).TotalDays < 30)
- {
- var days = (decimal)(list[i].Date - list[i - 1].Date).TotalDays;
- list[i].Interest = list[i - 1].Interest / days * (decimal)Math.Abs((adj.Key - list[i - 1].Date).TotalDays) + list[i].Interest / days * (decimal)Math.Abs((current - adj.Key).TotalDays);
- }
-
- if (prepayment?.ReducePeriod == true)
- {
- list[i].PeriodReduce = (int)Math.Round(list[i].Repayment / (Loan / Period));
- list[i].RemainPeriod = list[i - 1].RemainPeriod - list[i].PeriodReduce - 1;
- }
- else
- {
- list[i].RemainPeriod = list[i - 1].RemainPeriod - 1;
- }
-
- list[i].Amount = Math.Round(list[i - 1].Balance / (Period - i - list.Sum(p => p.PeriodReduce)), 2, MidpointRounding.AwayFromZero);
- list[i].Payment = Math.Round(list[i].Amount + list[i].Interest, 2, MidpointRounding.AwayFromZero);
- list[i].Balance = Math.Round(list[i - 1].Balance - list[i].Amount - list[i].Repayment, 2, MidpointRounding.AwayFromZero);
- if (list[i].Balance <= 0)
- {
- list[i].Payment += list[i].Balance;
- break;
- }
- }
-
- var totalInterest = Loan * Rate / 12 * (Period + 1) / 2;
- return new LoanResult(totalInterest, list);
- }
+ public Dictionary RateAdjustments { get; set; } = new();
+
+ public List Prepayments { get; set; } = new();
+
+ private static decimal CumIPMT(decimal rate, decimal loan, int period)
+ {
+ double interest = 0;
+ for (int i = 1; i <= period; i++)
+ {
+ interest += Financial.IPmt((double)(rate / 12), i, period, (double)loan);
+ }
+
+ return interest.ToDecimal(2);
+ }
+
+ ///
+ /// 生成还款计划
+ ///
+ ///
+ public LoanResult Payment()
+ {
+ var result = LoanType == LoanType.EquivalentPrincipal ? PrepaymentPrincipal() : PrepaymentInterest();
+ result.Plans[0].OriginRemainInterest = result.Plans[0].RemainInterest;
+ for (var i = 1; i < result.Plans.Count; i++)
+ {
+ result.Plans[i].Period = i + 1;
+ result.Plans[i].OriginRemainInterest = result.Plans[i - 1].RemainInterest - result.Plans[i].Interest;
+ if (result.Plans[i].LoanType != result.Plans[i - 1].LoanType)
+ {
+ result.Plans[i].Repayment = result.Plans[i - 1].Balance - result.Plans[i].Balance - result.Plans[i].Amount;
+ }
+ }
+
+ return result;
+ }
+
+ private LoanResult PrepaymentInterest()
+ {
+ var list = new List()
+ {
+ new()
+ {
+ Date = Start,
+ LoanType = LoanType.EquivalentInterest
+ }
+ };
+ var pmt = -Financial.Pmt((double)(Rate / 12), Period, (double)Loan);
+ list[0].Rate = Rate;
+ list[0].Period = 1;
+ list[0].RemainPeriod = Period;
+ list[0].Payment = pmt.ToDecimal(2);
+ list[0].Interest = Math.Round(Loan * Rate / 12, 2, MidpointRounding.AwayFromZero);
+ list[0].Amount = list[0].Payment - list[0].Interest;
+ list[0].Balance = Loan - list[0].Amount;
+ for (var i = 1; i < Period; i++)
+ {
+ var current = Start.AddMonths(i);
+ var adj = RateAdjustments.FirstOrDefault(x => x.Key.AddMonths(1) <= current && x.Key.AddMonths(1) > current.AddMonths(-1));
+ var newRate = adj.Value ?? list[i - 1].Rate;
+ var prepayment = Prepayments.Find(x => x.Date <= current && x.Date > current.AddMonths(-1));
+ if (prepayment?.ChangeType is LoanType.EquivalentPrincipal)
+ {
+ list.AddRange(new LoanModel(list[i - 1].Balance - prepayment.Amount, newRate, list[i - 1].RemainPeriod - 1, current, LoanType.EquivalentPrincipal)
+ {
+ Prepayments = Prepayments,
+ RateAdjustments = RateAdjustments
+ }.PrepaymentPrincipal().Plans);
+ break;
+ }
+
+ list.Add(new PaymentPlan()
+ {
+ Period = i,
+ Date = current,
+ LoanType = LoanType.EquivalentInterest
+ });
+ list[i].Rate = newRate;
+ list[i].Repayment = prepayment?.Amount ?? 0;
+ if (Prepayments.FirstOrDefault(x => x.Date <= current.AddMonths(-1) && x.Date > current.AddMonths(-2))?.ReducePeriod == true)
+ {
+ var leftPeriod = (int)Math.Round(-Math.Log((double)(1 - (list[i - 1].Balance * list[i].Rate / 12) / list[i - 1].Payment)) / Math.Log((double)(1 + list[i].Rate / 12)));
+ list[i].PeriodReduce = Period - list.Count + 1 - leftPeriod;
+ list[i].RemainPeriod = leftPeriod;
+ }
+ else
+ {
+ list[i].RemainPeriod = list[i - 1].RemainPeriod - 1;
+ }
+ list[i].Payment = -Financial.Pmt((double)(list[i].Rate / 12), list[i].RemainPeriod, (double)list[i - 1].Balance).ToDecimal(2);
+ if ((current - adj.Key).TotalDays > 0 && (current - adj.Key).TotalDays < 30)
+ {
+ var days = (decimal)(list[i].Date - list[i - 1].Date).TotalDays;
+ list[i].Payment = list[i - 1].Payment / days * (decimal)Math.Abs((adj.Key - list[i - 1].Date).TotalDays) + list[i].Payment / days * (decimal)Math.Abs((current - adj.Key).TotalDays);
+ }
+ list[i].Interest = Math.Round(list[i - 1].Balance * list[i].Rate / 12, 2);
+ list[i].Amount = Math.Round(list[i].Payment - list[i].Interest, 2);
+ list[i].Balance = Math.Round(list[i - 1].Balance - list[i].Amount - list[i].Repayment, 2);
+ if (list[i].Balance <= 0)
+ {
+ list[i].Payment += list[i].Balance;
+ break;
+ }
+ }
+
+ var totalInterest = -CumIPMT(Rate, Loan, Period);
+ return new LoanResult(totalInterest, list);
+ }
+
+ private LoanResult PrepaymentPrincipal()
+ {
+ var list = new List()
+ {
+ new()
+ {
+ Date = Start,
+ LoanType = LoanType.EquivalentPrincipal,
+ RemainPeriod = Period
+ }
+ };
+ list[0].Rate = Rate;
+ list[0].Period = 1;
+ list[0].Interest = Math.Round(Loan * Rate / 12, 2, MidpointRounding.AwayFromZero);
+ list[0].Amount = Math.Round(Loan / Period, 2, MidpointRounding.AwayFromZero);
+ list[0].Payment = Math.Round(list[0].Amount + list[0].Interest, 2, MidpointRounding.AwayFromZero);
+ list[0].Balance = Math.Round(Loan - list[0].Amount, 2, MidpointRounding.AwayFromZero);
+ for (var i = 1; i < Period; i++)
+ {
+ var current = Start.AddMonths(i);
+ var adj = RateAdjustments.FirstOrDefault(x => x.Key.AddMonths(1) <= current && x.Key.AddMonths(1) > current.AddMonths(-1));
+ var newRate = adj.Value ?? list[i - 1].Rate;
+ var prepayment = Prepayments.Find(x => x.Date <= current && x.Date > current.AddMonths(-1));
+ if (prepayment?.ChangeType is LoanType.EquivalentInterest)
+ {
+ list.AddRange(new LoanModel(list[i - 1].Balance - prepayment.Amount, newRate, list[i - 1].RemainPeriod, current)
+ {
+ Prepayments = Prepayments,
+ RateAdjustments = RateAdjustments
+ }.PrepaymentInterest().Plans);
+ break;
+ }
+
+ list.Add(new PaymentPlan()
+ {
+ Period = i,
+ Date = current,
+ LoanType = LoanType.EquivalentPrincipal
+ });
+ list[i].Rate = newRate;
+ list[i].Repayment = prepayment?.Amount ?? 0;
+ list[i].Interest = Math.Round(list[i - 1].Balance * list[i].Rate / 12, 2, MidpointRounding.AwayFromZero);
+ if ((current - adj.Key).TotalDays > 0 && (current - adj.Key).TotalDays < 30)
+ {
+ var days = (decimal)(list[i].Date - list[i - 1].Date).TotalDays;
+ list[i].Interest = list[i - 1].Interest / days * (decimal)Math.Abs((adj.Key - list[i - 1].Date).TotalDays) + list[i].Interest / days * (decimal)Math.Abs((current - adj.Key).TotalDays);
+ }
+
+ if (prepayment?.ReducePeriod == true)
+ {
+ list[i].PeriodReduce = (int)Math.Round(list[i].Repayment / (Loan / Period));
+ list[i].RemainPeriod = list[i - 1].RemainPeriod - list[i].PeriodReduce - 1;
+ }
+ else
+ {
+ list[i].RemainPeriod = list[i - 1].RemainPeriod - 1;
+ }
+
+ list[i].Amount = Math.Round(list[i - 1].Balance / (Period - i - list.Sum(p => p.PeriodReduce)), 2, MidpointRounding.AwayFromZero);
+ list[i].Payment = Math.Round(list[i].Amount + list[i].Interest, 2, MidpointRounding.AwayFromZero);
+ list[i].Balance = Math.Round(list[i - 1].Balance - list[i].Amount - list[i].Repayment, 2, MidpointRounding.AwayFromZero);
+ if (list[i].Balance <= 0)
+ {
+ list[i].Payment += list[i].Balance;
+ break;
+ }
+ }
+
+ var totalInterest = Loan * Rate / 12 * (Period + 1) / 2;
+ return new LoanResult(totalInterest, list);
+ }
}
///
@@ -201,17 +201,17 @@ private LoanResult PrepaymentPrincipal()
///
public enum LoanType
{
- ///
- /// 等额本息
- ///
- [Description("等额本息")]
- EquivalentInterest,
-
- ///
- /// 等额本金
- ///
- [Description("等额本金")]
- EquivalentPrincipal,
+ ///
+ /// 等额本息
+ ///
+ [Description("等额本息")]
+ EquivalentInterest,
+
+ ///
+ /// 等额本金
+ ///
+ [Description("等额本金")]
+ EquivalentPrincipal,
}
///
@@ -230,125 +230,125 @@ public record PrepaymentOption(DateTime Date, decimal Amount, bool ReducePeriod
/// 还款计划
public record LoanResult(decimal TotalInterest, List Plans)
{
- ///
- /// 总提前还款额
- ///
- public decimal TotalRepayment => Plans.Sum(e => e.Repayment);
-
- ///
- /// 实际总利息
- ///
- public decimal ActualInterest => Plans.Sum(e => e.Interest);
-
- ///
- /// 实际还款总额
- ///
- public decimal ActualPayment => Plans.Sum(e => e.Payment + e.Repayment);
-
- ///
- /// 节省利息
- ///
- public decimal SavedInterest => TotalInterest - ActualInterest;
-
- public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal totalRepayment, out List paymentPlans)
- {
- totalInterest = TotalInterest;
- actualInterest = ActualInterest;
- totalRepayment = TotalRepayment;
- paymentPlans = Plans;
- }
-
- public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal savedInterest, out decimal totalRepayment, out List paymentPlans)
- {
- totalInterest = TotalInterest;
- actualInterest = ActualInterest;
- totalRepayment = TotalRepayment;
- paymentPlans = Plans;
- savedInterest = SavedInterest;
- }
-
- public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal savedInterest, out decimal totalRepayment, out decimal actualPayment, out List paymentPlans)
- {
- totalInterest = TotalInterest;
- actualInterest = ActualInterest;
- totalRepayment = TotalRepayment;
- paymentPlans = Plans;
- savedInterest = SavedInterest;
- actualPayment = ActualPayment;
- }
+ ///
+ /// 总提前还款额
+ ///
+ public decimal TotalRepayment => Plans.Sum(e => e.Repayment);
+
+ ///
+ /// 实际总利息
+ ///
+ public decimal ActualInterest => Plans.Sum(e => e.Interest);
+
+ ///
+ /// 实际还款总额
+ ///
+ public decimal ActualPayment => Plans.Sum(e => e.Payment + e.Repayment);
+
+ ///
+ /// 节省利息
+ ///
+ public decimal SavedInterest => TotalInterest - ActualInterest;
+
+ public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal totalRepayment, out List paymentPlans)
+ {
+ totalInterest = TotalInterest;
+ actualInterest = ActualInterest;
+ totalRepayment = TotalRepayment;
+ paymentPlans = Plans;
+ }
+
+ public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal savedInterest, out decimal totalRepayment, out List paymentPlans)
+ {
+ totalInterest = TotalInterest;
+ actualInterest = ActualInterest;
+ totalRepayment = TotalRepayment;
+ paymentPlans = Plans;
+ savedInterest = SavedInterest;
+ }
+
+ public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal savedInterest, out decimal totalRepayment, out decimal actualPayment, out List paymentPlans)
+ {
+ totalInterest = TotalInterest;
+ actualInterest = ActualInterest;
+ totalRepayment = TotalRepayment;
+ paymentPlans = Plans;
+ savedInterest = SavedInterest;
+ actualPayment = ActualPayment;
+ }
}
public record PaymentPlan
{
- ///
- /// 期数
- ///
- public int Period { get; internal set; } = 12;
-
- ///
- /// 还款日
- ///
- public DateTime Date { get; internal set; } = DateTime.Now;
-
- ///
- /// 月供
- ///
- public decimal Payment { get; internal set; }
-
- ///
- /// 年利率
- ///
- public decimal Rate { get; internal set; }
-
- ///
- /// 月还利息
- ///
- public decimal Interest { get; internal set; }
-
- ///
- /// 月还本金
- ///
- public decimal Amount { get; internal set; }
-
- ///
- /// 当期提前还款额
- ///
- public decimal Repayment { get; internal set; }
-
- ///
- /// 当期剩余本金
- ///
- public decimal Balance { get; internal set; }
-
- ///
- /// 当期剩余利息
- ///
- public decimal RemainInterest => LoanType switch
- {
- LoanType.EquivalentInterest => Payment * (RemainPeriod - 1) - Balance - Repayment,
- LoanType.EquivalentPrincipal => RemainPeriod * Interest - (RemainPeriod - 1) * RemainPeriod * Amount * (Rate / 12) / 2 - Interest,
- _ => 0
- };
-
- ///
- /// 当期剩余利息(提前还款/利率调整前)
- ///
- public decimal OriginRemainInterest { get; internal set; }
-
- ///
- /// 贷款类型(默认等额本息)
- ///
- public LoanType LoanType { get; internal set; }
-
- ///
- /// 期数减少
- ///
- internal int PeriodReduce { get; set; }
-
- ///
- /// 剩余期数
- ///
- internal int RemainPeriod { get; set; }
+ ///
+ /// 期数
+ ///
+ public int Period { get; internal set; } = 12;
+
+ ///
+ /// 还款日
+ ///
+ public DateTime Date { get; internal set; } = DateTime.Now;
+
+ ///
+ /// 月供
+ ///
+ public decimal Payment { get; internal set; }
+
+ ///
+ /// 年利率
+ ///
+ public decimal Rate { get; internal set; }
+
+ ///
+ /// 月还利息
+ ///
+ public decimal Interest { get; internal set; }
+
+ ///
+ /// 月还本金
+ ///
+ public decimal Amount { get; internal set; }
+
+ ///
+ /// 当期提前还款额
+ ///
+ public decimal Repayment { get; internal set; }
+
+ ///
+ /// 当期剩余本金
+ ///
+ public decimal Balance { get; internal set; }
+
+ ///
+ /// 当期剩余利息
+ ///
+ public decimal RemainInterest => LoanType switch
+ {
+ LoanType.EquivalentInterest => Payment * (RemainPeriod - 1) - Balance - Repayment,
+ LoanType.EquivalentPrincipal => RemainPeriod * Interest - (RemainPeriod - 1) * RemainPeriod * Amount * (Rate / 12) / 2 - Interest,
+ _ => 0
+ };
+
+ ///
+ /// 当期剩余利息(提前还款/利率调整前)
+ ///
+ public decimal OriginRemainInterest { get; internal set; }
+
+ ///
+ /// 贷款类型(默认等额本息)
+ ///
+ public LoanType LoanType { get; internal set; }
+
+ ///
+ /// 期数减少
+ ///
+ internal int PeriodReduce { get; set; }
+
+ ///
+ /// 剩余期数
+ ///
+ internal int RemainPeriod { get; set; }
}
-#endif
+#endif
\ No newline at end of file