test amortisation for declining balance and support irregular first repayment on flat loans.
This commit is contained in:
+26
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+3
-7
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+102
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-13
@@ -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());
|
||||
+29
-1
@@ -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) {
|
||||
|
||||
+164
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user