You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by al...@apache.org on 2022/11/28 17:41:01 UTC

[fineract] branch 1.7.2 created (now 931946e5f)

This is an automated email from the ASF dual-hosted git repository.

aleks pushed a change to branch 1.7.2
in repository https://gitbox.apache.org/repos/asf/fineract.git


      at 931946e5f FINERACT-1745: Loan Schedule Final Installment calculation issue (#2690)

This branch includes the following new commits:

     new 23fc45c54 FINERACT-1746: Random Installments Generation Issue (#2629)
     new 7b66505cd FINERACT-1781:Interest is wrong for the first instalment. (#2709)
     new 0d7b7e216 FINERACT-1786: Repayment schedule is wrong with late repayment (#2703)
     new 931946e5f FINERACT-1745: Loan Schedule Final Installment calculation issue (#2690)

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[fineract] 04/04: FINERACT-1745: Loan Schedule Final Installment calculation issue (#2690)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch 1.7.2
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 931946e5f0c03b2e329268434ee42e7767753c44
Author: logoutdhaval <10...@users.noreply.github.com>
AuthorDate: Fri Nov 4 20:46:15 2022 +0530

    FINERACT-1745: Loan Schedule Final Installment calculation issue (#2690)
---
 .../domain/AbstractLoanScheduleGenerator.java      |  1 +
 ...liningBalanceInterestLoanScheduleGenerator.java |  3 +-
 .../ClientLoanIntegrationTest.java                 | 80 ++++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |  9 +++
 4 files changed, 91 insertions(+), 2 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index e7c389985..d8d2e54d2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -2313,6 +2313,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                     int period = periodNumber;
                     if (!lastInstallmentDate.isEqual(installment.getDueDate())) {
                         period--;
+                        periodNumber = period;
                     }
                     reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal,
                             loanApplicationTerms, totalCumulativePrincipal, period, mc);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
index c39112577..5a8af0483 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java
@@ -60,7 +60,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
             final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
             @SuppressWarnings("unused") final Money totalCumulativeInterest,
             @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
-            final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
+            final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, int periodNumber, final MathContext mc,
             final TreeMap<LocalDate, Money> principalVariation, final Map<LocalDate, Money> compoundingMap, final LocalDate periodStartDate,
             final LocalDate periodEndDate, final Collection<LoanTermVariationsData> termVariations) {
 
@@ -157,7 +157,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
         // update cumulative fields for principal & interest
         final Money interestBroughtFowardDueToGrace = cumulatingInterestDueToGrace;
         final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
-
         // adjust if needed
         principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
                 totalCumulativePrincipalToDate, periodNumber);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index e9cd83abd..2e39e4172 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -6091,6 +6091,86 @@ public class ClientLoanIntegrationTest {
         WorkingDaysHelper.updateWorkingDays(this.requestSpec, this.responseSpec);
     }
 
+    @Test
+    public void testLoanScheduleWithInterestRecalculationForLastInstallmentAmountNotMoreThanEMI() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        WorkingDaysHelper.updateWorkingDaysWeekDays(this.requestSpec, this.responseSpec);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "43", true);
+        final String loanDisbursementDate = "06 May 2022";
+        String firstRepayment = "27 May 2022";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        String principalAmount = "7800.00";
+        String rateOfInterest = "8.9";
+        String numberOfRepayments = "12";
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12", numberOfRepayments, principalAmount,
+                rateOfInterest);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment, numberOfRepayments,
+                principalAmount, rateOfInterest);
+
+        Assertions.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+
+        this.loanTransactionHelper.makeRepayment("27 May 2022", 1080.0F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("27 June 2022", 1080.0F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("27 July 2022", 1080.0F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("30 August 2022", 1080.0F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("01 September 2022", 17.01F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("01 September 2022", 34.02F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("27 September 2022", 1080.0F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        this.loanTransactionHelper.makeRepayment("27 October 2022", 1080.0F, loanID);
+        Assertions.assertTrue(
+                this.loanTransactionHelper.checkForLastInstallmentLessThanEMI(this.requestSpec, this.responseSpec, loanID, 1080.0F));
+
+        WorkingDaysHelper.updateWorkingDays(this.requestSpec, this.responseSpec);
+    }
+
     private Calendar convertStringDateToCalender(final String stringDate) {
         DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
         Calendar date = Calendar.getInstance();
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 36fbfdca2..fdc8af866 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -943,4 +943,13 @@ public class LoanTransactionHelper {
         return Utils.performServerOutputTemplateLocationGet(requestSpec, responseSpec,
                 "/fineract-provider/api/v1/imports/getOutputTemplateLocation" + "?" + Utils.TENANT_IDENTIFIER, importDocumentId);
     }
+
+    public boolean checkForLastInstallmentLessThanEMI(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final Integer loanID, final Float emiAmount) {
+        ArrayList<HashMap> loanSchedule = this.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        String principal = String.valueOf(loanSchedule.get(loanSchedule.size() - 1).get("principalOriginalDue"));
+        String interest = String.valueOf(loanSchedule.get(loanSchedule.size() - 1).get("interestOriginalDue"));
+        Float amount = Float.parseFloat(principal) + Float.parseFloat(interest);
+        return amount.compareTo(emiAmount) <= 0;
+    }
 }


[fineract] 02/04: FINERACT-1781:Interest is wrong for the first instalment. (#2709)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch 1.7.2
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 7b66505cda06093ef3a3f37c3d0d6d02c95ca66a
Author: logoutdhaval <10...@users.noreply.github.com>
AuthorDate: Fri Nov 4 17:28:22 2022 +0530

    FINERACT-1781:Interest is wrong for the first instalment. (#2709)
---
 .../domain/AbstractLoanScheduleGenerator.java      |   2 +-
 .../ClientLoanIntegrationTest.java                 | 123 +++++++++++++++++----
 2 files changed, 100 insertions(+), 25 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index 4894cc06f..d1ef9b5da 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -1867,7 +1867,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                     && isInterestChargedFromDateSameAsDisbursalDateEnabled) {
                 periodStartDateApplicableForInterest = expectedDisbursementDate;
             } else if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())) {
-                periodStartDateApplicableForInterest = idealDisbursementDate;
+                periodStartDateApplicableForInterest = periodStartDate;
             }
         }
         return periodStartDateApplicableForInterest;
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 22bae64a3..077c88230 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -4608,7 +4608,8 @@ public class ClientLoanIntegrationTest {
     private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy,
             final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
             final String recalculationRestFrequencyType, final String preCloseInterestCalculationStrategy, final Account[] accounts,
-            final String installmentMultipleOf) {
+            final String installmentMultipleOf, final String numberOfRepayments, final String principalAmount,
+            final String rateOfInterest) {
         final String recalculationCompoundingFrequencyType = null;
         final String recalculationCompoundingFrequencyInterval = null;
         final Integer recalculationCompoundingFrequencyOnDayType = null;
@@ -4616,7 +4617,7 @@ public class ClientLoanIntegrationTest {
         return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
                 rescheduleStrategyMethod, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval,
                 preCloseInterestCalculationStrategy, accounts, null, false, recalculationCompoundingFrequencyOnDayType,
-                recalculationCompoundingFrequencyDayOfWeekType, installmentMultipleOf);
+                recalculationCompoundingFrequencyDayOfWeekType, installmentMultipleOf, numberOfRepayments, principalAmount, rateOfInterest);
     }
 
     private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
@@ -4658,12 +4659,13 @@ public class ClientLoanIntegrationTest {
             final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval,
             final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId,
             boolean isArrearsBasedOnOriginalSchedule, final Integer recalculationCompoundingFrequencyOnDayType,
-            final Integer recalculationCompoundingFrequencyDayOfWeekType, final String installmentsMultiplesOf) {
+            final Integer recalculationCompoundingFrequencyDayOfWeekType, final String installmentsMultiplesOf,
+            final String numberOfRepayments, final String principalAmount, final String rateOfInterest) {
         LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
-        LoanProductTestBuilder builder = new LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("12")
-                .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("19.9")
-                .withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy).withAmortizationTypeAsEqualInstallments()
-                .withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
+        LoanProductTestBuilder builder = new LoanProductTestBuilder().withPrincipal(principalAmount)
+                .withNumberOfRepayments(numberOfRepayments).withRepaymentAfterEvery("1").withRepaymentTypeAsMonth()
+                .withinterestRatePerPeriod(rateOfInterest).withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy)
+                .withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
                 .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
                         preCloseInterestCalculationStrategy)
                 .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
@@ -4738,7 +4740,8 @@ public class ClientLoanIntegrationTest {
     }
 
     private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
-            final String disbursementDate, final String repaymentStrategy, final String firstRepaymentDate) {
+            final String disbursementDate, final String repaymentStrategy, final String firstRepaymentDate, final String numberOfRepayments,
+            final String principalAmount, final String rateOfInterest) {
         LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
         final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec);
         Assertions.assertNotNull(collateralId);
@@ -4750,13 +4753,13 @@ public class ClientLoanIntegrationTest {
         addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
 
         final String loanApplicationJSON = new LoanApplicationTestBuilder() //
-                .withPrincipal("10000.00") //
+                .withPrincipal(principalAmount) //
                 .withLoanTermFrequency("12") //
                 .withLoanTermFrequencyAsMonths() //
-                .withNumberOfRepayments("12") //
+                .withNumberOfRepayments(numberOfRepayments) //
                 .withRepaymentEveryAfter("1") //
                 .withLoanTermFrequencyAsMonths() //
-                .withInterestRatePerPeriod("19.9") //
+                .withInterestRatePerPeriod(rateOfInterest) //
                 .withAmortizationTypeAsEqualInstallments() //
                 .withInterestTypeAsDecliningBalance() //
                 .withInterestCalculationPeriodTypeAsDays() //
@@ -5770,15 +5773,20 @@ public class ClientLoanIntegrationTest {
         final String loanDisbursementDate = dateFormat.format(startDate.getTime());
         final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
         ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        String principalAmount = "10000.00";
+        String rateOfInterest = "19.9";
+        String numberOfRepayments = "12";
         final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
                 LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
                 LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
                 LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
                 LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
-                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12", numberOfRepayments, principalAmount,
+                rateOfInterest);
 
         final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
-                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment, numberOfRepayments,
+                principalAmount, rateOfInterest);
 
         Assertions.assertNotNull(loanID);
         HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
@@ -5831,15 +5839,20 @@ public class ClientLoanIntegrationTest {
         final String loanDisbursementDate = dateFormat.format(startDate.getTime());
         final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
         ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        String principalAmount = "10000.00";
+        String rateOfInterest = "19.9";
+        String numberOfRepayments = "12";
         final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
                 LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
                 LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
                 LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
                 LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
-                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12", numberOfRepayments, principalAmount,
+                rateOfInterest);
 
         final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
-                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment, numberOfRepayments,
+                principalAmount, rateOfInterest);
 
         Assertions.assertNotNull(loanID);
         HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
@@ -5869,7 +5882,9 @@ public class ClientLoanIntegrationTest {
         for (int i = 1; i < loanSchedule.size(); i++) {
 
             retrieveDueDate = dateFormat.format(repaymentDate.getTime());
-            amount = (Float) loanSchedule.get(i).get("principalOriginalDue") + (Float) loanSchedule.get(i).get("interestOriginalDue");
+            String principal = String.valueOf(loanSchedule.get(i).get("principalOriginalDue"));
+            String interest = String.valueOf(loanSchedule.get(i).get("interestOriginalDue"));
+            amount = Float.parseFloat(principal) + Float.parseFloat(interest);
             if (currentDate.after(repaymentDate)) {
                 this.loanTransactionHelper.makeRepayment(retrieveDueDate, amount, loanID);
             } else {
@@ -5894,15 +5909,20 @@ public class ClientLoanIntegrationTest {
         String firstRepayment = "01 March 2021";
         final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
         ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        String principalAmount = "10000.00";
+        String rateOfInterest = "19.9";
+        String numberOfRepayments = "12";
         final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
                 LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
                 LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
                 LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
                 LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
-                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12", numberOfRepayments, principalAmount,
+                rateOfInterest);
 
         final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
-                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment, numberOfRepayments,
+                principalAmount, rateOfInterest);
 
         Assertions.assertNotNull(loanID);
         HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
@@ -5923,20 +5943,75 @@ public class ClientLoanIntegrationTest {
         Assertions.assertNotNull(loanSchedule);
 
         List<Map<String, Object>> expectedvalues = new ArrayList<>();
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 March 2021"), 0, false, "388.11", "1831.89", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 March 2021"), 0, false, "126.41", "2093.59", "0.0", "0.0");
         this.loanTransactionHelper.makeRepayment("01 March 2021", 2220.0F, loanID);
 
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 April 2021"), 0, false, "270.55", "1949.45", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 April 2021"), 0, false, "217.47", "2002.53", "0.0", "0.0");
         this.loanTransactionHelper.makeRepayment("01 April 2021", 2220.0F, loanID);
 
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("03 May 2021"), 0, false, "264.31", "1955.69", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("03 May 2021"), 0, false, "198.41", "2021.59", "0.0", "0.0");
         this.loanTransactionHelper.makeRepayment("04 May 2021", 2220.0F, loanID);
 
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("04 May 2021"), 0, false, "0.0", "61.12", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("04 May 2021"), 0, false, "0.0", "63.17", "0.0", "0.0");
         this.loanTransactionHelper.makeRepayment("01 June 2021", 2220.0F, loanID);
 
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 June 2021"), 0, false, "496.07", "1662.81", "0.0", "0.0");
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 July 2021"), 0, false, "535.78", "1684.22", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 June 2021"), 0, false, "424.28", "1732.55", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 July 2021"), 0, false, "446.97", "1773.03", "0.0", "0.0");
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+        WorkingDaysHelper.updateWorkingDays(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void testInterestForFirstInstallmentWithInterestRecalculation() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        WorkingDaysHelper.updateWorkingDaysWeekDays(this.requestSpec, this.responseSpec);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
+
+        final String loanDisbursementDate = "06 May 2022";
+        String firstRepayment = "27 May 2022";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        String principalAmount = "7800.00";
+        String rateOfInterest = "8.9";
+        String numberOfRepayments = "12";
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12", numberOfRepayments, principalAmount,
+                rateOfInterest);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment, numberOfRepayments,
+                principalAmount, rateOfInterest);
+
+        Assertions.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("27 May 2022"), 0, false, "600.72", "479.28", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("27 May 2022", 1080.00F, loanID);
+
         loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
         Assertions.assertNotNull(loanSchedule);
         verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);


[fineract] 01/04: FINERACT-1746: Random Installments Generation Issue (#2629)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch 1.7.2
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 23fc45c545b3f043c31d4d09cc17b9efd9dddf30
Author: logoutdhaval <10...@users.noreply.github.com>
AuthorDate: Sat Oct 15 13:18:12 2022 +0530

    FINERACT-1746: Random Installments Generation Issue (#2629)
    
    Co-authored-by: Dhaval Maniyar <dh...@Dhavals-MacBook-Pro.local>
---
 .../loanschedule/data/LoanScheduleParams.java      |  73 +++---
 .../domain/AbstractLoanScheduleGenerator.java      |  15 +-
 .../ClientLoanIntegrationTest.java                 | 283 +++++++++++++++++++++
 .../common/loans/LoanProductTestBuilder.java       |   9 +
 .../common/loans/LoanTransactionHelper.java        |   5 +
 5 files changed, 350 insertions(+), 35 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
index c5c0bec83..1e18a9608 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java
@@ -25,6 +25,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import lombok.Getter;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -40,6 +41,8 @@ public final class LoanScheduleParams {
 
     private LocalDate periodStartDate;
     private LocalDate actualRepaymentDate;
+    @Getter
+    private LocalDate originalRepaymentDate;
 
     // variables for cumulative totals
     private Money totalCumulativePrincipal;
@@ -100,13 +103,13 @@ public final class LoanScheduleParams {
     private final boolean applyInterestRecalculation;
 
     private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
-            final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
-            final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
-            Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
-            final Map<LocalDate, Money> latePaymentMap, final Map<LocalDate, Money> compoundingMap, final Money unCompoundedAmount,
-            final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
-            final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
-            final Collection<RecalculationDetail> recalculationDetails,
+            final LocalDate actualRepaymentDate, final LocalDate originalRepaymentDate, final Money totalCumulativePrincipal,
+            final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
+            final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
+            final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
+            final Map<LocalDate, Money> compoundingMap, final Money unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
+            Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
+            final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
             final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
             final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
         this.periodNumber = periodNumber;
@@ -114,6 +117,7 @@ public final class LoanScheduleParams {
         this.loanTermInDays = loanTermInDays;
         this.periodStartDate = periodStartDate;
         this.actualRepaymentDate = actualRepaymentDate;
+        this.originalRepaymentDate = originalRepaymentDate;
         this.totalCumulativePrincipal = totalCumulativePrincipal;
         this.totalCumulativeInterest = totalCumulativeInterest;
         this.totalFeeChargesCharged = totalFeeChargesCharged;
@@ -142,10 +146,10 @@ public final class LoanScheduleParams {
     }
 
     public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int instalmentNumber,
-            int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
-            final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
-            final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
-            final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
+            int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final LocalDate originalRepaymentDate,
+            final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final Money totalFeeChargesCharged,
+            final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace,
+            final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
             final Map<LocalDate, Money> compoundingMap, Money unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
             final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
             final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
@@ -153,11 +157,11 @@ public final class LoanScheduleParams {
             final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
         final boolean partialUpdate = true;
         return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
-                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
-                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
-                compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
-                outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
-                scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
+                originalRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged,
+                totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+                principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+                outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
+                loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
     }
 
     public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
@@ -167,6 +171,7 @@ public final class LoanScheduleParams {
         final int instalmentNumber = 1;
         final LocalDate periodStartDate = null;
         final LocalDate actualRepaymentDate = null;
+        final LocalDate originalRepaymentDate = null;
         final Money totalCumulativePrincipal = null;
         final Money totalCumulativeInterest = null;
         final Money totalFeeChargesCharged = null;
@@ -187,11 +192,11 @@ public final class LoanScheduleParams {
         final MonetaryCurrency currency = null;
         final Money unCompoundedAmount = null;
         return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
-                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
-                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
-                compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
-                outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
-                scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
+                originalRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged,
+                totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+                principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+                outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
+                loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
     }
 
     public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
@@ -202,6 +207,7 @@ public final class LoanScheduleParams {
         final Money totalCumulativePrincipal = Money.zero(currency);
         final Money totalCumulativeInterest = Money.zero(currency);
         final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
+        final LocalDate originalRepaymentDate = periodStartDate;
         final LocalDate actualRepaymentDate = periodStartDate;
         final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
         final Money totalPenaltyChargesCharged = Money.zero(currency);
@@ -221,11 +227,11 @@ public final class LoanScheduleParams {
         final boolean applyInterestRecalculation = false;
         final Money unCompoundedAmount = Money.zero(currency);
         return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
-                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
-                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
-                compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
-                outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
-                scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
+                originalRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged,
+                totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+                principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+                outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
+                loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
     }
 
     public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
@@ -236,6 +242,7 @@ public final class LoanScheduleParams {
         final Money totalCumulativePrincipal = Money.zero(currency);
         final Money totalCumulativeInterest = Money.zero(currency);
         final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
+        final LocalDate originalRepaymentDate = periodStartDate;
         final LocalDate actualRepaymentDate = periodStartDate;
         final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
         final Money totalPenaltyChargesCharged = Money.zero(currency);
@@ -255,11 +262,11 @@ public final class LoanScheduleParams {
         final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
         final Money unCompoundedAmount = Money.zero(currency);
         return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
-                totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
-                totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
-                compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
-                outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
-                scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
+                originalRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged,
+                totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal,
+                principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled,
+                outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
+                loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
     }
 
     public int getPeriodNumber() {
@@ -390,6 +397,10 @@ public final class LoanScheduleParams {
         this.actualRepaymentDate = actualRepaymentDate;
     }
 
+    public void setOriginalRepaymentDate(LocalDate originalRepaymentDate) {
+        this.originalRepaymentDate = originalRepaymentDate;
+    }
+
     public void setReducePrincipal(Money reducePrincipal) {
         this.reducePrincipal = reducePrincipal;
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index 4f906e76e..4894cc06f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -2129,6 +2129,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
             if (loanApplicationTerms.isInterestRecalculationEnabled()) {
                 lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
             }
+
             LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
             boolean isFirstRepayment = true;
 
@@ -2144,6 +2145,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
             // Actual period Number plus interest only repayments
             int instalmentNumber = 1;
             LocalDate lastInstallmentDate = actualRepaymentDate;
+            LocalDate originalRepaymentDate = lastInstallmentDate;
             LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
             // Set fixed Amortization Amounts(either EMI or Principal )
             updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance);
@@ -2193,15 +2195,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                         actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
                                 loanApplicationTerms, isFirstRepayment);
                         if (actualRepaymentDate.isAfter(rescheduleFrom) || actualRepaymentDate.isEqual(rescheduleFrom)) {
-                            actualRepaymentDate = lastInstallmentDate;
+                            actualRepaymentDate = originalRepaymentDate;
                         }
                         isFirstRepayment = false;
                         LocalDate prevLastInstDate = lastInstallmentDate;
+                        originalRepaymentDate = actualRepaymentDate.plusMonths(1);
                         lastInstallmentDate = this.scheduledDateGenerator
                                 .adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
                         LocalDate modifiedLastInstDate = null;
                         LoanTermVariationsData variation1 = null;
+
+                        boolean hasDueDateVariation = false;
                         while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
+                            hasDueDateVariation = true;
                             LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
                             if (!variation.isSpecificToInstallment()) {
                                 modifiedLastInstDate = variation.getDateValue();
@@ -2209,7 +2215,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                             }
                         }
 
-                        if (!lastInstallmentDate.isEqual(installment.getDueDate())
+                        if (hasDueDateVariation && !lastInstallmentDate.isEqual(installment.getDueDate())
                                 && !installment.getDueDate().equals(modifiedLastInstDate)) {
                             lastInstallmentDate = prevLastInstDate;
                             actualRepaymentDate = lastInstallmentDate;
@@ -2347,8 +2353,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
             if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) {
                 Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
                 loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForPartialUpdate(periodNumber, instalmentNumber,
-                        loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest,
-                        totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
+                        loanTermInDays, periodStartDate, actualRepaymentDate, originalRepaymentDate, totalCumulativePrincipal,
+                        totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
                         totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
                         uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
                         newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
@@ -2356,6 +2362,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                 retainedInstallments.addAll(newRepaymentScheduleInstallments);
                 loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
                 loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getLoanSummary().getTotalInterestCharged()));
+                loanScheduleParams.setOriginalRepaymentDate(originalRepaymentDate);
             } else {
                 loanApplicationTerms.getLoanTermVariations().resetVariations();
             }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index b5324799b..22bae64a3 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -32,6 +32,7 @@ import io.restassured.specification.ResponseSpecification;
 import java.math.BigDecimal;
 import java.text.DateFormat;
 import java.text.DecimalFormat;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.util.ArrayList;
@@ -45,8 +46,10 @@ import java.util.Map;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.WorkingDaysHelper;
 import org.apache.fineract.integrationtests.common.accounting.Account;
 import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
 import org.apache.fineract.integrationtests.common.accounting.JournalEntry;
@@ -4602,6 +4605,20 @@ public class ClientLoanIntegrationTest {
                 recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType);
     }
 
+    private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationRestFrequencyType, final String preCloseInterestCalculationStrategy, final Account[] accounts,
+            final String installmentMultipleOf) {
+        final String recalculationCompoundingFrequencyType = null;
+        final String recalculationCompoundingFrequencyInterval = null;
+        final Integer recalculationCompoundingFrequencyOnDayType = null;
+        final Integer recalculationCompoundingFrequencyDayOfWeekType = null;
+        return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
+                rescheduleStrategyMethod, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval,
+                preCloseInterestCalculationStrategy, accounts, null, false, recalculationCompoundingFrequencyOnDayType,
+                recalculationCompoundingFrequencyDayOfWeekType, installmentMultipleOf);
+    }
+
     private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
             final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
             final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
@@ -4636,6 +4653,37 @@ public class ClientLoanIntegrationTest {
         return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
     }
 
+    private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
+            final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
+            final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval,
+            final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId,
+            boolean isArrearsBasedOnOriginalSchedule, final Integer recalculationCompoundingFrequencyOnDayType,
+            final Integer recalculationCompoundingFrequencyDayOfWeekType, final String installmentsMultiplesOf) {
+        LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
+        LoanProductTestBuilder builder = new LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("12")
+                .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("19.9")
+                .withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy).withAmortizationTypeAsEqualInstallments()
+                .withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
+                .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
+                        preCloseInterestCalculationStrategy)
+                .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
+                        preCloseInterestCalculationStrategy)
+                .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
+                        recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType,
+                        recalculationCompoundingFrequencyDayOfWeekType)
+                .withDefineInstallmentAmount(true).withInstallmentAmountInMultiplesOf(installmentsMultiplesOf);
+        if (accounts != null) {
+            builder = builder.withAccountingRulePeriodicAccrual(accounts);
+        }
+
+        if (isArrearsBasedOnOriginalSchedule) {
+            builder = builder.withArrearsConfiguration();
+        }
+
+        final String loanProductJSON = builder.build(chargeId);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
     private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
             final String disbursementDate, final String repaymentStrategy, final List<HashMap> charges) {
         return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, null,
@@ -4689,6 +4737,37 @@ public class ClientLoanIntegrationTest {
         return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
     }
 
+    private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
+            final String disbursementDate, final String repaymentStrategy, final String firstRepaymentDate) {
+        LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
+        final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec);
+        Assertions.assertNotNull(collateralId);
+        List<HashMap> collaterals = new ArrayList<>();
+
+        final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec,
+                String.valueOf(clientID), collateralId);
+        Assertions.assertNotNull(clientCollateralId);
+        addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+                .withPrincipal("10000.00") //
+                .withLoanTermFrequency("12") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("12") //
+                .withRepaymentEveryAfter("1") //
+                .withLoanTermFrequencyAsMonths() //
+                .withInterestRatePerPeriod("19.9") //
+                .withAmortizationTypeAsEqualInstallments() //
+                .withInterestTypeAsDecliningBalance() //
+                .withInterestCalculationPeriodTypeAsDays() //
+                .withExpectedDisbursementDate(disbursementDate) //
+                .withSubmittedOnDate(disbursementDate) //
+                .withwithRepaymentStrategy(repaymentStrategy).withRepaymentFrequencyTypeAsMonths()//
+                .withFirstRepaymentDate(firstRepaymentDate).withCollaterals(collaterals)
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
     private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule, List<Map<String, Object>> expectedvalues) {
         int index = 1;
         verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, index);
@@ -5673,6 +5752,210 @@ public class ClientLoanIntegrationTest {
         assertEquals(clientCollateralId, clientCollateralIdResult);
     }
 
+    @Test
+    public void testLoanScheduleWithInterestRecalculationMakePrepaymentAfterRepayment() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
+        Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        Calendar currentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        startDate.add(Calendar.MONTH, -8);
+
+        Calendar firstRepaymentDate = (Calendar) startDate.clone();
+        firstRepaymentDate.add(Calendar.MONTH, 1);
+        firstRepaymentDate.add(Calendar.DAY_OF_MONTH, firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) - Calendar.DAY_OF_MONTH);
+        String firstRepayment = dateFormat.format(firstRepaymentDate.getTime());
+
+        final String loanDisbursementDate = dateFormat.format(startDate.getTime());
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
+
+        Assertions.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+        startDate.add(Calendar.DAY_OF_MONTH, 2);
+        String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
+        //
+        Float earlyPayment = Float.parseFloat("3000");
+        this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate, earlyPayment, loanID);
+
+        HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
+        String prepayAmount = String.valueOf(prepayDetail.get("amount"));
+        String loanPrepaymentDate = dateFormat.format(currentDate.getTime());
+        this.loanTransactionHelper.makeRepayment(loanPrepaymentDate, Float.parseFloat(prepayAmount), loanID);
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculationMakeAdvancePaymentTillSettlement() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
+        final LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.requestSpec, errorResponse);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
+        Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        Calendar currentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
+        startDate.add(Calendar.MONTH, -8);
+
+        Calendar firstRepaymentDate = (Calendar) startDate.clone();
+        firstRepaymentDate.add(Calendar.MONTH, 1);
+        firstRepaymentDate.add(Calendar.DAY_OF_MONTH, firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) - Calendar.DAY_OF_MONTH);
+        String firstRepayment = dateFormat.format(firstRepaymentDate.getTime());
+
+        final String loanDisbursementDate = dateFormat.format(startDate.getTime());
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
+
+        Assertions.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+        Calendar repaymentDate = (Calendar) firstRepaymentDate.clone();
+        startDate.add(Calendar.DAY_OF_MONTH, 2);
+        String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
+        //
+        Float earlyPayment = Float.parseFloat("3000");
+        String retrieveDueDate = null;
+        Float amount = null;
+        this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate, earlyPayment, loanID);
+        for (int i = 1; i < loanSchedule.size(); i++) {
+
+            retrieveDueDate = dateFormat.format(repaymentDate.getTime());
+            amount = (Float) loanSchedule.get(i).get("principalOriginalDue") + (Float) loanSchedule.get(i).get("interestOriginalDue");
+            if (currentDate.after(repaymentDate)) {
+                this.loanTransactionHelper.makeRepayment(retrieveDueDate, amount, loanID);
+            } else {
+                break;
+            }
+            repaymentDate.add(Calendar.MONTH, 1);
+        }
+        HashMap savingsAccountErrorData = validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID);
+        ArrayList<HashMap> error = (ArrayList<HashMap>) savingsAccountErrorData.get("errors");
+        assertEquals("error.msg.loan.transaction.cannot.be.a.future.date", error.get(0).get("userMessageGlobalisationCode"));
+    }
+
+    @Test
+    public void testLoanScheduleWithInterestRecalculationAfterLatePayment() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        WorkingDaysHelper.updateWorkingDaysWeekDays(this.requestSpec, this.responseSpec);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
+
+        final String loanDisbursementDate = "28 January 2021";
+        String firstRepayment = "01 March 2021";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
+
+        Assertions.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 March 2021"), 0, false, "388.11", "1831.89", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("01 March 2021", 2220.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 April 2021"), 0, false, "270.55", "1949.45", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("01 April 2021", 2220.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("03 May 2021"), 0, false, "264.31", "1955.69", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("04 May 2021", 2220.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("04 May 2021"), 0, false, "0.0", "61.12", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("01 June 2021", 2220.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 June 2021"), 0, false, "496.07", "1662.81", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 July 2021"), 0, false, "535.78", "1684.22", "0.0", "0.0");
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+        WorkingDaysHelper.updateWorkingDays(this.requestSpec, this.responseSpec);
+    }
+
+    private Calendar convertStringDateToCalender(final String stringDate) {
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        Calendar date = Calendar.getInstance();
+        try {
+            Date date1 = dateFormat.parse(stringDate);
+            date.setTime(date1);
+
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+        return date;
+    }
+
     private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) {
         String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID);
         String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index 7f0e0f8b8..ecd5aa2e7 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -126,6 +126,7 @@ public class LoanProductTestBuilder {
     private boolean syncExpectedWithDisbursementDate = false;
     private String fixedPrincipalPercentagePerInstallment;
     private String installmentAmountInMultiplesOf;
+    private boolean canDefineInstallmentAmount;
 
     public String build(final String chargeId) {
         final HashMap<String, Object> map = new HashMap<>();
@@ -163,6 +164,9 @@ public class LoanProductTestBuilder {
         if (this.minimumDaysBetweenDisbursalAndFirstRepayment != null) {
             map.put("minimumDaysBetweenDisbursalAndFirstRepayment", this.minimumDaysBetweenDisbursalAndFirstRepayment);
         }
+        if (this.canDefineInstallmentAmount) {
+            map.put("canDefineInstallmentAmount", this.canDefineInstallmentAmount);
+        }
         if (multiDisburseLoan) {
             map.put("multiDisburseLoan", this.multiDisburseLoan);
             map.put("maxTrancheCount", this.maxTrancheCount);
@@ -443,6 +447,11 @@ public class LoanProductTestBuilder {
         return this;
     }
 
+    public LoanProductTestBuilder withDefineInstallmentAmount(final boolean canDefineInstallmentAmount) {
+        this.canDefineInstallmentAmount = canDefineInstallmentAmount;
+        return this;
+    }
+
     public LoanProductTestBuilder withInterestRecalculationDetails(final String interestRecalculationCompoundingMethod,
             final String rescheduleStrategyMethod, String preCloseInterestCalculationStrategy) {
         this.isInterestRecalculationEnabled = true;
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 85527944d..36fbfdca2 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -298,6 +298,11 @@ public class LoanTransactionHelper {
         return performLoanTransaction(createGlimAccountURL(DISBURSE_LOAN_COMMAND, glimID), getDisbursementAsJSON(date));
     }
 
+    public HashMap disburseLoanWithNetDisbursalAmount(final String date, final Integer loanID, final String netDisbursalAmount) {
+        return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID),
+                getDisburseLoanAsJSON(date, null, netDisbursalAmount));
+    }
+
     public HashMap undoDisburseGlimAccount(final Integer glimID) {
         LOG.info("--------------------------------- UNDO DISBURSAL GLIM APPLICATION -------------------------------");
         final String undoBodyJson = "{'note':'UNDO DISBURSAL'}";


[fineract] 03/04: FINERACT-1786: Repayment schedule is wrong with late repayment (#2703)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch 1.7.2
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 0d7b7e2167290fe669d1b3169e24165e941dcc04
Author: logoutdhaval <10...@users.noreply.github.com>
AuthorDate: Fri Nov 4 19:45:44 2022 +0530

    FINERACT-1786: Repayment schedule is wrong with late repayment (#2703)
---
 .../domain/AbstractLoanScheduleGenerator.java      |  2 +-
 .../ClientLoanIntegrationTest.java                 | 90 ++++++++++++++++++++--
 2 files changed, 86 insertions(+), 6 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index d1ef9b5da..e7c389985 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -2199,7 +2199,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                         }
                         isFirstRepayment = false;
                         LocalDate prevLastInstDate = lastInstallmentDate;
-                        originalRepaymentDate = actualRepaymentDate.plusMonths(1);
+                        originalRepaymentDate = actualRepaymentDate;
                         lastInstallmentDate = this.scheduledDateGenerator
                                 .adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
                         LocalDate modifiedLastInstDate = null;
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 077c88230..e9cd83abd 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -64,6 +64,7 @@ import org.apache.fineract.integrationtests.common.savings.AccountTransferHelper
 import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
 import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
 import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -5904,7 +5905,7 @@ public class ClientLoanIntegrationTest {
         DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
         dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
         GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
-
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "43", true);
         final String loanDisbursementDate = "28 January 2021";
         String firstRepayment = "01 March 2021";
         final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
@@ -5952,11 +5953,11 @@ public class ClientLoanIntegrationTest {
         addRepaymentValues(expectedvalues, convertStringDateToCalender("03 May 2021"), 0, false, "198.41", "2021.59", "0.0", "0.0");
         this.loanTransactionHelper.makeRepayment("04 May 2021", 2220.0F, loanID);
 
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("04 May 2021"), 0, false, "0.0", "63.17", "0.0", "0.0");
-        this.loanTransactionHelper.makeRepayment("01 June 2021", 2220.0F, loanID);
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("04 May 2021"), 0, false, "0.0", "61.88", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("01 June 2021", 61.88F, loanID);
 
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 June 2021"), 0, false, "424.28", "1732.55", "0.0", "0.0");
-        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 July 2021"), 0, false, "446.97", "1773.03", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 June 2021"), 0, false, "487.45", "1732.55", "0.0", "0.0");
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 July 2021"), 0, false, "459.37", "1760.63", "0.0", "0.0");
         loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
         Assertions.assertNotNull(loanSchedule);
         verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
@@ -6018,6 +6019,78 @@ public class ClientLoanIntegrationTest {
         WorkingDaysHelper.updateWorkingDays(this.requestSpec, this.responseSpec);
     }
 
+    @Test
+    public void testLoanScheduleWithInterestRecalculationForLateRepaymentOfLateRepaymentInstallments() {
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        WorkingDaysHelper.updateWorkingDaysWeekDays(this.requestSpec, this.responseSpec);
+        DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
+        dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "43", true);
+        final String loanDisbursementDate = "06 May 2022";
+        String firstRepayment = "27 May 2022";
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
+        String principalAmount = "7800.00";
+        String rateOfInterest = "8.9";
+        String numberOfRepayments = "12";
+        final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
+                LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
+                LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
+                LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
+                LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
+                LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12", numberOfRepayments, principalAmount,
+                rateOfInterest);
+
+        final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
+                LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment, numberOfRepayments,
+                principalAmount, rateOfInterest);
+
+        Assertions.assertNotNull(loanID);
+        HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
+        String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
+        loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+
+        List<Map<String, Object>> expectedvalues = new ArrayList<>();
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("27 May 2022"), 0, false, "600.72", "479.28", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("27 May 2022", 1080.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("27 June 2022"), 0, false, "426.98", "653.02", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("27 June 2022", 1080.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("27 July 2022"), 0, false, "485.52", "594.48", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("27 July 2022", 1080.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("29 August 2022"), 0, false, "472.96", "607.04", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("30 August 2022", 1080.0F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("30 August 2022"), 0, false, "0.0", "17.01", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("01 September 2022", 17.01F, loanID);
+
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("01 September 2022"), 0, false, "0.0", "34.02", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("01 September 2022", 34.02F, loanID);
+        addRepaymentValues(expectedvalues, convertStringDateToCalender("27 September 2022"), 0, false, "637.7", "442.3", "0.0", "0.0");
+        this.loanTransactionHelper.makeRepayment("27 September 2022", 1080.0F, loanID);
+
+        loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
+        Assertions.assertNotNull(loanSchedule);
+        verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
+        WorkingDaysHelper.updateWorkingDays(this.requestSpec, this.responseSpec);
+    }
+
     private Calendar convertStringDateToCalender(final String stringDate) {
         DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
         Calendar date = Calendar.getInstance();
@@ -6138,4 +6211,11 @@ public class ClientLoanIntegrationTest {
 
         return dayOfMonth;
     }
+
+    @AfterEach
+    public void tearDown() {
+        GlobalConfigurationHelper.resetAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec);
+        GlobalConfigurationHelper.verifyAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec);
+    }
+
 }