diff --git a/frontend/src/views/loans/LoansView.vue b/frontend/src/views/loans/LoansView.vue index 8bf68f0..8310a3b 100644 --- a/frontend/src/views/loans/LoansView.vue +++ b/frontend/src/views/loans/LoansView.vue @@ -104,7 +104,7 @@ - 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. @@ -268,12 +268,18 @@ const snackbar = ref(false) const snackbarText = ref('') const snackbarColor = ref('success') -const loanAccountTypes = ['AutoLoan', 'PersonalLoan', 'Mortgage'] +const loanAccountTypes = ['AutoLoan', 'PersonalLoan', 'Mortgage', 'LineOfCredit'] +const dailyAccrualTypes = ['AutoLoan', 'LineOfCredit'] const loanAccounts = computed(() => 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 = [ { title: '#', key: 'paymentNumber', width: '60px' }, { title: 'Date', key: 'paymentDate', width: '110px' }, diff --git a/src/Purrse.Api/Services/LoanService.cs b/src/Purrse.Api/Services/LoanService.cs index 071326e..d1aca58 100644 --- a/src/Purrse.Api/Services/LoanService.cs +++ b/src/Purrse.Api/Services/LoanService.cs @@ -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,11 +118,14 @@ public class LoanService : ILoanService // Build new schedule with extra payments var balance = Math.Abs(account.Balance); + var useDailyAccrual = UseDailyAccrual(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; var startDate = new DateTime(now.Year, now.Month, l.OriginationDate.Day, 0, 0, 0, DateTimeKind.Utc); + var previousDate = startDate; var newEntries = new List(); int paymentNum = 0; @@ -139,12 +142,15 @@ public class LoanService : ILoanService 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); if (principal < 0) principal = 0; balance -= principal; if (balance < 0.01m) balance = 0; + previousDate = paymentDate; newEntries.Add(new AmortizationEntryResponse( paymentNum, paymentDate, principal + interest + (l.EscrowAmount ?? 0), @@ -181,21 +187,30 @@ public class LoanService : ILoanService return new LoanDefaultsResponse(0, interestRate, termMonths, hasSyncData); } - private static List GenerateAmortizationSchedule(LoanDetail loan) + private static bool UseDailyAccrual(AccountType type) => + type is AccountType.AutoLoan or AccountType.LineOfCredit; + + private static List GenerateAmortizationSchedule(LoanDetail loan, AccountType accountType) { var entries = new List(); var balance = loan.OriginalBalance; + var useDailyAccrual = UseDailyAccrual(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; + var previousDate = loan.OriginationDate.AddMonths(-1); for (int i = 1; i <= loan.TermMonths && balance > 0; i++) { 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); balance -= principal; if (balance < 0.01m) balance = 0; + previousDate = paymentDate; entries.Add(new AmortizationEntry {