From 45cd2e7602b0ad97a35182329408e737b8854d1f Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Fri, 12 Sep 2014 19:24:46 +0530 Subject: [PATCH] MIFOSX-1517 : corrected recalculation for tranche loans --- ...d_loan_running_balance_of_transactions.sql | 2 +- .../loanaccount/api/LoansApiResource.java | 4 +- .../portfolio/loanaccount/domain/Loan.java | 14 +- .../domain/LoanDisbursementDetails.java | 19 +- .../domain/AbstractLoanScheduleGenerator.java | 239 +++++++++++++----- .../service/LoanScheduleAssembler.java | 2 +- .../service/LoanReadPlatformService.java | 2 +- .../service/LoanReadPlatformServiceImpl.java | 39 ++- .../domain/AmortizationMethod.java | 2 +- 9 files changed, 229 insertions(+), 94 deletions(-) diff --git a/mifosng-db/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql b/mifosng-db/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql index 4b9cdcfd4..cfb2e143b 100755 --- a/mifosng-db/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql +++ b/mifosng-db/migrations/core_db/V197__updated_loan_running_balance_of_transactions.sql @@ -8,7 +8,7 @@ CREATE TABLE `m_loan_transaction_temp` ( INSERT INTO m_loan_transaction_temp(`id`,`loan_id`,`transaction_date`,`amount`) select lt.id, lt.loan_id,lt.transaction_date,if(lt.transaction_type_enum = 1 , IFNULL(lt.amount,0),IFNULL(-lt.principal_portion_derived,0)) from m_loan_transaction lt where lt.is_reversed=0; -UPDATE m_loan_transaction lt SET lt.outstanding_loan_balance_derived = (select sum(ltt.amount) from m_loan_transaction_temp ltt where ltt.id <= lt.id and ltt.transaction_date <= lt.transaction_date and ltt.loan_id = lt.loan_id) where lt.transaction_type_enum != 10 and lt.is_reversed = 0; +UPDATE m_loan_transaction lt SET lt.outstanding_loan_balance_derived = (select sum(ltt.amount) from m_loan_transaction_temp ltt where ((ltt.transaction_date = lt.transaction_date and ltt.id <= lt.id) or ltt.transaction_date < lt.transaction_date) and ltt.loan_id = lt.loan_id) where lt.transaction_type_enum != 10 and lt.is_reversed = 0; DROP TABLE `m_loan_transaction_temp`; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 9fd5ceff9..1ddb083c7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -354,9 +354,9 @@ public class LoansApiResource { final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData(); repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData, - disbursementData); + disbursementData, loanBasicDetails.isInterestRecalculationEnabled()); - if (associationParameters.contains("futureSchedule")) { + if (associationParameters.contains("futureSchedule") && loanBasicDetails.isInterestRecalculationEnabled()) { mandatoryResponseParameters.add("futureSchedule"); this.calculationPlatformService.updateFutureSchedule(repaymentSchedule, loanId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 34a1e6b77..40d1b81fe 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -1817,7 +1817,7 @@ public class Loan extends AbstractPersistable { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() - && fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) { + && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) { LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); @@ -1960,6 +1960,18 @@ public class Loan extends AbstractPersistable { return details; } + private boolean isDisbursementMissed() { + boolean isDisbursementMissed = false; + for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) { + if (disbursementDetail.actualDisbursementDate() == null + && LocalDate.now().isAfter(disbursementDetail.expectedDisbursementDateAsLocalDate())) { + isDisbursementMissed = true; + break; + } + } + return isDisbursementMissed; + } + private BigDecimal getDisbursedAmount() { BigDecimal principal = BigDecimal.ZERO; for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java index 99b387168..10d5e6d7e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java @@ -24,6 +24,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_loan_disbursement_detail") public class LoanDisbursementDetails extends AbstractPersistable { + @SuppressWarnings("unused") @ManyToOne @JoinColumn(name = "loan_id", nullable = false) private Loan loan; @@ -77,6 +78,14 @@ public class LoanDisbursementDetails extends AbstractPersistable { return this.expectedDisbursementDate; } + public LocalDate expectedDisbursementDateAsLocalDate() { + LocalDate expectedDisburseDate = null; + if (this.expectedDisbursementDate != null) { + expectedDisburseDate = new LocalDate(this.expectedDisbursementDate); + } + return expectedDisburseDate; + } + public Date actualDisbursementDate() { return this.actualDisbursementDate; } @@ -102,14 +111,10 @@ public class LoanDisbursementDetails extends AbstractPersistable { } public DisbursementData toData() { - LocalDate expectedDisburseDate = null; + LocalDate expectedDisburseDate = expectedDisbursementDateAsLocalDate(); LocalDate actualDisburseDate = null; - if (this.expectedDisbursementDate != null) { - expectedDisburseDate = new LocalDate(this.expectedDisbursementDate); - actualDisburseDate = expectedDisburseDate; - } - if (getDisbursementDate() != null) { - actualDisburseDate = new LocalDate(getDisbursementDate()); + if (this.actualDisbursementDate != null) { + actualDisburseDate = new LocalDate(this.actualDisbursementDate); } return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, this.approvedPrincipal); } 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 0f18580fe..6576f53c8 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 @@ -158,20 +158,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); - if (loanApplicationTerms.isMultiDisburseLoan()) { - loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate); - BigDecimal disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, scheduledDueDate, periods, - BigDecimal.ZERO); - principalDisbursed = principalDisbursed.plus(disburseAmt); - loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt)); - outstandingBalance = outstandingBalance.plus(disburseAmt); - if (loanApplicationTerms.getMaxOutstandingBalance() != null - && outstandingBalance.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) { - String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance(); - throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance() - .getAmount(), disburseAmt); - } - } int daysInPeriodApplicableForInterest = daysInPeriod; if (periodStartDate.isBefore(idealDisbursementDate)) { @@ -203,8 +189,37 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDateAsPerFrequency, loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); + BigDecimal interestToBeAdded = BigDecimal.ZERO; + BigDecimal disburseAmt = BigDecimal.ZERO; + if (loanApplicationTerms.isMultiDisburseLoan()) { + loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate); + final Collection disbursementDatas = new ArrayList<>(); + disburseAmt = disbursementForPeriod(loanApplicationTerms, periodStartDate, scheduledDueDate, disbursementDatas, + diffAmt != null); + principalDisbursed = principalDisbursed.plus(disburseAmt); + loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseAmt)); + outstandingBalance = outstandingBalance.plus(disburseAmt); + if (loanApplicationTerms.getMaxOutstandingBalance() != null + && outstandingBalance.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) { + String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance(); + throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance() + .getAmount(), disburseAmt); + } + for (DisbursementData disbursementData : disbursementDatas) { + Money disbursedAmt = Money.of(currency, disbursementData.amount()); + final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( + disbursementData.disbursementDate(), disbursedAmt, chargesDueAtTimeOfDisbursement); + periods.add(disbursementPeriod); + if (disbursementData.disbursementDate().isAfter(periodStartDateApplicableForInterest)) { + interestToBeAdded = interestToBeAdded.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, periodNumber, + totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableInFullInstallment, + interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, disbursementData.disbursementDate(), + scheduledDueDate)); + } + } + } - Money balanceForcalculation = outstandingBalance; + Money balanceForcalculation = outstandingBalance.minus(disburseAmt); // reduce principal is the early payment, will processed as per the // reschedule strategy if (reducePrincipal.isGreaterThanZero()) { @@ -249,53 +264,36 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // update cumulative fields for principal & interest Money interestForThisinstallment = principalInterestForThisPeriod.interest(); + if (daysCalcForInstallmentNumber == periodNumber) { interestForThisinstallment = interestForThisinstallment.zero().plus( calculateInterestForDays(daysInPeriodApplicableInFullInstallment, interestForThisinstallment.getAmount(), daysInPeriodApplicableForInterest)); } - Money principalForThisPeriod = principalDisbursed.zero(); - Money outstandingForThePeriod = outstandingBalance; - // Exclude principal portion for interest only installments - if (!recalculatedInterestComponent) { - principalForThisPeriod = principalInterestForThisPeriod.principal(); - if (daysCalcForInstallmentNumber == periodNumber && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { - principalForThisPeriod = principalForThisPeriod.plus(principalInterestForThisPeriod.interest()).minus( - interestForThisinstallment); - } - totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod); - - // 6. update outstandingLoanBlance using correct 'principalDue' - outstandingBalance = outstandingBalance.minus(principalForThisPeriod); - } - - totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); - Money actualOutstandingbalance = outstandingBalance; + if (interestForThisinstallment.isGreaterThanZero()) { + interestForThisinstallment = interestForThisinstallment.plus(interestToBeAdded); + } else if (principalInterestForThisPeriod.interestPaymentDueToGrace().isGreaterThanZero()) { + totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.minus(interestToBeAdded); + } + + Money principalForThisPeriod = principalDisbursed.zero(); + Money outstandingForThePeriod = outstandingBalance; + Money reducePrincipalForCurrentInstallment = reducePrincipal; + + totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); Money feeChargesForInstallment = principalDisbursed.zero(); Money penaltyChargesForInstallment = principalDisbursed.zero(); - if (principalForThisPeriod.isGreaterThan(reducePrincipal)) { - principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal); - reducePrincipal = reducePrincipal.zero(); - } else { - reducePrincipal = reducePrincipal.minus(principalForThisPeriod); - principalForThisPeriod = principalForThisPeriod.zero(); - } - - if (periodNumber == 1) { - fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment); - } - Money extraPrincipal = reducePrincipal.zero(); + Money canBeReducedFromReducePrincipal = reducePrincipal.zero(); // this block is to identify interest based on late/early payment if (diffAmt != null && !diffAmt.isEmpty()) { BigDecimal interestDueToLatePayment = BigDecimal.ZERO; BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO; - boolean isperiodPrincipalReduced = false; for (RecalculationDetail detail : diffAmt) { if (!detail.isInterestompound()) { // will increase the principal portion and reduces @@ -307,9 +305,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener reducePrincipal = reducePrincipal.plus(detail.getAmount()); extraPrincipal = extraPrincipal.plus(detail.getAmount()); int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays(); - if (diffDays > 0 && !isperiodPrincipalReduced) { - reducePrincipal = reducePrincipal.minus(principalForThisPeriod); - isperiodPrincipalReduced = true; + if (diffDays > 0) { + canBeReducedFromReducePrincipal = canBeReducedFromReducePrincipal.plus(detail.getAmount()); } LocalDate startDate = detail.getStartDate(); @@ -363,16 +360,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - if (reducePrincipal.isLessThanZero()) { - reducePrincipal = reducePrincipal.zero(); - } else { - Money actualOutstanding = outstandingBalance.minus(reducePrincipal); - principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod); - if (actualOutstanding.isLessThanZero()) { - principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); - } - } - if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) { totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.plus(interestDueToLatePayment) .minus(interestReducedDueToEarlyPayment); @@ -384,7 +371,60 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } + + // Exclude principal portion for interest only installments + if (!recalculatedInterestComponent) { + principalForThisPeriod = principalInterestForThisPeriod.principal(); + if (/* daysCalcForInstallmentNumber == periodNumber && */loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { + principalForThisPeriod = principalForThisPeriod.plus(principalInterestForThisPeriod.interest()).minus( + interestForThisinstallment); + principalForThisPeriod = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisPeriod, + totalCumulativePrincipal.plus(principalForThisPeriod), periodNumber); + } + totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod); + + // 6. update outstandingLoanBlance using correct 'principalDue' + outstandingBalance = outstandingBalance.minus(principalForThisPeriod); + if (outstandingBalance.isLessThanZero()) { + principalForThisPeriod = principalForThisPeriod.plus(outstandingBalance); + outstandingBalance = outstandingBalance.zero(); + } + } + Money actualOutstandingbalance = outstandingBalance; + if (diffAmt != null && !diffAmt.isEmpty()) { + reducePrincipal = reducePrincipal.minus(canBeReducedFromReducePrincipal); + Money pricipalAfterCurrentPrincipal = canBeReducedFromReducePrincipal.minus(principalForThisPeriod); + if (pricipalAfterCurrentPrincipal.isGreaterThanZero()) { + reducePrincipal = reducePrincipal.plus(pricipalAfterCurrentPrincipal); + } + + if (reducePrincipal.isLessThanZero()) { + reducePrincipal = reducePrincipal.zero(); + } else { + Money actualOutstanding = outstandingBalance.minus(reducePrincipal); + principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod); + if (actualOutstanding.isLessThanZero()) { + principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); + } + } + } + + if (principalForThisPeriod.isGreaterThan(reducePrincipalForCurrentInstallment)) { + principalForThisPeriod = principalForThisPeriod.minus(reducePrincipalForCurrentInstallment); + reducePrincipal = reducePrincipal.minus(reducePrincipalForCurrentInstallment); + } else { + reducePrincipal = reducePrincipal.minus(principalForThisPeriod); + principalForThisPeriod = principalForThisPeriod.zero(); + } + + if (periodNumber == 1) { + fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment); + } + actualOutstandingbalance = actualOutstandingbalance.minus(reducePrincipal); + if (actualOutstandingbalance.isLessThanZero()) { + actualOutstandingbalance = actualOutstandingbalance.zero(); + } // 8. sum up real totalInstallmentDue from components final Money totalInstallmentDue = principalForThisPeriod// @@ -415,7 +455,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener instalmentNumber++; } - if (principalDisbursed.isNotEqualTo(expectedPrincipalDisburse)) { + if (principalDisbursed.isNotEqualTo(expectedPrincipalDisburse) && diffAmt == null) { final String errorMsg = "One of the Disbursement date is not falling on Loan Schedule"; throw new MultiDisbursementDisbursementDateException(errorMsg); } @@ -870,17 +910,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } private BigDecimal disbursementForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate startDate, LocalDate endDate, - final Collection periods, final BigDecimal chargesDueAtTimeOfDisbursement) { + final Collection disbursementDatas, boolean excludePastUndisbursed) { BigDecimal principal = BigDecimal.ZERO; - MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { - if (disbursementData.isDueForDisbursement(startDate, endDate)) { - final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement( - disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount()), chargesDueAtTimeOfDisbursement); - periods.add(disbursementPeriod); + if (!excludePastUndisbursed + || (excludePastUndisbursed && (disbursementData.isDisbursed() || !disbursementData.disbursementDate().isBefore( + LocalDate.now())))) { + if (disbursementData.isDueForDisbursement(startDate, endDate)) { + disbursementDatas.add(disbursementData); + principal = principal.add(disbursementData.amount()); + } + } + } + return principal; + } + + private BigDecimal disbursementAfterPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate endDate, + LocalDate lastinstallmentDueDate) { + BigDecimal principal = BigDecimal.ZERO; + for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { + + if (disbursementData.disbursementDate().isAfter(endDate) && lastinstallmentDueDate.isAfter(disbursementData.disbursementDate())) { principal = principal.add(disbursementData.amount()); } } + return principal; } @@ -1413,6 +1467,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money inerestForCurrentInstallment = Money.zero(currency); Money principalOutstanding = Money.zero(currency); Integer periodNumber = 0; + LocalDate lastDueDate = loanApplicationTerms.getExpectedDisbursementDate(); for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { if (currentInstallment.isNotFullyPaidOff()) { if (!currentInstallment.getDueDate().isAfter(onDate)) { @@ -1443,6 +1498,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } + if (!currentInstallment.isRecalculatedInterestComponent() && currentInstallment.getDueDate().isAfter(lastDueDate)) { + lastDueDate = currentInstallment.getDueDate(); + } } if (interestChargedFromLocalDate != null && calculateInterestFrom.isBefore(interestChargedFromLocalDate)) { calculateInterestFrom = interestChargedFromLocalDate; @@ -1463,6 +1521,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (useDailyInterest) { interestForInstallment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, principalOutstanding, calculateInterestFrom, repaymentDueDate); + interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment)); + interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days)); } else { LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( @@ -1485,13 +1545,58 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); + if (loanApplicationTerms.isMultiDisburseLoan()) { + BigDecimal reducePrincipal = disbursementAfterPeriod(loanApplicationTerms, repaymentDueDate, lastDueDate); + principalOutstanding = principalOutstanding.minus(reducePrincipal); + } + PrincipalInterest principalInterest = loanApplicationTerms.calculateTotalInterestForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, totalOutstandingInterestPaymentDueToGrace.zero(), daysInPeriodApplicableForInterest, principalOutstanding); interestForInstallment = principalInterest.interest().getAmount(); + Money alteredPrincipal = principalOutstanding; + Money reducePricipal = principalOutstanding.zero(); + BigDecimal interestToBeReduced = BigDecimal.ZERO; + BigDecimal interestForExtraPrincipal = BigDecimal.ZERO; + if (loanApplicationTerms.isMultiDisburseLoan()) { + final Collection disbursementDatas = new ArrayList<>(); + BigDecimal addedPrincipal = disbursementForPeriod(loanApplicationTerms, calculateInterestFrom, repaymentDueDate, + disbursementDatas, true); + alteredPrincipal = alteredPrincipal.minus(addedPrincipal); + for (DisbursementData disbursementData : disbursementDatas) { + Money disbursedAmt = Money.of(currency, disbursementData.amount()); + if (disbursementData.disbursementDate().isAfter(calculateInterestFrom)) { + LocalDate calculateTill = disbursementData.disbursementDate(); + interestToBeReduced = interestToBeReduced.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, + periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest, + interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, calculateInterestFrom, calculateTill)); + + if (onDate.isAfter(calculateTill)) { + interestForExtraPrincipal = interestForExtraPrincipal.add(calculateInterestForSpecificDays(mc, + loanApplicationTerms, periodNumber, totalOutstandingInterestPaymentDueToGrace, + daysInPeriodApplicableForInterest, interestCalculationGraceOnRepaymentPeriodFraction, disbursedAmt, + calculateTill, onDate)); + } else { + reducePricipal = reducePricipal.plus(disbursedAmt); + } + } + } + interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment).plus(interestToBeReduced)); + BigDecimal interestForCloseDate = BigDecimal.ZERO; + if (alteredPrincipal.isNotEqualTo(principalOutstanding)) { + interestForCloseDate = interestForExtraPrincipal; + interestForCloseDate = interestForCloseDate.add(calculateInterestForSpecificDays(mc, loanApplicationTerms, + periodNumber, totalOutstandingInterestPaymentDueToGrace, daysInPeriodApplicableForInterest, + interestCalculationGraceOnRepaymentPeriodFraction, alteredPrincipal, calculateInterestFrom, onDate)); + } else { + interestForCloseDate = interestForCloseDate.add(BigDecimal.valueOf(calculateInterestForDays( + daysInPeriodApplicableForInterest, interestForInstallment, days))); + } + interest = interest.plus(interestForCloseDate); + principalOutstanding = principalOutstanding.minus(reducePricipal); + } + } - interest = interest.plus(inerestForCurrentInstallment.minus(interestForInstallment)); - interest = interest.plus(calculateInterestForDays(daysInPeriodApplicableForInterest, interestForInstallment, days)); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 4428ce0fb..c58068442 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -397,7 +397,7 @@ public class LoanScheduleAssembler { final WorkingDays workingDays = this.workingDaysRepository.findOne(); CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); - LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate); + LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate.minusDays(1)); if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java index 533a611e2..8e0c6e23c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -26,7 +26,7 @@ public interface LoanReadPlatformService { LoanAccountData retrieveOne(Long loanId); LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData, - Collection disbursementData); + Collection disbursementData, boolean isInterestRecalculationEnabled); Collection retrieveLoanTransactions(Long loanId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 64e8bf19d..ce3223ad3 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -177,13 +177,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, - final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection disbursementData) { + final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection disbursementData, + boolean isInterestRecalculationEnabled) { try { this.context.authenticatedUser(); final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor( - repaymentScheduleRelatedLoanData, disbursementData); + repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled); final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment"; return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId }); @@ -862,15 +863,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final Collection disbursementData; private LocalDate lastDueDate; private BigDecimal outstandingLoanPrincipalBalance; + private boolean excludePastUndisbursed; public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, - Collection disbursementData) { + Collection disbursementData, boolean isInterestRecalculationEnabled) { this.currency = repaymentScheduleRelatedLoanData.getCurrency(); this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); this.lastDueDate = this.disbursement.disbursementDate(); this.outstandingLoanPrincipalBalance = this.disbursement.amount(); this.disbursementData = disbursementData; + this.excludePastUndisbursed = isInterestRecalculationEnabled; } public String schema() { @@ -899,6 +902,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { periods.add(disbursementPeriod); totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.amount()).getAmount(); } else { + if (!this.disbursement.isDisbursed()) { + excludePastUndisbursed = false; + } this.outstandingLoanPrincipalBalance = BigDecimal.ZERO; } @@ -930,7 +936,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate"); final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate"); final boolean complete = rs.getBoolean("complete"); - if (disbursementData != null) { BigDecimal principal = BigDecimal.ZERO; for (DisbursementData data : disbursementData) { @@ -939,14 +944,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed()); periods.add(periodData); - } else if (data.isDueForDisbursement(fromDate, dueDate)) { - principal = principal.add(data.amount()); - final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( - data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed()); - periods.add(periodData); + this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount()); + } else if (data.isDueForDisbursement(fromDate, dueDate) + && this.outstandingLoanPrincipalBalance.compareTo(BigDecimal.ZERO) == 1) { + if (!excludePastUndisbursed + || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) { + principal = principal.add(data.amount()); + final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( + data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed()); + periods.add(periodData); + this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount()); + } } } - this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(principal); totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal); } @@ -1588,15 +1598,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("SELECT ml.id FROM m_loan ml "); sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); + sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null "); sqlBuilder.append(" WHERE ml.loan_status_id = ? "); sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 "); sqlBuilder.append(" and ml.is_npa = 0 "); - sqlBuilder.append(" and mr.completed_derived is false "); - sqlBuilder.append(" and mr.duedate < ? "); + sqlBuilder.append(" and (("); + sqlBuilder.append(" mr.completed_derived is false "); + sqlBuilder.append(" and mr.duedate < ? )"); + sqlBuilder.append(" or dd.expected_disburse_date < ? ) "); sqlBuilder.append(" group by ml.id"); try { return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, - new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()) }); + new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()),formatter.print(LocalDate.now()) }); } catch (final EmptyResultDataAccessException e) { return null; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java index 56cb01cd0..534ac43d3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/AmortizationMethod.java @@ -46,6 +46,6 @@ public enum AmortizationMethod { } public boolean isEqualInstallment() { - return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS); + return this.value.equals(AmortizationMethod.EQUAL_INSTALLMENTS.getValue()); } } \ No newline at end of file