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 SubmitLoanApplicationCommand command = this.apiDataConversionService
.convertJsonToSubmitLoanApplicationCommand(jsonRequestBody); .convertJsonToSubmitLoanApplicationCommand(jsonRequestBody);
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
.toCalculateLoanScheduleCommand(); LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
LoanSchedule loanSchedule = this.calculationPlatformService
.calculateLoanSchedule(calculateLoanScheduleCommand);
// for now just auto generating the loan schedule and setting support // for now just auto generating the loan schedule and setting support
// for 'manual' loan schedule creation later. // for 'manual' loan schedule creation later.
@@ -160,8 +158,7 @@ public class LoansApiResource {
return Response.ok().entity(loanSchedule).build(); return Response.ok().entity(loanSchedule).build();
} }
EntityIdentifier identifier = this.loanWritePlatformService EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command);
.submitLoanApplication(command);
return Response.ok().entity(identifier).build(); return Response.ok().entity(identifier).build();
} }
@@ -186,8 +186,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LocalDate interestChargedFromDate, LocalDate interestChargedFromDate,
LoanLifecycleStateMachine lifecycleStateMachine) { LoanLifecycleStateMachine lifecycleStateMachine) {
this.loanStatus = lifecycleStateMachine.transition( this.loanStatus = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, this.loanStatus);
LoanEvent.LOAN_CREATED, this.loanStatus);
this.submittedOnDate = submittedOn.toDateTimeAtCurrentTime().toDate(); this.submittedOnDate = submittedOn.toDateTimeAtCurrentTime().toDate();
@@ -67,9 +67,14 @@ public class CalculationPlatformServiceImpl implements
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType()); final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType()); final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType()); 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 Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType()); final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType());
final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, defaultNominalInterestRatePerPeriod); 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(), CurrencyData currencyData = new CurrencyData(applicationCurrency.getCode(), applicationCurrency.getName(), currency.getDigitsAfterDecimal(),
applicationCurrency.getDisplaySymbol(), applicationCurrency.getNameCode()); 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); 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.currency.domain.Money;
import org.mifosng.platform.loan.domain.AmortizationMethod; import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public class DecliningBalanceMethodLoanScheduleGenerator implements public class DecliningBalanceMethodLoanScheduleGenerator implements
LoanScheduleGenerator { LoanScheduleGenerator {
@@ -26,6 +27,8 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements
@Override @Override
public LoanSchedule generate( public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo, final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
final PeriodFrequencyType loanTermFrequencyType,
final LocalDate disbursementDate, final LocalDate disbursementDate,
final LocalDate firstRepaymentDate, final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom, CurrencyData currencyData) { final LocalDate interestCalculatedFrom, CurrencyData currencyData) {
@@ -43,6 +46,10 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements
final Money totalDuePerInstallment = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency); final Money totalDuePerInstallment = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
final Money totalDue = this.pmtCalculator.calculateTotalRepaymentFrom(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 totalInterestDue = totalDue.minus(loanScheduleInfo.getPrincipal());
Money outstandingBalance = loanScheduleInfo.getPrincipal(); Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(monetaryCurrency); 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.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public class FlatLoanScheduleGenerator implements LoanScheduleGenerator { public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
@@ -22,6 +23,8 @@ public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
@Override @Override
public LoanSchedule generate( public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo, final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
final PeriodFrequencyType loanTermFrequencyType,
final LocalDate disbursementDate, final LocalDate firstRepaymentDate, final LocalDate disbursementDate, final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom, final CurrencyData currencyData) { final LocalDate interestCalculatedFrom, final CurrencyData currencyData) {
@@ -31,40 +34,57 @@ public class FlatLoanScheduleGenerator implements LoanScheduleGenerator {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
// FIXME - use the expected loan term to work out interest due BigDecimal loanTermPeriodsInYear = BigDecimal.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType));
// loan term = 9 months 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() Money principalPerInstallment = loanScheduleInfo.getPrincipal()
.dividedBy(loanScheduleInfo.getNumberOfRepayments(), .dividedBy(loanScheduleInfo.getNumberOfRepayments(),
RoundingMode.HALF_EVEN); RoundingMode.HALF_EVEN);
BigDecimal paymentPeriodsInYear = BigDecimal // BigDecimal paymentPeriodsInYear = BigDecimal
.valueOf(this.paymentPeriodsInOneYearCalculator // .valueOf(this.paymentPeriodsInOneYearCalculator
.calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType())); // .calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
BigDecimal periodicInterestRate = loanScheduleInfo // BigDecimal periodicInterestRate = loanScheduleInfo
.getAnnualNominalInterestRate() // .getAnnualNominalInterestRate()
.divide(paymentPeriodsInYear, mc) // .divide(paymentPeriodsInYear, mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc) // .divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
.multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery())); // .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 outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(outstandingBalance.getCurrency()); Money totalPrincipal = Money.zero(outstandingBalance.getCurrency());
Money totalInterest = Money.zero(outstandingBalance.getCurrency());
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
int installmentNumber = 1; int installmentNumber = 1;
for (LocalDate scheduledDueDate : scheduledDates) { for (LocalDate scheduledDueDate : scheduledDates) {
totalPrincipal = totalPrincipal.plus(principalPerInstallment); totalPrincipal = totalPrincipal.plus(principalPerInstallment);
totalInterest = totalInterest.plus(interestPerInstallment);
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) { if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal()); final Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
if (difference.isLessThanZero()) { if (difference.isLessThanZero()) {
principalPerInstallment = principalPerInstallment.plus(difference.abs()); principalPerInstallment = principalPerInstallment.plus(difference.abs());
} else if (difference.isGreaterThanZero()) { } else if (difference.isGreaterThanZero()) {
principalPerInstallment = principalPerInstallment.minus(difference.abs()); 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); 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.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public interface LoanScheduleGenerator { public interface LoanScheduleGenerator {
LoanSchedule generate(LoanProductRelatedDetail loanScheduleInfo, LoanSchedule generate(LoanProductRelatedDetail loanScheduleInfo,
Integer loanTermFrequency,
PeriodFrequencyType loanTermFrequencyType,
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom, LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom,
CurrencyData currencyData); CurrencyData currencyData);
@@ -6,12 +6,23 @@ import java.math.RoundingMode;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public class PeriodicInterestRateCalculator { public class PeriodicInterestRateCalculator {
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); 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); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
@@ -8,7 +8,9 @@ import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class PmtCalculator { 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 interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
double futureValue = 0; double futureValue = 0;
@@ -22,7 +24,9 @@ public class PmtCalculator {
return Money.of(monetaryCurrency, BigDecimal.valueOf(paymentPerInstallment)); 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 interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
double futureValue = 0; double futureValue = 0;
@@ -38,6 +42,27 @@ public class PmtCalculator {
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment)); 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). * PMT calculates a fixed monthly payment to be paid by borrower every 'period' to ensure loan is paid off in full (with interest).
* *