/** For normal on-time repayments, pays off interest first, then principal. */ @Override protected Money handleTransactionThatIsOnTimePaymentOfInstallment( final LoanRepaymentScheduleInstallment currentInstallment, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed) { Money transactionAmountRemaining = transactionAmountUnprocessed; Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money chargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); if (loanTransaction.isInterestWaiver()) { interestPortion = currentInstallment.waiveInterestComponent(transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); } else { interestPortion = currentInstallment.payInterestComponent(transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); principalPortion = currentInstallment.payPrincipalComponent(transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); } loanTransaction.updateComponents(principalPortion, interestPortion, chargesPortion); return transactionAmountRemaining; }
private Money getTotalInterestOnLoan( final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments) { Money cumulativeInterest = Money.zero(currency); for (LoanRepaymentScheduleInstallment scheduledRepayment : repaymentScheduleInstallments) { cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterest(currency)); } return cumulativeInterest; }
@Override public Money calculate( final LocalDate actualDisbursementDate, final LocalDate paidInFullDate, final Money loanPrincipal, final BigDecimal interestRatePerAnnum, final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final List<LoanTransaction> loanRepayments) { MonetaryCurrency currency = loanPrincipal.getCurrency(); Money totalOriginalInterest = getTotalInterestOnLoan(currency, repaymentScheduleInstallments); BigDecimal dailyEquivalentPeriodicInterestRate = interestRatePerAnnum.divide(BigDecimal.valueOf(36500), 9, RoundingMode.HALF_EVEN); Money totalRecalculatedPrincipalPaid = Money.zero(loanPrincipal.getCurrency()); Money totalRecalculatedInterestPaid = Money.zero(loanPrincipal.getCurrency()); LocalDate lastBalanceDate = actualDisbursementDate; Money outstandingBalance = loanPrincipal; for (LoanTransaction loanRepayment : loanRepayments) { int daysAtBalance = Days.daysBetween(lastBalanceDate, loanRepayment.getTransactionDate()).getDays(); BigDecimal periodInterestRateForPaymentPeriod = dailyEquivalentPeriodicInterestRate.multiply(BigDecimal.valueOf(daysAtBalance)); BigDecimal interestDueOnPaymentDateAmount = outstandingBalance.getAmount().multiply(periodInterestRateForPaymentPeriod); Money interestDueOnPaymentDate = Money.of(loanPrincipal.getCurrency(), interestDueOnPaymentDateAmount); Money principalDueOnPaymentDate = loanRepayment.getAmount(currency).minus(interestDueOnPaymentDate); totalRecalculatedPrincipalPaid = totalRecalculatedPrincipalPaid.plus(principalDueOnPaymentDate); totalRecalculatedInterestPaid = totalRecalculatedInterestPaid.plus(interestDueOnPaymentDate); logger.warn( "Installment: " + lastBalanceDate + " - " + loanRepayment.getTransactionDate() + " principal: " + principalDueOnPaymentDate + "interest: " + interestDueOnPaymentDate + " total: " + loanRepayment.getAmount()); lastBalanceDate = loanRepayment.getTransactionDate(); outstandingBalance = outstandingBalance.minus(principalDueOnPaymentDate); } // finally // add up total paid to date - work out interest due on that int daysAtBalance = Days.daysBetween(lastBalanceDate, paidInFullDate).getDays(); BigDecimal periodInterestRateForPaymentPeriod = dailyEquivalentPeriodicInterestRate.multiply(BigDecimal.valueOf(daysAtBalance)); BigDecimal interestDueOnPaymentDateAmount = outstandingBalance.getAmount().multiply(periodInterestRateForPaymentPeriod); Money interestDueOnPaymentDate = Money.of(loanPrincipal.getCurrency(), interestDueOnPaymentDateAmount); Money principalDueOnPaymentDate = outstandingBalance; Money netOutstandingOnPaidInFullDate = principalDueOnPaymentDate.plus(interestDueOnPaymentDate); totalRecalculatedPrincipalPaid = totalRecalculatedPrincipalPaid.plus(principalDueOnPaymentDate); totalRecalculatedInterestPaid = totalRecalculatedInterestPaid.plus(interestDueOnPaymentDate); logger.warn( "Installment: " + lastBalanceDate + " - " + paidInFullDate + " principal: " + principalDueOnPaymentDate + "interest: " + interestDueOnPaymentDate + " total: " + netOutstandingOnPaidInFullDate); lastBalanceDate = paidInFullDate; outstandingBalance = outstandingBalance.minus(principalDueOnPaymentDate); Money rebate = Money.zero(loanPrincipal.getCurrency()); // Interest can be greater for following reasons: // 1. calculated interest may be slightly ahead based on daily // equivalent // method of calculating interest compared to original method. // 2. payments were under expected amount or after expected date // resulting in larger outstanding balance being used to calculate // interest // at each repayment point in time. if (totalRecalculatedInterestPaid.isLessThan(totalOriginalInterest) && totalRecalculatedInterestPaid.isGreaterThanZero()) { rebate = totalOriginalInterest.minus(totalRecalculatedInterestPaid); } return rebate; }