test amortisation for declining balance and support irregular first repayment on flat loans.

This commit is contained in:
Keith Woodlock
2012-08-17 15:11:37 +01:00
parent af5e2c0e84
commit 068620c810
8 changed files with 430 additions and 22 deletions
@@ -0,0 +1,26 @@
package org.mifosng.platform.loanschedule.domain;
import org.mifosng.platform.loan.domain.AmortizationMethod;
public class AmortizationLoanScheduleGeneratorFactory {
public AmortizationLoanScheduleGenerator createGenerator(final AmortizationMethod amortizationMethod) {
AmortizationLoanScheduleGenerator generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
switch (amortizationMethod) {
case INVALID:
generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
break;
case EQUAL_INSTALLMENTS:
generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
break;
case EQUAL_PRINCIPAL:
generator = new EqualPrincipalAmortizationLoanScheduleGenerator();
break;
}
return generator;
}
}
@@ -1,17 +1,11 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
@@ -37,6 +31,7 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory();
@Override
public LoanSchedule generate(
@@ -57,7 +52,8 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo);
// Determine with 'amortisation' approach to use
AmortizationLoanScheduleGenerator generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo.getAmortizationMethod());
return generator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData,
periodInterestRateForRepaymentPeriod, idealDisbursementDateBasedOnFirstRepaymentDate, scheduledDates);
}
@@ -12,7 +12,7 @@ public class DefaultLoanScheduleGeneratorFactory implements
switch (interestMethod) {
case FLAT:
loanScheduleGenerator = new FlatLoanScheduleGenerator();
loanScheduleGenerator = new FlatMethodLoanScheduleGenerator();
break;
case DECLINING_BALANCE:
loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator();
@@ -0,0 +1,102 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class EqualPrincipalAmortizationLoanScheduleGenerator implements AmortizationLoanScheduleGenerator {
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@Override
public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom,
final CurrencyData currencyData,
final BigDecimal periodInterestRateForRepaymentPeriod,
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
final List<LocalDate> scheduledDates) {
List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>();
// 1. we know 'principal' must be equal over repayments so use this to calculate interest and thus total repayment due.
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
Money totalDuePerInstallment = Money.zero(monetaryCurrency);
Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(monetaryCurrency);
Money totalInterest = Money.zero(monetaryCurrency);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator.calculateRepaymentPeriodAsAFractionOfDays(loanScheduleInfo.getRepaymentPeriodFrequencyType(),
loanScheduleInfo.getRepayEvery(), interestCalculatedFrom, scheduledDates, idealDisbursementDateBasedOnFirstRepaymentDate);
LocalDate startDate = disbursementDate;
int installmentNumber = 1;
for (LocalDate scheduledDueDate : scheduledDates) {
// number of days from startDate to this scheduledDate
int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime()).getDays();
Money interestForInstallment = this.periodicInterestRateCalculator.calculateInterestOn(outstandingBalance, periodInterestRateForRepaymentPeriod, daysInPeriod, loanScheduleInfo);
Money principalForInstallment = this.periodicInterestRateCalculator.calculatePrincipalOn(totalDuePerInstallment, interestForInstallment, loanScheduleInfo);
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
Money graceOnInterestForRepaymentPeriod = interestForInstallment;
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction - Integer.valueOf(1).doubleValue();
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25") && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
Money graceOnInterestForRepaymentPeriod = interestForInstallment.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf("0");
}
totalPrincipal = totalPrincipal.plus(principalForInstallment);
totalInterest = totalInterest.plus(interestForInstallment);
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
Money principalDifference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
if (principalDifference.isLessThanZero()) {
principalForInstallment = principalForInstallment.plus(principalDifference.abs());
} else if (principalDifference.isGreaterThanZero()) {
principalForInstallment = principalForInstallment.minus(principalDifference.abs());
}
}
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment);
MoneyData principalPerInstallmentValue = MoneyData.of(currencyData, principalForInstallment.getAmount());
MoneyData interestPerInstallmentValue = MoneyData.of(currencyData, interestForInstallment.getAmount());
MoneyData totalInstallmentDueValue = MoneyData.of(currencyData, totalInstallmentDue.getAmount());
MoneyData outstandingBalanceValue = MoneyData.of(currencyData, outstandingBalance.getAmount());
ScheduledLoanInstallment installment = new ScheduledLoanInstallment(
Integer.valueOf(installmentNumber), startDate,
scheduledDueDate, principalPerInstallmentValue,
interestPerInstallmentValue,
totalInstallmentDueValue,
outstandingBalanceValue);
scheduledLoanInstallments.add(installment);
startDate = scheduledDueDate;
installmentNumber++;
}
return new LoanSchedule(scheduledLoanInstallments);
}
}
@@ -15,7 +15,7 @@ import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@@ -49,18 +49,6 @@ public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
.dividedBy(loanScheduleInfo.getNumberOfRepayments(),
RoundingMode.HALF_EVEN);
// BigDecimal paymentPeriodsInYear = BigDecimal
// .valueOf(this.paymentPeriodsInOneYearCalculator
// .calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
// BigDecimal periodicInterestRate = loanScheduleInfo
// .getAnnualNominalInterestRate()
// .divide(paymentPeriodsInYear, mc)
// .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
// .multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery()));
// Money interestPerInstallment = loanScheduleInfo.getPrincipal().multiplyRetainScale(periodicInterestRate, RoundingMode.HALF_EVEN);
Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(outstandingBalance.getCurrency());
Money totalInterest = Money.zero(outstandingBalance.getCurrency());