calculate total interest based on etered loan term.
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
+7
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -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);
|
||||
|
||||
+32
-12
@@ -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);
|
||||
|
||||
+3
@@ -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);
|
||||
|
||||
|
||||
+12
-1
@@ -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);
|
||||
|
||||
|
||||
+27
-2
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user