calculate total interest based on etered loan term.

This commit is contained in:
Keith Woodlock
2012-08-07 14:38:28 +01:00
parent 41e57ba4bd
commit 5638a0880c
8 changed files with 92 additions and 25 deletions
@@ -147,10 +147,8 @@ public class LoansApiResource {
SubmitLoanApplicationCommand command = this.apiDataConversionService
.convertJsonToSubmitLoanApplicationCommand(jsonRequestBody);
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command
.toCalculateLoanScheduleCommand();
LoanSchedule loanSchedule = this.calculationPlatformService
.calculateLoanSchedule(calculateLoanScheduleCommand);
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
// for now just auto generating the loan schedule and setting support
// for 'manual' loan schedule creation later.
@@ -160,8 +158,7 @@ public class LoansApiResource {
return Response.ok().entity(loanSchedule).build();
}
EntityIdentifier identifier = this.loanWritePlatformService
.submitLoanApplication(command);
EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command);
return Response.ok().entity(identifier).build();
}
@@ -186,8 +186,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LocalDate interestChargedFromDate,
LoanLifecycleStateMachine lifecycleStateMachine) {
this.loanStatus = lifecycleStateMachine.transition(
LoanEvent.LOAN_CREATED, this.loanStatus);
this.loanStatus = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, this.loanStatus);
this.submittedOnDate = submittedOn.toDateTimeAtCurrentTime().toDate();
@@ -67,9 +67,14 @@ public class CalculationPlatformServiceImpl implements
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType());
final Integer loanTermFrequency = command.getLoanTermFrequency();
final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType());
final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, defaultNominalInterestRatePerPeriod);
@@ -96,7 +101,7 @@ public class CalculationPlatformServiceImpl implements
CurrencyData currencyData = new CurrencyData(applicationCurrency.getCode(), applicationCurrency.getName(), currency.getDigitsAfterDecimal(),
applicationCurrency.getDisplaySymbol(), applicationCurrency.getNameCode());
return loanScheduleGenerator.generate(loanScheduleInfo, command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate(),
return loanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType, command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate(),
command.getInterestChargedFromDate(), currencyData);
}
@@ -14,6 +14,7 @@ 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;
public class DecliningBalanceMethodLoanScheduleGenerator implements
LoanScheduleGenerator {
@@ -26,6 +27,8 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements
@Override
public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
final PeriodFrequencyType loanTermFrequencyType,
final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom, CurrencyData currencyData) {
@@ -43,6 +46,10 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements
final Money totalDuePerInstallment = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
final Money totalDue = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
// REVIEW. calculate total interest due based on loan term settings
final BigDecimal periodInterestRateForLoanTermPeriod = this.periodicInterestRateCalculator.calculateFrom(loanTermFrequencyType, loanScheduleInfo.getAnnualNominalInterestRate());
final Money totalRepaymentDueForLoanTerm = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo.getPrincipal(), loanTermFrequency, periodInterestRateForLoanTermPeriod, monetaryCurrency);
Money totalInterestDue = totalDue.minus(loanScheduleInfo.getPrincipal());
Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(monetaryCurrency);
@@ -13,6 +13,7 @@ import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
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 {
@@ -22,6 +23,8 @@ public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
@Override
public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
final PeriodFrequencyType loanTermFrequencyType,
final LocalDate disbursementDate, final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom, final CurrencyData currencyData) {
@@ -31,40 +34,57 @@ public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
// FIXME - use the expected loan term to work out interest due
// loan term = 9 months
BigDecimal loanTermPeriodsInYear = BigDecimal.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType));
BigDecimal interestRateForLoanTerm = loanScheduleInfo
.getAnnualNominalInterestRate()
.divide(loanTermPeriodsInYear, mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
.multiply(BigDecimal.valueOf(loanTermFrequency));
Money totalInterestForLoanTerm = loanScheduleInfo.getPrincipal().multiplyRetainScale(interestRateForLoanTerm, RoundingMode.HALF_EVEN);
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(loanScheduleInfo.getNumberOfRepayments()), RoundingMode.HALF_EVEN);
Money principalPerInstallment = loanScheduleInfo.getPrincipal()
.dividedBy(loanScheduleInfo.getNumberOfRepayments(),
RoundingMode.HALF_EVEN);
BigDecimal paymentPeriodsInYear = BigDecimal
.valueOf(this.paymentPeriodsInOneYearCalculator
.calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
// 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()));
// 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 interestPerInstallment = loanScheduleInfo.getPrincipal().multiplyRetainScale(periodicInterestRate, RoundingMode.HALF_EVEN);
Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(outstandingBalance.getCurrency());
Money totalInterest = Money.zero(outstandingBalance.getCurrency());
LocalDate startDate = disbursementDate;
int installmentNumber = 1;
for (LocalDate scheduledDueDate : scheduledDates) {
totalPrincipal = totalPrincipal.plus(principalPerInstallment);
totalInterest = totalInterest.plus(interestPerInstallment);
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
final Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
if (difference.isLessThanZero()) {
principalPerInstallment = principalPerInstallment.plus(difference.abs());
} else if (difference.isGreaterThanZero()) {
principalPerInstallment = principalPerInstallment.minus(difference.abs());
}
final Money interestDifference = totalInterest.minus(totalInterestForLoanTerm);
if (interestDifference.isLessThanZero()) {
interestPerInstallment = interestPerInstallment.plus(interestDifference.abs());
} else if (interestDifference.isGreaterThanZero()) {
interestPerInstallment = interestPerInstallment.minus(interestDifference.abs());
}
}
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment);
@@ -4,10 +4,13 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public interface LoanScheduleGenerator {
LoanSchedule generate(LoanProductRelatedDetail loanScheduleInfo,
Integer loanTermFrequency,
PeriodFrequencyType loanTermFrequencyType,
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom,
CurrencyData currencyData);
@@ -6,12 +6,23 @@ import java.math.RoundingMode;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public class PeriodicInterestRateCalculator {
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
public BigDecimal calculateFrom(LoanProductRelatedDetail loanScheduleInfo) {
public BigDecimal calculateFrom(final PeriodFrequencyType loanTermFrequencyType, final BigDecimal annualNominalInterestRate) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer paymentPeriodsInOneYear = this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType);
BigDecimal divisor = BigDecimal.valueOf(paymentPeriodsInOneYear * 100);
return annualNominalInterestRate.divide(divisor, mc);
}
public BigDecimal calculateFrom(final LoanProductRelatedDetail loanScheduleInfo) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
@@ -8,7 +8,9 @@ import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class PmtCalculator {
public Money calculatePaymentForOnePeriodFrom(LoanProductRelatedDetail loanScheduleInfo, BigDecimal periodInterestRateForRepaymentPeriod, MonetaryCurrency monetaryCurrency) {
public Money calculatePaymentForOnePeriodFrom(final LoanProductRelatedDetail loanScheduleInfo,
final BigDecimal periodInterestRateForRepaymentPeriod,
final MonetaryCurrency monetaryCurrency) {
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
double futureValue = 0;
@@ -22,7 +24,9 @@ public class PmtCalculator {
return Money.of(monetaryCurrency, BigDecimal.valueOf(paymentPerInstallment));
}
public Money calculateTotalRepaymentFrom(LoanProductRelatedDetail loanScheduleInfo, BigDecimal periodInterestRateForRepaymentPeriod, MonetaryCurrency monetaryCurrency) {
public Money calculateTotalRepaymentFrom(final LoanProductRelatedDetail loanScheduleInfo,
final BigDecimal periodInterestRateForRepaymentPeriod,
final MonetaryCurrency monetaryCurrency) {
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
double futureValue = 0;
@@ -37,6 +41,27 @@ public class PmtCalculator {
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
}
public Money calculateTotalRepaymentFrom(
final Money principal,
final Integer loanTermFrequency,
final BigDecimal periodInterestRateForLoanTermPeriod,
final MonetaryCurrency monetaryCurrency) {
double interestRateFraction = periodInterestRateForLoanTermPeriod.doubleValue();
double futureValue = 0;
double numberOfPeriods = loanTermFrequency;
double principalAmount = principal.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
// work out the total payment (principal + interest components) for each installment due.
// use the period type and interest as expressed e.g. 2% per month or 24% per year even is 'daily' interest calculation is selected
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principalAmount, futureValue, false);
double totalRepayment = paymentPerInstallment * loanTermFrequency;
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
}
/**
* PMT calculates a fixed monthly payment to be paid by borrower every 'period' to ensure loan is paid off in full (with interest).