MIFOSX-672: fix issue with FLAT interest calculation not correctly calculation prinicpal balance for final installments.

This commit is contained in:
Keith Woodlock
2013-09-18 12:15:30 +01:00
parent b6324630ff
commit b2168bff2c
@@ -50,7 +50,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 +60,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 +68,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
@@ -171,15 +171,10 @@ public final class LoanApplicationTerms {
Money adjusted = principalForPeriod; Money adjusted = principalForPeriod;
// adjust cumulative fields for principal & interest if needed final Money totalPrincipalRemaining = this.principal.minus(totalCumulativePrincipalToDate);
if (this.principal.getAmount().subtract(totalCumulativePrincipalToDate.getAmount()).add(principalForPeriod.getAmount()).compareTo(principalForPeriod.getAmount()) == -1) { if (totalPrincipalRemaining.isLessThanZero()) {
adjusted = adjusted.minus(adjusted); // paid too much principal, subtract amount that overpays from principal paid for period.
MonetaryCurrency monetaryCurrency = new MonetaryCurrency(adjusted.getCurrencyCode(), 0, adjusted.getCurrencyInMultiplesOf()); adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs());
BigDecimal diff = this.principal.getAmount().subtract(totalCumulativePrincipalToDate.getAmount()).add(principalForPeriod.getAmount());
if(diff.doubleValue()<0){
diff = BigDecimal.ZERO;
}
adjusted = adjusted.plus(Money.of(monetaryCurrency,diff));
} else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) { } else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
final Money difference = totalCumulativePrincipalToDate.minus(this.principal); final Money difference = totalCumulativePrincipalToDate.minus(this.principal);
@@ -197,15 +192,11 @@ public final class LoanApplicationTerms {
final Money totalInterestDueForLoan, final int periodNumber) { final Money totalInterestDueForLoan, final int periodNumber) {
Money adjusted = interestForThisPeriod; Money adjusted = interestForThisPeriod;
if (totalInterestDueForLoan.getAmount().subtract(totalCumulativeInterestToDate.getAmount()).add(interestForThisPeriod.getAmount())
.compareTo(interestForThisPeriod.getAmount()) == -1) { final Money totalInterestRemaining = totalInterestDueForLoan.minus(totalCumulativeInterestToDate);
adjusted = adjusted.minus(adjusted); if (totalInterestRemaining.isLessThanZero()) {
MonetaryCurrency monetaryCurrency = new MonetaryCurrency(adjusted.getCurrencyCode(), 0, adjusted.getCurrencyInMultiplesOf()); // paid too much interest, subtract amount that overpays from interest paid for period.
BigDecimal diff = totalInterestDueForLoan.getAmount().subtract(totalCumulativeInterestToDate.getAmount()).add(interestForThisPeriod.getAmount()); adjusted = interestForThisPeriod.minus(totalInterestRemaining.abs());
if(diff.doubleValue()<0){
diff = BigDecimal.ZERO;
}
adjusted = adjusted.plus(Money.of(monetaryCurrency,diff));
} else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) { } else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan); final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan);
if (interestDifference.isLessThanZero()) { if (interestDifference.isLessThanZero()) {
@@ -223,7 +214,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) {
@@ -270,7 +261,7 @@ public final class LoanApplicationTerms {
switch (this.amortizationMethod) { switch (this.amortizationMethod) {
case EQUAL_INSTALLMENTS: case EQUAL_INSTALLMENTS:
// equal installments // equal installments
int periodsElapsed = periodNumber - 1; final int periodsElapsed = periodNumber - 1;
final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc); final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc);
final Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod( final Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(
@@ -313,7 +304,7 @@ public final class LoanApplicationTerms {
case EQUAL_PRINCIPAL: case EQUAL_PRINCIPAL:
// interest follows time-value of money and is brought // interest follows time-value of money and is brought
// forward to next applicable interest payment period // forward to next applicable interest payment period
PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc, final PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc,
interestBroughtForwardDueToGrace); interestBroughtForwardDueToGrace);
interestForInstallment = result.interest(); interestForInstallment = result.interest();
interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace(); interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
@@ -324,10 +315,10 @@ public final class LoanApplicationTerms {
break; break;
case DECLINING_BALANCE: case DECLINING_BALANCE:
Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator,
mc, daysInPeriod, outstandingBalance); mc, daysInPeriod, outstandingBalance);
Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace(calculator, final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, mc, daysInPeriod, outstandingBalance, periodNumber); interestCalculationGraceOnRepaymentPeriodFraction, mc, daysInPeriod, outstandingBalance, periodNumber);
interestForInstallment = interestForThisInstallmentAfterGrace; interestForInstallment = interestForThisInstallmentAfterGrace;
@@ -359,7 +350,7 @@ public final class LoanApplicationTerms {
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed); final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed);
final double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments; final double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments;
return Money.of(principal.getCurrency(), BigDecimal.valueOf(totalRepayment)); return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment));
} }
/** /**
@@ -448,12 +439,12 @@ public final class LoanApplicationTerms {
final Money totalInterestForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc); final Money totalInterestForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc);
Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace()); final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace());
Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree); final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree);
Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.numberOfRepayments); final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.numberOfRepayments);
interestForInstallment = realTotalInterestForLoan interestForInstallment = realTotalInterestForLoan
.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode()); .dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode());
@@ -548,10 +539,10 @@ public final class LoanApplicationTerms {
switch (this.interestCalculationPeriodMethod) { switch (this.interestCalculationPeriodMethod) {
case DAILY: case DAILY:
BigDecimal dailyInterestRate = this.annualNominalInterestRate.divide(BigDecimal.valueOf(Long.valueOf(365)), mc) final BigDecimal dailyInterestRate = this.annualNominalInterestRate.divide(BigDecimal.valueOf(Long.valueOf(365)), mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(this.repaymentEvery)); .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(this.repaymentEvery));
BigDecimal equivalentInterestRateForPeriod = dailyInterestRate.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod))); final BigDecimal equivalentInterestRateForPeriod = dailyInterestRate.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod)));
interestDue = outstandingBalance.multiplyRetainScale(equivalentInterestRateForPeriod, mc.getRoundingMode()); interestDue = outstandingBalance.multiplyRetainScale(equivalentInterestRateForPeriod, mc.getRoundingMode());
break; break;
@@ -589,7 +580,7 @@ public final class LoanApplicationTerms {
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25") } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25")
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) { && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction); final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
interest = interest.minus(graceOnInterestForRepaymentPeriod); interest = interest.minus(graceOnInterestForRepaymentPeriod);
fraction = Double.valueOf("0"); fraction = Double.valueOf("0");
} }
@@ -616,7 +607,7 @@ public final class LoanApplicationTerms {
private Money calculatePrincipalDueForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, private Money calculatePrincipalDueForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc,
final int periodNumber, final Money totalDuePerInstallment, final int daysInPeriod, final Money outstandingBalance) { final int periodNumber, final Money totalDuePerInstallment, final int daysInPeriod, final Money outstandingBalance) {
Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc,
daysInPeriod, outstandingBalance); daysInPeriod, outstandingBalance);
Money principal = totalDuePerInstallment.minus(interestForThisInstallmentBeforeGrace); Money principal = totalDuePerInstallment.minus(interestForThisInstallmentBeforeGrace);