Use daily interest accrual for lines of credit and auto loans, fix hasLoanDetail on account list
Lines of credit and auto loans use daily accrual (rate/365 × days) matching actual bank calculations. Mortgages and personal loans keep monthly compounding. Include LoanDetail in GetAllAsync so the loan cards correctly show status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -104,7 +104,7 @@
|
||||
</v-card>
|
||||
|
||||
<v-alert v-if="loanDetail" type="info" variant="tonal" density="compact" class="mb-4">
|
||||
Amounts shown are estimates based on standard monthly compounding. Actual values may differ due to your lender's rounding practices, extra payments, or rate changes.
|
||||
Amounts shown are estimates. Lines of credit and auto loans use daily interest accrual; mortgages and personal loans use standard monthly compounding. Actual values may differ due to payment timing, your lender's rounding practices, extra payments, or rate changes.
|
||||
</v-alert>
|
||||
|
||||
<!-- Amortization Schedule -->
|
||||
|
||||
@@ -18,6 +18,7 @@ public class AccountService : IAccountService
|
||||
public async Task<List<AccountResponse>> GetAllAsync(Guid userId)
|
||||
{
|
||||
return await _db.Accounts
|
||||
.Include(a => a.LoanDetail)
|
||||
.Where(a => a.UserId == userId)
|
||||
.OrderBy(a => a.SortOrder).ThenBy(a => a.Name)
|
||||
.Select(a => MapToResponse(a))
|
||||
|
||||
@@ -49,7 +49,7 @@ public class LoanService : ILoanService
|
||||
ExtraPayment = request.ExtraPayment
|
||||
};
|
||||
|
||||
var entries = GenerateAmortizationSchedule(loanDetail);
|
||||
var entries = GenerateAmortizationSchedule(loanDetail, account.Type);
|
||||
loanDetail.AmortizationEntries = entries;
|
||||
|
||||
_db.LoanDetails.Add(loanDetail);
|
||||
@@ -82,7 +82,7 @@ public class LoanService : ILoanService
|
||||
l.EscrowAmount = request.EscrowAmount;
|
||||
l.ExtraPayment = request.ExtraPayment;
|
||||
|
||||
var entries = GenerateAmortizationSchedule(l);
|
||||
var entries = GenerateAmortizationSchedule(l, account.Type);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.LoanDetailId = l.Id;
|
||||
@@ -118,7 +118,9 @@ public class LoanService : ILoanService
|
||||
|
||||
// Build new schedule with extra payments
|
||||
var balance = Math.Abs(account.Balance);
|
||||
var useDailyAccrual = UsesDailyAccrual(account.Type);
|
||||
var monthlyRate = l.InterestRate / 100m / 12m;
|
||||
var dailyRate = l.InterestRate / 100m / 365m;
|
||||
var extraMonthly = request.ExtraMonthlyPayment ?? 0;
|
||||
var basePayment = l.MonthlyPayment - (l.EscrowAmount ?? 0);
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -139,7 +141,18 @@ public class LoanService : ILoanService
|
||||
paymentNum++;
|
||||
var paymentDate = startDate.AddMonths(paymentNum);
|
||||
|
||||
var interest = Math.Round(balance * monthlyRate, 2);
|
||||
decimal interest;
|
||||
if (useDailyAccrual)
|
||||
{
|
||||
var prevDate = startDate.AddMonths(paymentNum - 1);
|
||||
var days = (paymentDate - prevDate).Days;
|
||||
interest = Math.Round(balance * dailyRate * days, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
interest = Math.Round(balance * monthlyRate, 2);
|
||||
}
|
||||
|
||||
var principal = Math.Min(basePayment + extraMonthly - interest, balance);
|
||||
if (principal < 0) principal = 0;
|
||||
|
||||
@@ -181,18 +194,35 @@ public class LoanService : ILoanService
|
||||
return new LoanDefaultsResponse(0, interestRate, termMonths, hasSyncData);
|
||||
}
|
||||
|
||||
private static List<AmortizationEntry> GenerateAmortizationSchedule(LoanDetail loan)
|
||||
private static bool UsesDailyAccrual(AccountType type) =>
|
||||
type is AccountType.LineOfCredit or AccountType.AutoLoan;
|
||||
|
||||
private static List<AmortizationEntry> GenerateAmortizationSchedule(LoanDetail loan, AccountType accountType)
|
||||
{
|
||||
var entries = new List<AmortizationEntry>();
|
||||
var balance = loan.OriginalBalance;
|
||||
var useDailyAccrual = UsesDailyAccrual(accountType);
|
||||
var monthlyRate = loan.InterestRate / 100m / 12m;
|
||||
var dailyRate = loan.InterestRate / 100m / 365m;
|
||||
var payment = loan.MonthlyPayment - (loan.EscrowAmount ?? 0);
|
||||
var extra = loan.ExtraPayment ?? 0;
|
||||
|
||||
for (int i = 1; i <= loan.TermMonths && balance > 0; i++)
|
||||
{
|
||||
var paymentDate = loan.OriginationDate.AddMonths(i - 1);
|
||||
var interest = Math.Round(balance * monthlyRate, 2);
|
||||
|
||||
decimal interest;
|
||||
if (useDailyAccrual)
|
||||
{
|
||||
var prevDate = loan.OriginationDate.AddMonths(i - 2);
|
||||
var days = (paymentDate - prevDate).Days;
|
||||
interest = Math.Round(balance * dailyRate * days, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
interest = Math.Round(balance * monthlyRate, 2);
|
||||
}
|
||||
|
||||
var principal = Math.Min(payment + extra - interest, balance);
|
||||
balance -= principal;
|
||||
if (balance < 0.01m) balance = 0;
|
||||
|
||||
Reference in New Issue
Block a user