Private
Public Access
1
0

Use daily interest accrual for HELOC and auto loan amortization

Mortgages and personal loans use monthly compounding (rate/12), but
HELOCs and auto loans accrue interest daily (rate/365 * actual days).
Select the calculation method based on account type and update the
disclaimer to reflect which method is being used.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-14 18:41:32 -05:00
parent 98104c0c0e
commit dd2cdbf815
2 changed files with 28 additions and 7 deletions
+8 -2
View File
@@ -104,7 +104,7 @@
</v-card> </v-card>
<v-alert v-if="loanDetail" type="info" variant="tonal" density="compact" class="mb-4"> <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 slightly due to your lender's rounding practices. Amounts shown are estimates using {{ isDailyAccrualAccount ? 'daily interest accrual' : 'standard monthly compounding' }}. Actual values may differ slightly due to your lender's rounding practices.
</v-alert> </v-alert>
<!-- Amortization Schedule --> <!-- Amortization Schedule -->
@@ -268,12 +268,18 @@ const snackbar = ref(false)
const snackbarText = ref('') const snackbarText = ref('')
const snackbarColor = ref('success') const snackbarColor = ref('success')
const loanAccountTypes = ['AutoLoan', 'PersonalLoan', 'Mortgage'] const loanAccountTypes = ['AutoLoan', 'PersonalLoan', 'Mortgage', 'LineOfCredit']
const dailyAccrualTypes = ['AutoLoan', 'LineOfCredit']
const loanAccounts = computed(() => const loanAccounts = computed(() =>
accountsStore.accounts.filter(a => a.hasLoanDetail || loanAccountTypes.includes(a.type)) accountsStore.accounts.filter(a => a.hasLoanDetail || loanAccountTypes.includes(a.type))
) )
const isDailyAccrualAccount = computed(() => {
const account = accountsStore.accounts.find(a => a.id === props.id)
return account ? dailyAccrualTypes.includes(account.type) : false
})
const amortHeaders = [ const amortHeaders = [
{ title: '#', key: 'paymentNumber', width: '60px' }, { title: '#', key: 'paymentNumber', width: '60px' },
{ title: 'Date', key: 'paymentDate', width: '110px' }, { title: 'Date', key: 'paymentDate', width: '110px' },
+20 -5
View File
@@ -49,7 +49,7 @@ public class LoanService : ILoanService
ExtraPayment = request.ExtraPayment ExtraPayment = request.ExtraPayment
}; };
var entries = GenerateAmortizationSchedule(loanDetail); var entries = GenerateAmortizationSchedule(loanDetail, account.Type);
loanDetail.AmortizationEntries = entries; loanDetail.AmortizationEntries = entries;
_db.LoanDetails.Add(loanDetail); _db.LoanDetails.Add(loanDetail);
@@ -82,7 +82,7 @@ public class LoanService : ILoanService
l.EscrowAmount = request.EscrowAmount; l.EscrowAmount = request.EscrowAmount;
l.ExtraPayment = request.ExtraPayment; l.ExtraPayment = request.ExtraPayment;
var entries = GenerateAmortizationSchedule(l); var entries = GenerateAmortizationSchedule(l, account.Type);
foreach (var entry in entries) foreach (var entry in entries)
{ {
entry.LoanDetailId = l.Id; entry.LoanDetailId = l.Id;
@@ -118,11 +118,14 @@ public class LoanService : ILoanService
// Build new schedule with extra payments // Build new schedule with extra payments
var balance = Math.Abs(account.Balance); var balance = Math.Abs(account.Balance);
var useDailyAccrual = UseDailyAccrual(account.Type);
var monthlyRate = l.InterestRate / 100m / 12m; var monthlyRate = l.InterestRate / 100m / 12m;
var dailyRate = l.InterestRate / 100m / 365m;
var extraMonthly = request.ExtraMonthlyPayment ?? 0; var extraMonthly = request.ExtraMonthlyPayment ?? 0;
var basePayment = l.MonthlyPayment - (l.EscrowAmount ?? 0); var basePayment = l.MonthlyPayment - (l.EscrowAmount ?? 0);
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var startDate = new DateTime(now.Year, now.Month, l.OriginationDate.Day, 0, 0, 0, DateTimeKind.Utc); var startDate = new DateTime(now.Year, now.Month, l.OriginationDate.Day, 0, 0, 0, DateTimeKind.Utc);
var previousDate = startDate;
var newEntries = new List<AmortizationEntryResponse>(); var newEntries = new List<AmortizationEntryResponse>();
int paymentNum = 0; int paymentNum = 0;
@@ -139,12 +142,15 @@ public class LoanService : ILoanService
paymentNum++; paymentNum++;
var paymentDate = startDate.AddMonths(paymentNum); var paymentDate = startDate.AddMonths(paymentNum);
var interest = Math.Round(balance * monthlyRate, 2); var interest = useDailyAccrual
? Math.Round(balance * dailyRate * (paymentDate - previousDate).Days, 2)
: Math.Round(balance * monthlyRate, 2);
var principal = Math.Min(basePayment + extraMonthly - interest, balance); var principal = Math.Min(basePayment + extraMonthly - interest, balance);
if (principal < 0) principal = 0; if (principal < 0) principal = 0;
balance -= principal; balance -= principal;
if (balance < 0.01m) balance = 0; if (balance < 0.01m) balance = 0;
previousDate = paymentDate;
newEntries.Add(new AmortizationEntryResponse( newEntries.Add(new AmortizationEntryResponse(
paymentNum, paymentDate, principal + interest + (l.EscrowAmount ?? 0), paymentNum, paymentDate, principal + interest + (l.EscrowAmount ?? 0),
@@ -181,21 +187,30 @@ public class LoanService : ILoanService
return new LoanDefaultsResponse(0, interestRate, termMonths, hasSyncData); return new LoanDefaultsResponse(0, interestRate, termMonths, hasSyncData);
} }
private static List<AmortizationEntry> GenerateAmortizationSchedule(LoanDetail loan) private static bool UseDailyAccrual(AccountType type) =>
type is AccountType.AutoLoan or AccountType.LineOfCredit;
private static List<AmortizationEntry> GenerateAmortizationSchedule(LoanDetail loan, AccountType accountType)
{ {
var entries = new List<AmortizationEntry>(); var entries = new List<AmortizationEntry>();
var balance = loan.OriginalBalance; var balance = loan.OriginalBalance;
var useDailyAccrual = UseDailyAccrual(accountType);
var monthlyRate = loan.InterestRate / 100m / 12m; var monthlyRate = loan.InterestRate / 100m / 12m;
var dailyRate = loan.InterestRate / 100m / 365m;
var payment = loan.MonthlyPayment - (loan.EscrowAmount ?? 0); var payment = loan.MonthlyPayment - (loan.EscrowAmount ?? 0);
var extra = loan.ExtraPayment ?? 0; var extra = loan.ExtraPayment ?? 0;
var previousDate = loan.OriginationDate.AddMonths(-1);
for (int i = 1; i <= loan.TermMonths && balance > 0; i++) for (int i = 1; i <= loan.TermMonths && balance > 0; i++)
{ {
var paymentDate = loan.OriginationDate.AddMonths(i - 1); var paymentDate = loan.OriginationDate.AddMonths(i - 1);
var interest = Math.Round(balance * monthlyRate, 2); var interest = useDailyAccrual
? Math.Round(balance * dailyRate * (paymentDate - previousDate).Days, 2)
: Math.Round(balance * monthlyRate, 2);
var principal = Math.Min(payment + extra - interest, balance); var principal = Math.Min(payment + extra - interest, balance);
balance -= principal; balance -= principal;
if (balance < 0.01m) balance = 0; if (balance < 0.01m) balance = 0;
previousDate = paymentDate;
entries.Add(new AmortizationEntry entries.Add(new AmortizationEntry
{ {