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
@@ -59,7 +59,35 @@ public class DecliningBalanceMethodLoanScheduleGeneratorTest {
given(schedule).assertTotalPaymentDueOf(1, is("57718.30")).assertInterestDueComponentOf(1, is("12000.00")).assertPrincipalDueComponentOf(1, is("45718.30"));
given(schedule).assertTotalPaymentDueOf(2, is("57718.30")).assertInterestDueComponentOf(2, is("9256.90")).assertPrincipalDueComponentOf(2, is("48461.40"));
given(schedule).assertTotalPaymentDueOf(3, is("57718.30")).assertInterestDueComponentOf(3, is("6349.22")).assertPrincipalDueComponentOf(3, is("51369.08"));
given(schedule).assertTotalPaymentDueOf(4, is("57718.30")).assertInterestDueComponentOf(4, is("3267.08")).assertPrincipalDueComponentOf(4, is("54451.22"));
given(schedule).assertTotalPaymentDueOf(4, is("57718.29")).assertInterestDueComponentOf(4, is("3267.07")).assertPrincipalDueComponentOf(4, is("54451.22"));
}
@Test
public void givenEqualPrincipalAmortisationShouldCalculateInterestComponentFromOutstandingPrincipalBlanceResultingInDecliningInterestAndTotalRepayments() {
// setup
Integer loanTermFrequency = 12;
PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForEqualPrincipalAmortizationQuarterly();
LocalDate disbursementDate = new LocalDate(2011, 1, 11);
LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
// pre assertions
assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
// exercise test
LoanSchedule schedule = decliningBalanceGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
// verification (post assertions)
given(schedule).assertHasInstallmentSizeOf(4);
given(schedule).assertPrincipalDueComponentOf(1, is("50000.00")).assertInterestDueComponentOf(1, is("12000.00")).assertTotalPaymentDueOf(1, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(2, is("50000.00")).assertInterestDueComponentOf(2, is("9000.00")).assertTotalPaymentDueOf(2, is("59000.00"));
given(schedule).assertPrincipalDueComponentOf(3, is("50000.00")).assertInterestDueComponentOf(3, is("6000.00")).assertTotalPaymentDueOf(3, is("56000.00"));
given(schedule).assertPrincipalDueComponentOf(4, is("50000.00")).assertInterestDueComponentOf(4, is("3000.00")).assertTotalPaymentDueOf(4, is("53000.00"));
}
private LoanScheduleAssertionBuilder given(final LoanSchedule schedule) {
@@ -0,0 +1,164 @@
package org.mifosng.platform.loanschedule.domain;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.List;
import org.hamcrest.Matcher;
import org.joda.time.LocalDate;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class FlatMethodLoanScheduleGeneratorTest {
// class under test
private FlatMethodLoanScheduleGenerator flatLoanScheduleGenerator;
// test doubles
private CurrencyData currencyData = null;
private LoanProductRelatedDetail loanScheduleInfo = null;
@Before
public void setupForEachTest() {
currencyData = new CurrencyData("USD", "US Dollar", 2, "$", "currency.USD");
flatLoanScheduleGenerator = new FlatMethodLoanScheduleGenerator();
}
@Test
public void givenFlatMethodShouldCalculateFlatInterestComponentToDerivePrincipalAndTotalRepaymentRegardlessOfEqualInstallmentAmortisation() {
// setup
Integer loanTermFrequency = 12;
PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForFlatQuarterly(AmortizationMethod.EQUAL_INSTALLMENTS);
LocalDate disbursementDate = new LocalDate(2011, 1, 11);
LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
// pre assertions
assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
// exercise test
LoanSchedule schedule = flatLoanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
// verification (post assertions)
given(schedule).assertHasInstallmentSizeOf(4);
given(schedule).assertPrincipalDueComponentOf(1, is("50000.00")).assertInterestDueComponentOf(1, is("12000.00")).assertTotalPaymentDueOf(1, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(2, is("50000.00")).assertInterestDueComponentOf(2, is("12000.00")).assertTotalPaymentDueOf(2, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(3, is("50000.00")).assertInterestDueComponentOf(3, is("12000.00")).assertTotalPaymentDueOf(3, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(4, is("50000.00")).assertInterestDueComponentOf(4, is("12000.00")).assertTotalPaymentDueOf(4, is("62000.00"));
}
@Test
public void givenFlatMethodShouldCalculateFlatInterestComponentToDerivePrincipalAndTotalRepaymentRegardlessOfEqualPrincipalAmortisation() {
// setup
Integer loanTermFrequency = 12;
PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForFlatQuarterly(AmortizationMethod.EQUAL_PRINCIPAL);
LocalDate disbursementDate = new LocalDate(2011, 1, 11);
LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
// pre assertions
assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
// exercise test
LoanSchedule schedule = flatLoanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
// verification (post assertions)
given(schedule).assertHasInstallmentSizeOf(4);
given(schedule).assertPrincipalDueComponentOf(1, is("50000.00")).assertInterestDueComponentOf(1, is("12000.00")).assertTotalPaymentDueOf(1, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(2, is("50000.00")).assertInterestDueComponentOf(2, is("12000.00")).assertTotalPaymentDueOf(2, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(3, is("50000.00")).assertInterestDueComponentOf(3, is("12000.00")).assertTotalPaymentDueOf(3, is("62000.00"));
given(schedule).assertPrincipalDueComponentOf(4, is("50000.00")).assertInterestDueComponentOf(4, is("12000.00")).assertTotalPaymentDueOf(4, is("62000.00"));
}
@Test
public void givenFlatMethodAndIrregularFirstRepaymentShouldCalculateFlatInterestComponent() {
// setup
currencyData = new CurrencyData("KSH", "Keyan Shilling", 0, "KSh", "currency.KSH");
Integer loanTermFrequency = 9;
PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForIrregularFlatEveryFourMonths();
LocalDate disbursementDate = new LocalDate(2012, 3, 27);
LocalDate firstRepaymentDate = new LocalDate(2011, 7, 27);
LocalDate interestCalculatedFrom = disbursementDate;
// exercise test
LoanSchedule schedule = flatLoanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
// verification (post assertions)
given(schedule).assertHasInstallmentSizeOf(2);
given(schedule).assertPrincipalDueComponentOf(1, is("7500")).assertInterestDueComponentOf(1, is("1350")).assertTotalPaymentDueOf(1, is("8850"));
given(schedule).assertPrincipalDueComponentOf(2, is("7500")).assertInterestDueComponentOf(2, is("1350")).assertTotalPaymentDueOf(2, is("8850"));
}
private LoanScheduleAssertionBuilder given(final LoanSchedule schedule) {
return new LoanScheduleAssertionBuilder(schedule);
}
private class LoanScheduleAssertionBuilder {
private final LoanSchedule schedule;
public LoanScheduleAssertionBuilder(final LoanSchedule schedule) {
this.schedule = schedule;
}
public LoanScheduleAssertionBuilder assertPrincipalDueComponentOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
final Integer installmentIndex = installmentNumber -1;
List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
assertThat("Expected principal due component for installment " + installmentNumber + " is wrong: ",
repaymentInstallment.getPrincipalDue().toString(), matchesExpectedAmount);
return this;
}
public LoanScheduleAssertionBuilder assertInterestDueComponentOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
final Integer installmentIndex = installmentNumber -1;
List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
assertThat("Expected interest due component for installment " + installmentNumber + " is wrong: ",
repaymentInstallment.getInterestDue().toString(), matchesExpectedAmount);
return this;
}
public LoanScheduleAssertionBuilder assertTotalPaymentDueOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
final Integer installmentIndex = installmentNumber -1;
List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
assertThat("Expected total payment for installment " + installmentNumber + " is wrong: ",
repaymentInstallment.getTotalInstallmentDue().toString(), matchesExpectedAmount);
return this;
}
public LoanScheduleAssertionBuilder assertHasInstallmentSizeOf(int expectedInstallmentSize) {
assertThat("Expected number of installments is wrong: ", schedule.getScheduledLoanInstallments().size(), is(expectedInstallmentSize));
return this;
}
}
}
@@ -14,6 +14,34 @@ import org.mifosng.platform.loan.domain.PeriodFrequencyType;
* This class is used to keep in one place configurations for setting up {@link LoanProductRelatedDetail} object used in {@link LoanScheduleGenerator}'s
*/
public class LoanProductRelatedDetailTestHelper {
public static LoanProductRelatedDetail createSettingsForEqualPrincipalAmortizationQuarterly() {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
InterestMethod interestMethod = InterestMethod.DECLINING_BALANCE;
InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
Integer repayEvery = Integer.valueOf(3);
PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
Integer defaultNumberOfRepayments = Integer.valueOf(4);
AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL;
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return createLoanProductRelatedDetail(currency, defaultPrincipal,
defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfRepayments,
amortizationMethod, inArrearsTolerance);
}
public static LoanProductRelatedDetail createSettingsForEqualInstallmentAmortizationQuarterly() {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
@@ -34,6 +62,82 @@ public class LoanProductRelatedDetailTestHelper {
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return createLoanProductRelatedDetail(currency, defaultPrincipal,
defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfRepayments,
amortizationMethod, inArrearsTolerance);
}
public static LoanProductRelatedDetail createSettingsForFlatQuarterly(final AmortizationMethod amortizationMethod) {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
InterestMethod interestMethod = InterestMethod.FLAT;
InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
Integer repayEvery = Integer.valueOf(3);
PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
Integer defaultNumberOfRepayments = Integer.valueOf(4);
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return createLoanProductRelatedDetail(currency, defaultPrincipal,
defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfRepayments,
amortizationMethod, inArrearsTolerance);
}
public static LoanProductRelatedDetail createSettingsForIrregularFlatEveryFourMonths() {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("KSH").withDigitsAfterDecimal(0).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("15000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24"));
InterestMethod interestMethod = InterestMethod.FLAT;
InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
Integer repayEvery = Integer.valueOf(3);
PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS;
Integer defaultNumberOfRepayments = Integer.valueOf(2);
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL;
return createLoanProductRelatedDetail(currency, defaultPrincipal,
defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfRepayments,
amortizationMethod, inArrearsTolerance);
}
private static LoanProductRelatedDetail createLoanProductRelatedDetail(
MonetaryCurrency currency, BigDecimal defaultPrincipal,
BigDecimal defaultNominalInterestRatePerPeriod,
PeriodFrequencyType interestPeriodFrequencyType,
BigDecimal defaultAnnualNominalInterestRate,
InterestMethod interestMethod,
InterestCalculationPeriodMethod interestCalculationPeriodMethod,
Integer repayEvery, PeriodFrequencyType repaymentFrequencyType,
Integer defaultNumberOfRepayments,
AmortizationMethod amortizationMethod, BigDecimal inArrearsTolerance) {
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);