MIFOSX-2006 : fixed emi amount for tranche loan

This commit is contained in:
pramod
2015-06-16 22:57:56 +05:30
parent 95f8bb8bca
commit 5a4dbe3239
3 changed files with 86 additions and 24 deletions
@@ -113,6 +113,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
int instalmentNumber = 1; int instalmentNumber = 1;
Money outstandingBalance = principalDisbursed; Money outstandingBalance = principalDisbursed;
// Set fixed EMI Amount
updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance);
// disbursement map for tranche details(will added to outstanding // disbursement map for tranche details(will added to outstanding
// balance as per the start date) // balance as per the start date)
final Map<LocalDate, Money> disburseDetailMap = new HashMap<>(); final Map<LocalDate, Money> disburseDetailMap = new HashMap<>();
@@ -160,9 +164,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
boolean isNextRepaymentAvailable = true; boolean isNextRepaymentAvailable = true;
Boolean extendTermForDailyRepayments = false; Boolean extendTermForDailyRepayments = false;
if(holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true && if (holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true
loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS && && loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS
loanApplicationTerms.getRepaymentEvery() == 1){ && loanApplicationTerms.getRepaymentEvery() == 1) {
holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue()); holidayDetailDTO.getWorkingDays().setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue());
extendTermForDailyRepayments = true; extendTermForDailyRepayments = true;
} }
@@ -180,7 +184,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
compoundingDateVariations.put(periodStartDate, new TreeMap<>(compoundingMap)); compoundingDateVariations.put(periodStartDate, new TreeMap<>(compoundingMap));
} }
if(extendTermForDailyRepayments){ if (extendTermForDailyRepayments) {
actualRepaymentDate = scheduledDueDate; actualRepaymentDate = scheduledDueDate;
} }
@@ -367,7 +371,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// method applies early payment strategy // method applies early payment strategy
reducePrincipal = reducePrincipal.plus(unprocessed); reducePrincipal = reducePrincipal.plus(unprocessed);
reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal,
totalCumulativePrincipal, periodNumber, mc);
} }
// identify late payments and add compounding // identify late payments and add compounding
// details to // details to
@@ -501,10 +506,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
Money principalForThisPeriod = principalInterestForThisPeriod.principal(); Money principalForThisPeriod = principalInterestForThisPeriod.principal();
if (principalForThisPeriod.isZero()) {
loanApplicationTerms.resetFixedEmiAmount();
}
// applies early payments on principal portion // applies early payments on principal portion
if (principalForThisPeriod.isGreaterThan(reducePrincipal)) { if (principalForThisPeriod.isGreaterThan(reducePrincipal)) {
principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal); principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal);
@@ -518,9 +519,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// reducePrincipal.plus(unprocessed); // reducePrincipal.plus(unprocessed);
Money reducedBalance = earlyPaidAmount; Money reducedBalance = earlyPaidAmount;
earlyPaidAmount = earlyPaidAmount.minus(principalForThisPeriod); earlyPaidAmount = earlyPaidAmount.minus(principalForThisPeriod);
boolean isEmiAmountChanged = false;
if (earlyPaidAmount.isGreaterThanZero()) { if (earlyPaidAmount.isGreaterThanZero()) {
reducePrincipal = reducePrincipal.plus(earlyPaidAmount); 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); principalForThisPeriod = principalForThisPeriod.plus(earlyPaidAmount);
} }
@@ -587,6 +594,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
compoundingMap.clear(); compoundingMap.clear();
compoundingMap.putAll(compoundingDateVariations.get(periodStartDateApplicableForInterest)); compoundingMap.putAll(compoundingDateVariations.get(periodStartDateApplicableForInterest));
} }
if (isEmiAmountChanged) {
updateFixedInstallmentAmount(mc, loanApplicationTerms,
loanApplicationTerms.getExpectedDisbursementDate(), periodNumber, loanApplicationTerms
.getPrincipal().minus(totalCumulativePrincipal));
}
PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod( PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
@@ -626,9 +638,15 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalCumulativePrincipal = totalCumulativePrincipal.plus(unprocessed.plus(addToPrinciapal)); totalCumulativePrincipal = totalCumulativePrincipal.plus(unprocessed.plus(addToPrinciapal));
reducePrincipal = reducePrincipal.plus(unprocessed); reducePrincipal = reducePrincipal.plus(unprocessed);
reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal);
principalForThisPeriod = principalForThisPeriod.plus(unprocessed.plus(addToPrinciapal)); principalForThisPeriod = principalForThisPeriod.plus(unprocessed.plus(addToPrinciapal));
principalProcessed = principalProcessed.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); 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, private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency,
final LoanTransaction transaction) { final LoanTransaction transaction) {
Money arrears = transaction.getPrincipalPortion(currency); 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 * 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()) { if (reducePrincipal.isGreaterThanZero()) {
switch (loanApplicationTerms.getRescheduleStrategyMethod()) { switch (loanApplicationTerms.getRescheduleStrategyMethod()) {
case REDUCE_EMI_AMOUNT: case REDUCE_EMI_AMOUNT:
// in this case emi amount will be reduced but number of // in this case emi amount will be reduced but number of
// installments won't change // installments won't change
if (!loanApplicationTerms.isMultiDisburseLoan()) { if (loanApplicationTerms.getActualFixedEmiAmount() == null) {
loanApplicationTerms.setFixedEmiAmount(null); loanApplicationTerms.setFixedEmiAmount(null);
updateFixedInstallmentAmount(mc, loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
periodNumber, loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal));
} }
reducePrincipal = reducePrincipal.zero(); reducePrincipal = reducePrincipal.zero();
break; break;
@@ -458,14 +458,7 @@ public final class LoanApplicationTerms {
case DECLINING_BALANCE: case DECLINING_BALANCE:
switch (this.amortizationMethod) { switch (this.amortizationMethod) {
case EQUAL_INSTALLMENTS: case EQUAL_INSTALLMENTS:
// equal installments Money totalPmtForThisInstallment = pmtForInstallment(calculator, daysInPeriod, outstandingBalance, periodNumber, mc);
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);
principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment, principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment,
interestForThisInstallment); interestForThisInstallment);
break; break;
@@ -483,6 +476,19 @@ public final class LoanApplicationTerms {
return principalForInstallment; 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, public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc, final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
final Money cumulatingInterestPaymentDueToGrace, final int daysInPeriodApplicableForInterest, final Money outstandingBalance) { final Money cumulatingInterestPaymentDueToGrace, final int daysInPeriodApplicableForInterest, final Money outstandingBalance) {
@@ -1204,4 +1210,9 @@ public final class LoanApplicationTerms {
public RecalculationFrequencyType getCompoundingFrequencyType() { public RecalculationFrequencyType getCompoundingFrequencyType() {
return this.compoundingFrequencyType; return this.compoundingFrequencyType;
} }
public BigDecimal getActualFixedEmiAmount() {
return this.actualFixedEmiAmount;
}
} }
@@ -5,6 +5,7 @@
*/ */
package org.mifosplatform.portfolio.loanproduct.domain; package org.mifosplatform.portfolio.loanproduct.domain;
public enum InterestMethod { public enum InterestMethod {
DECLINING_BALANCE(0, "interestType.declining.balance"), FLAT(1, "interestType.flat"), INVALID(2, "interestType.invalid"); DECLINING_BALANCE(0, "interestType.declining.balance"), FLAT(1, "interestType.flat"), INVALID(2, "interestType.invalid");
@@ -40,4 +41,8 @@ public enum InterestMethod {
} }
return repaymentMethod; return repaymentMethod;
} }
public boolean isDecliningBalnce() {
return this.value.equals(InterestMethod.DECLINING_BALANCE.getValue());
}
} }