diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java index 09420415b..d4c29f4de 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java @@ -4155,6 +4155,84 @@ public class ClientLoanIntegrationTest { } + @Test + public void testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM_INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE() { + testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM( + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, "10006.59"); + } + + @Test + public void testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM_INTEREST_APPLICABLE_STRATEGY_REST_DATE() { + testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM( + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_REST_DATE, "10046.15"); + } + + + private void testLoanScheduleWithInterestRecalculation_FOR_PRE_CLOSE_WITH_MORATORIUM(final String preCloseStrategy, + final String preCloseAmount) { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + + Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -1); + final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, + LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseStrategy, null); + + final Integer loanID = applyForLoanApplicationForInterestRecalculationWithMoratorium(clientID, loanProductID, + LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, new ArrayList(0), "1", null); + + Assert.assertNotNull(loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + List> expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -1); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "0.0", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "80.84", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -1); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "0.0", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "80.84", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); + String prepayAmount = String.valueOf(prepayDetail.get("amount")); + validateNumberForEqualWithMsg("verify pre-close amount", preCloseAmount, prepayAmount); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + String LOAN_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime()); + this.loanTransactionHelper.makeRepayment(LOAN_REPAYMENT_DATE, new Float(prepayAmount), loanID); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); + } + private void addRepaymentValues(List> expectedvalues, Calendar todaysDate, int addPeriod, boolean isAddDays, String principalDue, String interestDue, String feeChargesDue, String penaltyChargesDue) { Map values = new HashMap<>(3); @@ -4247,9 +4325,24 @@ public class ClientLoanIntegrationTest { compoundingStartDate, repaymentStrategy, charges); } + private Integer applyForLoanApplicationForInterestRecalculationWithMoratorium(final Integer clientID, final Integer loanProductID, + final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List charges, + final String graceOnInterestPayment, final String graceOnPrincipalPayment) { + final String compoundingStartDate = null; + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate, + compoundingStartDate, repaymentStrategy, charges, graceOnInterestPayment, graceOnPrincipalPayment); + } + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy, final List charges) { + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate, + compoundingStartDate, repaymentStrategy, charges, null, null); + } + + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, + final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy, + final List charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment) { System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); final String loanApplicationJSON = new LoanApplicationTestBuilder() // .withPrincipal("10000.00") // @@ -4267,6 +4360,8 @@ public class ClientLoanIntegrationTest { .withRestFrequencyDate(restStartDate)// .withCompoundingFrequencyDate(compoundingStartDate)// .withwithRepaymentStrategy(repaymentStrategy) // + .withPrincipalGrace(graceOnPrincipalPayment) // + .withInterestGrace(graceOnInterestPayment)// .withCharges(charges)// .build(clientID.toString(), loanProductID.toString(), null); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); 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 a65582488..bc74d5c40 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 @@ -483,7 +483,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // update cumulative fields for principal & interest Money interestForThisinstallment = principalInterestForThisPeriod.interest(); - + Money lastTotalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace; totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); Money principalForThisPeriod = principalInterestForThisPeriod.principal(); @@ -576,7 +576,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, - totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, + lastTotalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, periodStartDateApplicableForInterest, calculateTill, @@ -595,6 +595,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener detail.getTransactionDate(), principalForThisPeriod, outstandingBalance, interestForThisinstallment, feeChargesForInstallment, penaltyChargesForInstallment, totalDue, false); + totalOutstandingInterestPaymentDueToGrace = interestTillDate.interestPaymentDueToGrace(); } } @@ -651,6 +652,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener compoundingDateVariations.clear(); } + // this condition is to add the interest from grace period if not + // already applied. + if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) { + LoanScheduleModelPeriod installment = ((List) periods).get(periods.size() - 1); + installment.addInterestAmount(totalOutstandingInterestPaymentDueToGrace); + totalRepaymentExpected = totalRepaymentExpected.add(totalOutstandingInterestPaymentDueToGrace.getAmount()); + totalCumulativeInterest = totalCumulativeInterest.plus(totalOutstandingInterestPaymentDueToGrace); + totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.zero(); + } + // 7. determine fees and penalties for charges which depends on total // loan interest for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index 007c67ac9..a3bcec953 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -77,11 +77,15 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS interestForDays, balanceForInterestCalculation); if (loanApplicationTerms.getInterestCalculationPeriodMethod().isDaily()) { interestForThisInstallment = interestForThisInstallment.plus(result.interest()); + cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); } else { interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays( daysForInterestInFullPeriod, result.interest().getAmount(), interestForDays)); + cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays( + daysForInterestInFullPeriod, result.interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace) + .getAmount(), interestForDays)); } - cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); + interestStartDate = principal.getKey(); } Money compoundFee = totalCumulativePrincipal.zero(); @@ -117,22 +121,26 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS balanceForInterestCalculation); if (loanApplicationTerms.getInterestCalculationPeriodMethod().isDaily()) { interestForThisInstallment = interestForThisInstallment.plus(result.interest()); + cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); } else { interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, result .interest().getAmount(), interestForDays)); + cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays( + daysForInterestInFullPeriod, result.interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace) + .getAmount(), interestForDays)); } Money interestForPeriod = interestForThisInstallment; if (interestForPeriod.isGreaterThanZero()) { interestForPeriod = interestForPeriod.minus(cumulatingInterestPaymentDueToGrace); } else { - interestForPeriod = result.interestPaymentDueToGrace().minus(cumulatingInterestPaymentDueToGrace); + interestForPeriod = cumulatingInterestDueToGrace.minus(cumulatingInterestPaymentDueToGrace); } Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, interestForPeriod); // update cumulative fields for principal & interest - final Money interestBroughtFowardDueToGrace = result.interestPaymentDueToGrace(); + final Money interestBroughtFowardDueToGrace = cumulatingInterestDueToGrace; final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment); // adjust if needed