You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by av...@apache.org on 2020/09/14 14:26:26 UTC

[fineract] branch develop updated: FINERACT-1109-rework-with-fixed-emi

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 10dd1fa  FINERACT-1109-rework-with-fixed-emi
     new 44bc6e6  Merge pull request #1327 from fynmanoj/FINERACT-1109-rework
10dd1fa is described below

commit 10dd1fa2b944c1311c8faae2c7df736dc733df1e
Author: Manoj <ma...@fynarfin.io>
AuthorDate: Sun Sep 13 22:18:20 2020 +0530

    FINERACT-1109-rework-with-fixed-emi
---
 .../LoanRescheduleOnDecliningBalanceLoanTest.java  | 150 ++++++++++++++++++++-
 .../domain/AbstractLoanScheduleGenerator.java      |  31 ++++-
 .../LoanScheduleModelDisbursementPeriod.java       |  11 ++
 .../domain/LoanScheduleModelPeriod.java            |   4 +
 .../domain/LoanScheduleModelRepaymentPeriod.java   |  11 ++
 .../data/LoanRescheduleRequestDataValidator.java   |   5 -
 6 files changed, 198 insertions(+), 14 deletions(-)

diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
index 4a7ae3a..c4b4905 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
@@ -71,8 +71,6 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
 
         this.generalResponseSpec = new ResponseSpecBuilder().build();
 
-        // create all required entities
-        this.createRequiredEntities();
     }
 
     @AfterEach
@@ -91,6 +89,16 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
     }
 
     /**
+     * Creates the client, loan product, and loan entities
+     **/
+    private void createRequiredEntitiesWithRecalculationEnabled() {
+        this.createClientEntity();
+        this.createLoanProductWithInterestRecalculation();
+        this.createLoanEntity();
+        this.enableConfig();
+    }
+
+    /**
      * create a new client
      **/
     private void createClientEntity() {
@@ -114,6 +122,38 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
         LOG.info("Successfully created loan product  (ID:{}) ", this.loanProductId);
     }
 
+    private void createLoanProductWithInterestRecalculation() {
+        LOG.info(
+                "---------------------------------CREATING LOAN PRODUCT WITH RECALULATION ENABLED ------------------------------------------");
+
+        final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE;
+        final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS;
+        final String recalculationRestFrequencyType = LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY;
+        final String recalculationRestFrequencyInterval = "0";
+        final String preCloseInterestCalculationStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE;
+        final String recalculationCompoundingFrequencyType = null;
+        final String recalculationCompoundingFrequencyInterval = null;
+        final Integer recalculationCompoundingFrequencyOnDayType = null;
+        final Integer recalculationCompoundingFrequencyDayOfWeekType = null;
+        final Integer recalculationRestFrequencyOnDayType = null;
+        final Integer recalculationRestFrequencyDayOfWeekType = null;
+
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
+                .withInterestRateFrequencyTypeAsYear().withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
+                .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
+                        preCloseInterestCalculationStrategy)
+                .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval,
+                        recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType)
+                .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
+                        recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType,
+                        recalculationCompoundingFrequencyDayOfWeekType)
+                .build(null);
+
+        this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+        LOG.info("Successfully created loan product  (ID:{}) ", this.loanProductId);
+    }
+
     /**
      * submit a new loan application, approve and disburse the loan
      **/
@@ -173,6 +213,8 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
 
     @Test
     public void testCreateLoanRescheduleRequestWithInterestAppropriation() {
+        // create all required entities
+        this.createRequiredEntities();
         this.createAndApproveLoanRescheduleRequestForInterestAppropriation();
 
     }
@@ -210,8 +252,108 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
         final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
         final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
 
-        assertEquals(12186, totalDueForPeriod.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK");
-        assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK");
+        assertEquals(12186, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK");
+        assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK");
+
+        LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
+
+    }
+
+    @Test
+    public void testCreateLoanRescheduleRequestWithRecalculationEnabled() {
+        // create all required entities
+        this.createRequiredEntitiesWithRecalculationEnabled();
+        this.createAndApproveLoanRescheduleRequestWithRecalculationEnabled();
+    }
+
+    /**
+     * create new loan reschedule request with recalculation enabled in Loan product
+     **/
+
+    private void createAndApproveLoanRescheduleRequestWithRecalculationEnabled() {
+        LOG.info(
+                "---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR LOAN WITH RECALCULATION------------------------------------");
+
+        final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null)
+                .updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 October 2015")
+                .updateRecalculateInterest(true).build(this.loanId.toString());
+
+        this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
+        this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
+
+        LOG.info("Successfully created loan reschedule request (ID: {} )", this.loanRescheduleRequestId);
+
+        final String aproveRequestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
+        this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, aproveRequestJSON);
+        final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
+        assertTrue((Boolean) response.get("approved"));
+
+        LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
+
+        final Map repaymentSchedule = (Map) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId,
+                "repaymentSchedule");
+        final ArrayList periods = (ArrayList) repaymentSchedule.get("periods");
+
+        HashMap period = (HashMap) periods.get(5);
+        Float totalDueForPeriod = (Float) period.get("totalDueForPeriod");
+
+        final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
+        final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
+
+        assertEquals(12326, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK");
+        assertEquals(131512, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK");
+
+        LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
+
+    }
+
+    @Test
+    public void testCreateLoanRescheduleRequestForInterestAppropriationAndFixedEMI() {
+        // create all required entities
+        this.createRequiredEntities();
+        this.createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI();
+    }
+
+    /**
+     * create new loan reschedule request with combination of date change, interest appropriation and fixed emi
+     **/
+    private void createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI() {
+        LOG.info(
+                "---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR INTEREST APPROPRIATTION-------------------------------------");
+
+        final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null)
+                .updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 July 2015").updateEMI("5000")
+                .updateEmiChangeEndDate("4 September 2015").updateRecalculateInterest(true).build(this.loanId.toString());
+
+        this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
+        this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
+
+        LOG.info("Successfully created loan reschedule request (ID: {} )", this.loanRescheduleRequestId);
+
+        final String aproveRequestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
+        this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, aproveRequestJSON);
+        final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
+        assertTrue((Boolean) response.get("approved"));
+
+        LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
+
+        final Map repaymentSchedule = (Map) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId,
+                "repaymentSchedule");
+        final ArrayList periods = (ArrayList) repaymentSchedule.get("periods");
+
+        HashMap period = (HashMap) periods.get(5);
+        Float totalFixedDueForPeriod = (Float) period.get("totalDueForPeriod");
+
+        HashMap period2 = (HashMap) periods.get(8);
+        Float totalDueForPeriod = (Float) period2.get("totalDueForPeriod");
+
+        final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
+        final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
+
+        assertEquals(5000, totalFixedDueForPeriod.intValue(), "EXPECTED FIXED REPAYMENT is NOK");
+
+        assertEquals(15316, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK");
+        assertEquals(120806, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK");
 
         LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
 
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 62f6158..5fd4380 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
@@ -337,6 +337,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                     loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate,
                     periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams,
                     lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges);
+
+            if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
+                installment.setEMIFixedSpecificToInstallmentTrue();
+            }
+
             periods.add(installment);
 
             // Updates principal paid map with efective date for reducing
@@ -373,15 +378,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
         if (loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
                 && loanApplicationTerms.getInterestTobeApproppriated() != null
                 && loanApplicationTerms.getInterestTobeApproppriated().isGreaterThanZero()) {
-            Money interestFraction = loanApplicationTerms.getInterestTobeApproppriated().dividedBy(periods.size(), mc.getRoundingMode());
+            int emisTobeChanged = 1;
+            for (LoanScheduleModelPeriod installment : (List<LoanScheduleModelPeriod>) periods) {
+                if (!installment.isEMIFixedSpecificToInstallment()) {
+                    emisTobeChanged++;
+                }
+            }
+            if (emisTobeChanged > 1) {
+                emisTobeChanged--;
+            }
+            Money interestTobeApproppriated = loanApplicationTerms.getInterestTobeApproppriated();
+            Money interestFraction = interestTobeApproppriated.dividedBy(emisTobeChanged, mc.getRoundingMode());
             BigDecimal roundFraction = interestFraction.getAmount().remainder(BigDecimal.ONE);
             interestFraction = interestFraction.minus(roundFraction);
-            roundFraction = roundFraction.multiply(new BigDecimal(periods.size()));
             for (LoanScheduleModelPeriod installment : (List<LoanScheduleModelPeriod>) periods) {
-                installment.addInterestAmount(interestFraction);
+                if (!installment.isEMIFixedSpecificToInstallment()) {
+                    installment.addInterestAmount(interestFraction);
+                    interestTobeApproppriated = interestTobeApproppriated.minus(interestFraction);
+                }
+            }
+            LoanScheduleModelPeriod lastInstallment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
+
+            if (interestTobeApproppriated.isGreaterThanZero()) {
+                lastInstallment.addInterestAmount(interestTobeApproppriated);
             }
-            LoanScheduleModelPeriod installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
-            installment.addInterestAmount(Money.of(currency, roundFraction));
             scheduleParams.addTotalRepaymentExpected(loanApplicationTerms.getInterestTobeApproppriated());
             scheduleParams.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated());
             loanApplicationTerms.setInterestTobeApproppriated(Money.zero(currency));
@@ -1079,6 +1099,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
                     if (loanTermVariationsData.isSpecificToInstallment()) {
                         loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue());
                         recalculateAmounts = true;
+
                     } else {
                         loanApplicationTerms.setFixedEmiAmount(loanTermVariationsData.getDecimalValue());
                     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
index 68731b4..2dbb4e1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
@@ -35,6 +35,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
     private final LocalDate disbursementDate;
     private final Money principalDisbursed;
     private final BigDecimal chargesDueAtTimeOfDisbursement;
+    private boolean isEMIFixedSpecificToInstallment = false;
 
     public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
             final BigDecimal chargesDueAtTimeOfDisbursement) {
@@ -127,4 +128,14 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
     public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
         return null;
     }
+
+    @Override
+    public void setEMIFixedSpecificToInstallmentTrue() {
+        this.isEMIFixedSpecificToInstallment = true;
+    }
+
+    @Override
+    public boolean isEMIFixedSpecificToInstallment() {
+        return isEMIFixedSpecificToInstallment;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
index 0a7b8e9..207e08b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java
@@ -54,4 +54,8 @@ public interface LoanScheduleModelPeriod {
     void addInterestAmount(Money interestDue);
 
     Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails();
+
+    void setEMIFixedSpecificToInstallmentTrue();
+
+    boolean isEMIFixedSpecificToInstallment();
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
index b4720b6..a7c0edb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
@@ -42,6 +42,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
     private Money totalDue;
     private final boolean recalculatedInterestComponent;
     private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
+    private boolean isEMIFixedSpecificToInstallment = false;
 
     public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
             final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
@@ -161,4 +162,14 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
     public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
         return this.loanCompoundingDetails;
     }
+
+    @Override
+    public boolean isEMIFixedSpecificToInstallment() {
+        return this.isEMIFixedSpecificToInstallment;
+    }
+
+    @Override
+    public void setEMIFixedSpecificToInstallmentTrue() {
+        this.isEMIFixedSpecificToInstallment = true;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
index ee35f99..48de022 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
@@ -202,11 +202,6 @@ public class LoanRescheduleRequestDataValidator {
                     "Loan rescheduling is not supported for multidisbursement loans");
         }
 
-        if (loan.isInterestRecalculationEnabledForProduct()) {
-            dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                    RescheduleLoansApiConstants.resheduleWithInterestRecalculationNotSupportedErrorCode,
-                    "Loan rescheduling is not supported for the loan product with interest recalculation enabled");
-        }
         validateForOverdueCharges(dataValidatorBuilder, loan, installment);
         if (!dataValidationErrors.isEmpty()) {
             throw new PlatformApiDataValidationException(dataValidationErrors);