diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 31c2591a2..e6ba38bbb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -113,6 +113,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener int instalmentNumber = 1; Money outstandingBalance = principalDisbursed; + + // Set fixed EMI Amount + updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance); + // disbursement map for tranche details(will added to outstanding // balance as per the start date) final Map disburseDetailMap = new HashMap<>(); @@ -159,10 +163,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Map> compoundingDateVariations = new HashMap<>(); boolean isNextRepaymentAvailable = true; Boolean extendTermForDailyRepayments = false; - - if(holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true && - loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS && - loanApplicationTerms.getRepaymentEvery() == 1){ + + if (holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true + && loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS + && loanApplicationTerms.getRepaymentEvery() == 1) { holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue()); extendTermForDailyRepayments = true; } @@ -170,7 +174,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment); - isFirstRepayment = false; + isFirstRepayment = false; LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); @@ -179,8 +183,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener compoundingMap, loanCharges, currency); compoundingDateVariations.put(periodStartDate, new TreeMap<>(compoundingMap)); } - - if(extendTermForDailyRepayments){ + + if (extendTermForDailyRepayments) { actualRepaymentDate = scheduledDueDate; } @@ -367,7 +371,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // method applies early payment strategy reducePrincipal = reducePrincipal.plus(unprocessed); - reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); + reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, + totalCumulativePrincipal, periodNumber, mc); } // identify late payments and add compounding // details to @@ -501,10 +506,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); Money principalForThisPeriod = principalInterestForThisPeriod.principal(); - if (principalForThisPeriod.isZero()) { - loanApplicationTerms.resetFixedEmiAmount(); - } - // applies early payments on principal portion if (principalForThisPeriod.isGreaterThan(reducePrincipal)) { principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal); @@ -518,9 +519,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // reducePrincipal.plus(unprocessed); Money reducedBalance = earlyPaidAmount; earlyPaidAmount = earlyPaidAmount.minus(principalForThisPeriod); + boolean isEmiAmountChanged = false; if (earlyPaidAmount.isGreaterThanZero()) { reducePrincipal = reducePrincipal.plus(earlyPaidAmount); - reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); + BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount(); + reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, totalCumulativePrincipal, + periodNumber + 1, mc); + if (fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) { + isEmiAmountChanged = true; + } principalForThisPeriod = principalForThisPeriod.plus(earlyPaidAmount); } @@ -587,6 +594,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener compoundingMap.clear(); compoundingMap.putAll(compoundingDateVariations.get(periodStartDateApplicableForInterest)); } + if (isEmiAmountChanged) { + updateFixedInstallmentAmount(mc, loanApplicationTerms, + loanApplicationTerms.getExpectedDisbursementDate(), periodNumber, loanApplicationTerms + .getPrincipal().minus(totalCumulativePrincipal)); + } PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, @@ -626,9 +638,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalCumulativePrincipal = totalCumulativePrincipal.plus(unprocessed.plus(addToPrinciapal)); reducePrincipal = reducePrincipal.plus(unprocessed); - reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); principalForThisPeriod = principalForThisPeriod.plus(unprocessed.plus(addToPrinciapal)); principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrinciapal)); + BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount(); + reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, + totalCumulativePrincipal.plus(principalForThisPeriod.minus(principalProcessed)), periodNumber + 1, mc); + if (fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) { + isEmiAmountChanged = true; + } + } } } @@ -717,6 +735,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding); } + private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + LocalDate actualRepaymentDate, int periodNumber, Money outstandingBalance) { + boolean isAmountChanged = false; + if (loanApplicationTerms.getActualFixedEmiAmount() == null && loanApplicationTerms.getInterestMethod().isDecliningBalnce() + && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { + LocalDate nextPeriodDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, + false); + if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) { + periodNumber = loanApplicationTerms.getPrincipalGrace() + 1; + } + Money emiAmount = loanApplicationTerms.pmtForInstallment(this.paymentPeriodsInOneYearCalculator, + Days.daysBetween(actualRepaymentDate, nextPeriodDate).getDays(), outstandingBalance, periodNumber, mc); + loanApplicationTerms.setFixedEmiAmount(emiAmount.getAmount()); + isAmountChanged = true; + } + return isAmountChanged; + } + private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, final LoanTransaction transaction) { Money arrears = transaction.getPrincipalPortion(currency); @@ -887,15 +923,25 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener /** * method applies early payment strategy as per the configurations provided + * + * @param totalCumulativePrincipal + * TODO + * @param periodNumber + * TODO + * @param mc + * TODO */ - private Money applyEarlyPaymentStrategy(final LoanApplicationTerms loanApplicationTerms, Money reducePrincipal) { + private Money applyEarlyPaymentStrategy(final LoanApplicationTerms loanApplicationTerms, Money reducePrincipal, + final Money totalCumulativePrincipal, int periodNumber, final MathContext mc) { if (reducePrincipal.isGreaterThanZero()) { switch (loanApplicationTerms.getRescheduleStrategyMethod()) { case REDUCE_EMI_AMOUNT: // in this case emi amount will be reduced but number of // installments won't change - if (!loanApplicationTerms.isMultiDisburseLoan()) { + if (loanApplicationTerms.getActualFixedEmiAmount() == null) { loanApplicationTerms.setFixedEmiAmount(null); + updateFixedInstallmentAmount(mc, loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), + periodNumber, loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal)); } reducePrincipal = reducePrincipal.zero(); break; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 2bc7c940e..c70437be4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -458,14 +458,7 @@ public final class LoanApplicationTerms { case DECLINING_BALANCE: switch (this.amortizationMethod) { case EQUAL_INSTALLMENTS: - // equal installments - final int periodsElapsed = periodNumber - 1; - // with periodic interest for default month and year for - // equal installment - final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, daysInPeriod, - DaysInMonthType.DAYS_30, DaysInYearType.DAYS_365); - Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod( - periodicInterestRateForRepaymentPeriod, outstandingBalance, periodsElapsed); + Money totalPmtForThisInstallment = pmtForInstallment(calculator, daysInPeriod, outstandingBalance, periodNumber, mc); principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment, interestForThisInstallment); break; @@ -483,6 +476,19 @@ public final class LoanApplicationTerms { return principalForInstallment; } + public Money pmtForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final int daysInPeriod, + final Money outstandingBalance, final int periodNumber, final MathContext mc) { + // equal installments + final int periodsElapsed = periodNumber - 1; + // with periodic interest for default month and year for + // equal installment + final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, daysInPeriod, + DaysInMonthType.DAYS_30, DaysInYearType.DAYS_365); + Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(periodicInterestRateForRepaymentPeriod, + outstandingBalance, periodsElapsed); + return totalPmtForThisInstallment; + } + public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc, final Money cumulatingInterestPaymentDueToGrace, final int daysInPeriodApplicableForInterest, final Money outstandingBalance) { @@ -1204,4 +1210,9 @@ public final class LoanApplicationTerms { public RecalculationFrequencyType getCompoundingFrequencyType() { return this.compoundingFrequencyType; } + + + public BigDecimal getActualFixedEmiAmount() { + return this.actualFixedEmiAmount; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestMethod.java index a202c84a9..4226dd115 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestMethod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestMethod.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.loanproduct.domain; + public enum InterestMethod { DECLINING_BALANCE(0, "interestType.declining.balance"), FLAT(1, "interestType.flat"), INVALID(2, "interestType.invalid"); @@ -40,4 +41,8 @@ public enum InterestMethod { } return repaymentMethod; } + + public boolean isDecliningBalnce() { + return this.value.equals(InterestMethod.DECLINING_BALANCE.getValue()); + } } \ No newline at end of file