MIFOSX-729: For flat loans, daily interest calculation not using daily periods when calulating interest, it was still using the installment period and as a result the interest calculated was not correct.

This commit is contained in:
Keith Woodlock
2013-10-27 18:16:05 +00:00
parent 8e6370f8c3
commit 2641d03c8f
3 changed files with 65 additions and 17 deletions
@@ -38,6 +38,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays, final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays,
workingDays); workingDays);
final LocalDate loanEndDate = scheduledDates.get(scheduledDates.size() - 1);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
// 2. determine the total charges due at time of disbursement // 2. determine the total charges due at time of disbursement
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
@@ -141,8 +144,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest, Money outstandingBalance, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, int daysInPeriodApplicableForInterest,
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc); Money outstandingBalance, LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc);
protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
return periodNumber == numberOfRepayments; return periodNumber == numberOfRepayments;
@@ -18,12 +18,12 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener
final int daysInPeriodApplicableForInterest, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int daysInPeriodApplicableForInterest, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms,
final int periodNumber, final MathContext mc) { final int periodNumber, final MathContext mc) {
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriodApplicableForInterest, Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator,
outstandingBalance, periodNumber, mc); daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc);
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace,
outstandingBalance); daysInPeriodApplicableForInterest, outstandingBalance);
Money interestForThisInstallment = result.interest(); Money interestForThisInstallment = result.interest();
// update cumulative fields for principal & interest // update cumulative fields for principal & interest
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.Date; import java.util.Date;
import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
@@ -50,7 +51,7 @@ public final class LoanApplicationTerms {
* Integer representing the number of 'repayment frequencies' or * Integer representing the number of 'repayment frequencies' or
* installments where 'grace' should apply to the payment of interest in a * installments where 'grace' should apply to the payment of interest in a
* loans repayment period (installment). * loans repayment period (installment).
* *
* <b>Note:</b> Interest is still calculated taking into account the full * <b>Note:</b> Interest is still calculated taking into account the full
* loan term, the interest is simply offset to a later period. * loan term, the interest is simply offset to a later period.
*/ */
@@ -60,7 +61,7 @@ public final class LoanApplicationTerms {
* Integer representing the number of 'repayment frequencies' or * Integer representing the number of 'repayment frequencies' or
* installments where 'grace' should apply to the charging of interest in a * installments where 'grace' should apply to the charging of interest in a
* loans repayment period (installment). * loans repayment period (installment).
* *
* <b>Note:</b> The loan is <i>interest-free</i> for the period of time * <b>Note:</b> The loan is <i>interest-free</i> for the period of time
* indicated. * indicated.
*/ */
@@ -68,7 +69,7 @@ public final class LoanApplicationTerms {
/** /**
* Legacy method of support 'grace' on the charging of interest on a loan. * Legacy method of support 'grace' on the charging of interest on a loan.
* *
* <p> * <p>
* For the typical structured loan, its reasonable to use an integer to * For the typical structured loan, its reasonable to use an integer to
* indicate the number of 'repayment frequency' periods the 'grace' should * indicate the number of 'repayment frequency' periods the 'grace' should
@@ -82,6 +83,9 @@ public final class LoanApplicationTerms {
private final LocalDate interestChargedFromDate; private final LocalDate interestChargedFromDate;
private final Money inArrearsTolerance; private final Money inArrearsTolerance;
// added
private LocalDate loanEndDate;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, final AmortizationMethod amortizationMethod, final PeriodFrequencyType repaymentPeriodFrequencyType, final AmortizationMethod amortizationMethod,
@@ -216,7 +220,7 @@ public final class LoanApplicationTerms {
/** /**
* Calculates the total interest to be charged on loan taking into account * Calculates the total interest to be charged on loan taking into account
* grace settings. * grace settings.
* *
*/ */
public Money calculateTotalInterestCharged(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { public Money calculateTotalInterestCharged(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
@@ -224,7 +228,7 @@ public final class LoanApplicationTerms {
switch (this.interestMethod) { switch (this.interestMethod) {
case FLAT: case FLAT:
final Money totalInterestChargedForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc); final Money totalInterestChargedForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
@@ -321,7 +325,8 @@ public final class LoanApplicationTerms {
calculator, mc, daysInPeriodApplicableForInterest, outstandingBalance); calculator, mc, daysInPeriodApplicableForInterest, outstandingBalance);
final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace( final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace(
calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, daysInPeriodApplicableForInterest, outstandingBalance, periodNumber); calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, daysInPeriodApplicableForInterest,
outstandingBalance, periodNumber);
interestForInstallment = interestForThisInstallmentAfterGrace; interestForInstallment = interestForThisInstallmentAfterGrace;
if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) { if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) {
@@ -359,7 +364,7 @@ public final class LoanApplicationTerms {
* general method to calculate totalInterestDue discounting any grace * general method to calculate totalInterestDue discounting any grace
* settings * settings
*/ */
private Money calculateTotalInterestDueWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { private Money calculateTotalFlatInterestDueWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
Money totalInterestDue = this.principal.zero(); Money totalInterestDue = this.principal.zero();
@@ -385,15 +390,43 @@ public final class LoanApplicationTerms {
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.loanTermFrequency); final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm();
return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
.multiply(loanTermFrequencyBigDecimal); .multiply(loanTermFrequencyBigDecimal);
} }
private BigDecimal calculatePeriodsInLoanTerm() {
BigDecimal periodsInLoanTerm = BigDecimal.valueOf(this.loanTermFrequency);
switch (this.interestCalculationPeriodMethod) {
case DAILY:
// number of days from 'ideal disbursement' to final date
LocalDate loanStartDate = getExpectedDisbursementDate();
if (getInterestChargedFromDate() != null && loanStartDate.isBefore(getInterestChargedFromLocalDate())) {
loanStartDate = getInterestChargedFromLocalDate();
}
final int periodsInLoanTermInteger = Days.daysBetween(loanStartDate, this.loanEndDate).getDays();
periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger);
break;
case INVALID:
break;
case SAME_AS_REPAYMENT_PERIOD:
break;
}
return periodsInLoanTerm;
}
public void updateLoanEndDate(final LocalDate loanEndDate) {
this.loanEndDate = loanEndDate;
}
private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
final Money totalInterestForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc); final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.numberOfRepayments), mc.getRoundingMode()); return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.numberOfRepayments), mc.getRoundingMode());
} }
@@ -439,7 +472,7 @@ public final class LoanApplicationTerms {
interestForInstallment = interestForInstallment.zero(); interestForInstallment = interestForInstallment.zero();
} else { } else {
final Money totalInterestForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc); final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
@@ -469,7 +502,19 @@ public final class LoanApplicationTerms {
} }
private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) { private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) {
return calculator.calculate(this.repaymentPeriodFrequencyType).longValue();
long periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue();
switch (this.interestCalculationPeriodMethod) {
case DAILY:
periodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue();
break;
case INVALID:
break;
case SAME_AS_REPAYMENT_PERIOD:
break;
}
return periodsInOneYear;
} }
private int calculateNumberOfRepaymentsWithPrincipalPayment() { private int calculateNumberOfRepaymentsWithPrincipalPayment() {