|
@@ -15,185 +15,185 @@ namespace Masuit.Tools.Models;
|
|
|
/// <param name="Start">开始还款日</param>
|
|
|
public record LoanModel(decimal Loan, decimal Rate, int Period, DateTime Start, LoanType LoanType = LoanType.EquivalentInterest)
|
|
|
{
|
|
|
- public Dictionary<DateTime, decimal?> RateAdjustments { get; set; } = new();
|
|
|
-
|
|
|
- public List<PrepaymentOption> 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);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 生成还款计划
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- 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<PaymentPlan>()
|
|
|
- {
|
|
|
- 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<PaymentPlan>()
|
|
|
- {
|
|
|
- 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<DateTime, decimal?> RateAdjustments { get; set; } = new();
|
|
|
+
|
|
|
+ public List<PrepaymentOption> 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成还款计划
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ 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<PaymentPlan>()
|
|
|
+ {
|
|
|
+ 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<PaymentPlan>()
|
|
|
+ {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -201,17 +201,17 @@ public record LoanModel(decimal Loan, decimal Rate, int Period, DateTime Start,
|
|
|
/// </summary>
|
|
|
public enum LoanType
|
|
|
{
|
|
|
- /// <summary>
|
|
|
- /// 等额本息
|
|
|
- /// </summary>
|
|
|
- [Description("等额本息")]
|
|
|
- EquivalentInterest,
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 等额本金
|
|
|
- /// </summary>
|
|
|
- [Description("等额本金")]
|
|
|
- EquivalentPrincipal,
|
|
|
+ /// <summary>
|
|
|
+ /// 等额本息
|
|
|
+ /// </summary>
|
|
|
+ [Description("等额本息")]
|
|
|
+ EquivalentInterest,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 等额本金
|
|
|
+ /// </summary>
|
|
|
+ [Description("等额本金")]
|
|
|
+ EquivalentPrincipal,
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -230,125 +230,125 @@ public record PrepaymentOption(DateTime Date, decimal Amount, bool ReducePeriod
|
|
|
/// <param name="Plans">还款计划</param>
|
|
|
public record LoanResult(decimal TotalInterest, List<PaymentPlan> Plans)
|
|
|
{
|
|
|
- /// <summary>
|
|
|
- /// 总提前还款额
|
|
|
- /// </summary>
|
|
|
- public decimal TotalRepayment => Plans.Sum(e => e.Repayment);
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 实际总利息
|
|
|
- /// </summary>
|
|
|
- public decimal ActualInterest => Plans.Sum(e => e.Interest);
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 实际还款总额
|
|
|
- /// </summary>
|
|
|
- public decimal ActualPayment => Plans.Sum(e => e.Payment + e.Repayment);
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 节省利息
|
|
|
- /// </summary>
|
|
|
- public decimal SavedInterest => TotalInterest - ActualInterest;
|
|
|
-
|
|
|
- public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal totalRepayment, out List<PaymentPlan> 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<PaymentPlan> 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<PaymentPlan> paymentPlans)
|
|
|
- {
|
|
|
- totalInterest = TotalInterest;
|
|
|
- actualInterest = ActualInterest;
|
|
|
- totalRepayment = TotalRepayment;
|
|
|
- paymentPlans = Plans;
|
|
|
- savedInterest = SavedInterest;
|
|
|
- actualPayment = ActualPayment;
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// 总提前还款额
|
|
|
+ /// </summary>
|
|
|
+ public decimal TotalRepayment => Plans.Sum(e => e.Repayment);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 实际总利息
|
|
|
+ /// </summary>
|
|
|
+ public decimal ActualInterest => Plans.Sum(e => e.Interest);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 实际还款总额
|
|
|
+ /// </summary>
|
|
|
+ public decimal ActualPayment => Plans.Sum(e => e.Payment + e.Repayment);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 节省利息
|
|
|
+ /// </summary>
|
|
|
+ public decimal SavedInterest => TotalInterest - ActualInterest;
|
|
|
+
|
|
|
+ public void Deconstruct(out decimal totalInterest, out decimal actualInterest, out decimal totalRepayment, out List<PaymentPlan> 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<PaymentPlan> 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<PaymentPlan> paymentPlans)
|
|
|
+ {
|
|
|
+ totalInterest = TotalInterest;
|
|
|
+ actualInterest = ActualInterest;
|
|
|
+ totalRepayment = TotalRepayment;
|
|
|
+ paymentPlans = Plans;
|
|
|
+ savedInterest = SavedInterest;
|
|
|
+ actualPayment = ActualPayment;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public record PaymentPlan
|
|
|
{
|
|
|
- /// <summary>
|
|
|
- /// 期数
|
|
|
- /// </summary>
|
|
|
- public int Period { get; internal set; } = 12;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 还款日
|
|
|
- /// </summary>
|
|
|
- public DateTime Date { get; internal set; } = DateTime.Now;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 月供
|
|
|
- /// </summary>
|
|
|
- public decimal Payment { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 年利率
|
|
|
- /// </summary>
|
|
|
- public decimal Rate { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 月还利息
|
|
|
- /// </summary>
|
|
|
- public decimal Interest { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 月还本金
|
|
|
- /// </summary>
|
|
|
- public decimal Amount { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 当期提前还款额
|
|
|
- /// </summary>
|
|
|
- public decimal Repayment { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 当期剩余本金
|
|
|
- /// </summary>
|
|
|
- public decimal Balance { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 当期剩余利息
|
|
|
- /// </summary>
|
|
|
- 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
|
|
|
- };
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 当期剩余利息(提前还款/利率调整前)
|
|
|
- /// </summary>
|
|
|
- public decimal OriginRemainInterest { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 贷款类型(默认等额本息)
|
|
|
- /// </summary>
|
|
|
- public LoanType LoanType { get; internal set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 期数减少
|
|
|
- /// </summary>
|
|
|
- internal int PeriodReduce { get; set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 剩余期数
|
|
|
- /// </summary>
|
|
|
- internal int RemainPeriod { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 期数
|
|
|
+ /// </summary>
|
|
|
+ public int Period { get; internal set; } = 12;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 还款日
|
|
|
+ /// </summary>
|
|
|
+ public DateTime Date { get; internal set; } = DateTime.Now;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 月供
|
|
|
+ /// </summary>
|
|
|
+ public decimal Payment { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 年利率
|
|
|
+ /// </summary>
|
|
|
+ public decimal Rate { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 月还利息
|
|
|
+ /// </summary>
|
|
|
+ public decimal Interest { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 月还本金
|
|
|
+ /// </summary>
|
|
|
+ public decimal Amount { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当期提前还款额
|
|
|
+ /// </summary>
|
|
|
+ public decimal Repayment { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当期剩余本金
|
|
|
+ /// </summary>
|
|
|
+ public decimal Balance { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当期剩余利息
|
|
|
+ /// </summary>
|
|
|
+ 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
|
|
|
+ };
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当期剩余利息(提前还款/利率调整前)
|
|
|
+ /// </summary>
|
|
|
+ public decimal OriginRemainInterest { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 贷款类型(默认等额本息)
|
|
|
+ /// </summary>
|
|
|
+ public LoanType LoanType { get; internal set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 期数减少
|
|
|
+ /// </summary>
|
|
|
+ internal int PeriodReduce { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 剩余期数
|
|
|
+ /// </summary>
|
|
|
+ internal int RemainPeriod { get; set; }
|
|
|
}
|
|
|
|
|
|
-#endif
|
|
|
+#endif
|