fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From av...@apache.org
Subject [fineract] branch develop updated: FINERACT-1109 appropriate-interest-larger-than-emi
Date Wed, 19 Aug 2020 14:50:35 GMT
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 db98023  FINERACT-1109 appropriate-interest-larger-than-emi
     new 899eab8  Merge pull request #1220 from fynmanoj/AL-14
db98023 is described below

commit db980236ad24f9a15a92f64e918c629f1eec927a
Author: Manoj <manoj@fynarfin.io>
AuthorDate: Tue Aug 18 23:23:06 2020 +0530

    FINERACT-1109 appropriate-interest-larger-than-emi
---
 .../LoanRescheduleOnDecliningBalanceLoanTest.java  | 220 +++++++++++++++++++++
 .../common/GlobalConfigurationHelper.java          |  14 +-
 .../domain/ConfigurationDomainService.java         |   2 +
 .../domain/ConfigurationDomainServiceJpa.java      |   5 +
 .../loanaccount/data/ScheduleGeneratorDTO.java     |   7 +-
 .../portfolio/loanaccount/domain/Loan.java         |   6 +-
 .../domain/AbstractLoanScheduleGenerator.java      |  22 ++-
 ...liningBalanceInterestLoanScheduleGenerator.java |  28 +++
 .../loanschedule/domain/LoanApplicationTerms.java  |  34 +++-
 .../domain/LoanScheduleModelRepaymentPeriod.java   |   2 +-
 .../RescheduleLoansApiConstants.java               |   1 -
 .../loanaccount/service/LoanUtilService.java       |   6 +-
 .../core_db/V361__conf_interest_appropriations.sql |  22 +++
 13 files changed, 351 insertions(+), 18 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
new file mode 100644
index 0000000..4a7ae3a
--- /dev/null
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRescheduleOnDecliningBalanceLoanTest.java
@@ -0,0 +1,220 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanRescheduleRequestTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LoanRescheduleOnDecliningBalanceLoanTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LoanRescheduleOnDecliningBalanceLoanTest.class);
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification generalResponseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private LoanRescheduleRequestHelper loanRescheduleRequestHelper;
+    private Integer clientId;
+    private Integer loanProductId;
+    private Integer loanId;
+    private Integer loanRescheduleRequestId;
+    private final String loanPrincipalAmount = "100000.00";
+    private final String numberOfRepayments = "12";
+    private final String interestRatePerPeriod = "18";
+    private final String dateString = "4 September 2014";
+
+    @BeforeEach
+    public void initialize() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec,
this.responseSpec);
+
+        this.generalResponseSpec = new ResponseSpecBuilder().build();
+
+        // create all required entities
+        this.createRequiredEntities();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        disableConfig();
+    }
+
+    /**
+     * Creates the client, loan product, and loan entities
+     **/
+    private void createRequiredEntities() {
+        this.createClientEntity();
+        this.createLoanProductEntity();
+        this.createLoanEntity();
+        this.enableConfig();
+    }
+
+    /**
+     * create a new client
+     **/
+    private void createClientEntity() {
+        this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
+
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId);
+    }
+
+    /**
+     * create a new loan product
+     **/
+    private void createLoanProductEntity() {
+        LOG.info("---------------------------------CREATING LOAN PRODUCT------------------------------------------");
+
+        final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
+                .withInterestRateFrequencyTypeAsYear().withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
+                .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
+     **/
+    private void createLoanEntity() {
+        LOG.info("---------------------------------NEW LOAN APPLICATION------------------------------------------");
+
+        final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
+                .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments)
+                .withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments()
+                .withInterestCalculationPeriodTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod)
+                .withInterestTypeAsDecliningBalance().withSubmittedOnDate(dateString).withExpectedDisbursementDate(dateString)
+                .withPrincipalGrace("2").withInterestGrace("2").build(this.clientId.toString(),
this.loanProductId.toString(), null);
+
+        this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+
+        LOG.info("Sucessfully created loan (ID: {} )", this.loanId);
+
+        this.approveLoanApplication();
+        this.disburseLoan();
+    }
+
+    /**
+     * approve the loan application
+     **/
+    private void approveLoanApplication() {
+
+        if (this.loanId != null) {
+            this.loanTransactionHelper.approveLoan(this.dateString, this.loanId);
+            LOG.info("Successfully approved loan (ID: {} )", this.loanId);
+        }
+    }
+
+    /**
+     * disburse the newly created loan
+     **/
+    private void disburseLoan() {
+
+        if (this.loanId != null) {
+            this.loanTransactionHelper.disburseLoan(this.dateString, this.loanId);
+            LOG.info("Successfully disbursed loan (ID: {} )", this.loanId);
+        }
+    }
+
+    /**
+     * enables the configuration `is-interest-to-be-appropriated-equally-when-greater-than-emi`
+     **/
+    private void enableConfig() {
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "34", true);
+    }
+
+    /**
+     * disables the configuration `is-interest-to-be-appropriated-equally-when-greater-than-emi`
+     **/
+    private void disableConfig() {
+        GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec,
this.responseSpec, "34", false);
+    }
+
+    @Test
+    public void testCreateLoanRescheduleRequestWithInterestAppropriation() {
+        this.createAndApproveLoanRescheduleRequestForInterestAppropriation();
+
+    }
+
+    /**
+     * create new loan reschedule request
+     **/
+    private void createAndApproveLoanRescheduleRequestForInterestAppropriation() {
+        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
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(12186, totalDueForPeriod.intValue(), "TOTAL EXPECTED LAST REPAYMENT
is NOK");
+        assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED LAST REPAYMENT
is NOK");
+
+        LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
+
+    }
+
+}
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
index cfd43a5..39657e2 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
@@ -88,9 +88,9 @@ public class GlobalConfigurationHelper {
         ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
         ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec,
responseSpec);
 
-        // There are currently 29 global configurations.
-        Assertions.assertEquals(29, expectedGlobalConfigurations.size());
-        Assertions.assertEquals(29, actualGlobalConfigurations.size());
+        // There are currently 30 global configurations.
+        Assertions.assertEquals(30, expectedGlobalConfigurations.size());
+        Assertions.assertEquals(30, actualGlobalConfigurations.size());
 
         for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
 
@@ -354,6 +354,14 @@ public class GlobalConfigurationHelper {
         isFirstPaydayAllowedOnHoliday.put("trapDoor", false);
         defaults.add(isFirstPaydayAllowedOnHoliday);
 
+        HashMap<String, Object> isInterestAppropriationEnabled = new HashMap<>();
+        isInterestAppropriationEnabled.put("id", 34);
+        isInterestAppropriationEnabled.put("name", "is-interest-to-be-appropriated-equally-when-greater-than-emi");
+        isInterestAppropriationEnabled.put("value", 0);
+        isInterestAppropriationEnabled.put("enabled", false);
+        isInterestAppropriationEnabled.put("trapDoor", false);
+        defaults.add(isInterestAppropriationEnabled);
+
         return defaults;
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 2632ae6..cc1b199 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -96,4 +96,6 @@ public interface ConfigurationDomainService {
     boolean isSubRatesEnabled();
 
     boolean isFirstRepaymentDateAfterRescheduleAllowedOnHoliday();
+
+    boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI();
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index a037f2c..321d640 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -258,6 +258,11 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
     }
 
     @Override
+    public boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI() {
+        return getGlobalConfigurationPropertyData("is-interest-to-be-appropriated-equally-when-greater-than-emi").isEnabled();
+    }
+
+    @Override
     public Long retreivePeroidInNumberOfDaysForSkipMeetingDate() {
         final String propertyName = "skip-repayment-on-first-day-of-month";
         final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
index 81571ac..c14403f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java
@@ -44,6 +44,7 @@ public class ScheduleGeneratorDTO {
     final boolean isSkipRepaymentOnFirstDayofMonth;
     final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
     final boolean isFirstRepaymentDateAllowedOnHoliday;
+    final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
 
     public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final
ApplicationCurrency applicationCurrency,
             final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO
holidayDetailDTO,
@@ -52,7 +53,7 @@ public class ScheduleGeneratorDTO {
             final Calendar calendar, final CalendarHistoryDataWrapper calendarHistoryDataWrapper,
             final Boolean isInterestChargedFromDateAsDisbursementDateEnabled, final Integer
numberOfdays,
             final boolean isSkipRepaymentOnFirstDayofMonth, final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled,
-            final boolean isFirstRepaymentDateAllowedOnHoliday) {
+            final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI)
{
 
         this.loanScheduleFactory = loanScheduleFactory;
         this.applicationCurrency = applicationCurrency;
@@ -70,6 +71,7 @@ public class ScheduleGeneratorDTO {
         this.isSkipRepaymentOnFirstDayofMonth = isSkipRepaymentOnFirstDayofMonth;
         this.isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled = isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled;
         this.isFirstRepaymentDateAllowedOnHoliday = isFirstRepaymentDateAllowedOnHoliday;
+        this.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI = isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
     }
 
     public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
@@ -148,4 +150,7 @@ public class ScheduleGeneratorDTO {
         return isFirstRepaymentDateAllowedOnHoliday;
     }
 
+    public boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI() {
+        return isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index e9e385a..9a419b1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -5486,7 +5486,8 @@ public class Loan extends AbstractPersistableCustom {
                 compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
                 rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate,
loanTermVariations,
                 calendarHistoryDataWrapper, scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(),
-                holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday());
+                holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(),
+                scheduleGeneratorDTO.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI());
         return loanApplicationTerms;
     }
 
@@ -5764,7 +5765,8 @@ public class Loan extends AbstractPersistableCustom {
                 this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType,
restCalendarInstance, compoundingMethod,
                 compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
                 rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate,
loanTermVariations,
-                calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO,
allowCompoundingOnEod, false);
+                calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO,
allowCompoundingOnEod, false,
+                false);
     }
 
     /**
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 ae69759..1930170 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
@@ -60,7 +60,7 @@ import org.joda.time.LocalDate;
 
 public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGenerator {
 
-    private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
+    protected final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
     private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new
DefaultPaymentPeriodsInOneYearCalculator();
 
     @Override
@@ -282,7 +282,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
 
             // will check for EMI amount greater than interest calculated
             if (loanApplicationTerms.getFixedEmiAmount() != null
-                    && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount())
< 0) {
+                    && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount())
< 0
+                    && !loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled())
{
                 String errorMsg = "EMI amount must be greater than : " + principalInterestForThisPeriod.interest().getAmount();
                 throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(),
                         loanApplicationTerms.getFixedEmiAmount());
@@ -369,6 +370,23 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
             scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(Money.zero(currency));
         }
 
+        if (loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
+                && loanApplicationTerms.getInterestTobeApproppriated() != null
+                && loanApplicationTerms.getInterestTobeApproppriated().isGreaterThanZero())
{
+            Money interestFraction = loanApplicationTerms.getInterestTobeApproppriated().dividedBy(periods.size(),
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);
+            }
+            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));
+        }
+
         // determine fees and penalties for charges which depends on total
         // loan interest
         updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges);
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 2af9487..defae92 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
@@ -128,6 +128,34 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
         interestForThisInstallment = interestForThisInstallment.plus(result.interest());
         cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
 
+        Money interestTobeApproppriated = loanApplicationTerms.getInterestTobeApproppriated()
== null
+                ? Money.zero(interestForThisInstallment.getCurrency())
+                : loanApplicationTerms.getInterestTobeApproppriated();
+
+        if (loanApplicationTerms.getFixedEmiAmount() != null
+                && loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
&& interestForThisInstallment
+                        .isGreaterThan(Money.of(interestForThisInstallment.getCurrency(),
loanApplicationTerms.getFixedEmiAmount()))) {
+            LocalDate actualPeriodEndDate = this.scheduledDateGenerator.generateNextRepaymentDate(interestStartDate,
loanApplicationTerms,
+                    false);
+            PrincipalInterest tempInterest = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
+                    interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc,
cumulatingInterestDueToGrace,
+                    balanceForInterestCalculation, interestStartDate, actualPeriodEndDate);
+
+            Money fixedEmi = Money.of(interestForThisInstallment.getCurrency(), loanApplicationTerms.getFixedEmiAmount());
+
+            if (tempInterest.interest().isGreaterThan(fixedEmi)) {
+                loanApplicationTerms
+                        .setInterestTobeApproppriated(interestTobeApproppriated.plus(interestForThisInstallment.minus(fixedEmi)));
+                interestForThisInstallment = fixedEmi;
+            } else {
+                loanApplicationTerms.setInterestTobeApproppriated(
+                        interestTobeApproppriated.plus(interestForThisInstallment.minus(tempInterest.interest())));
+                interestForThisInstallment = tempInterest.interest();
+            }
+        }
+
+        cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
+
         Money interestForPeriod = interestForThisInstallment;
         if (interestForPeriod.isGreaterThanZero()) {
             interestForPeriod = interestForPeriod.minus(cumulatingInterestPaymentDueToGrace);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 2789796..5f5ba82 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -192,6 +192,8 @@ public final class LoanApplicationTerms {
 
     private final boolean isFirstRepaymentDateAllowedOnHoliday;
 
+    private final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+
     private final HolidayDetailDTO holidayDetailDTO;
 
     private final Set<Integer> periodNumbersApplicableForPrincipalGrace = new HashSet<>();
@@ -208,6 +210,7 @@ public final class LoanApplicationTerms {
     private int periodsCompleted = 0;
     private int extraPeriods = 0;
     private boolean isEqualAmortization;
+    private Money interestTobeApproppriated;
 
     public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final
Integer loanTermFrequency,
             final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments,
final Integer repaymentEvery,
@@ -245,7 +248,7 @@ public final class LoanApplicationTerms {
                 recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType,
principalThresholdForLastInstalment,
                 installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar,
approvedAmount, loanTermVariations,
                 calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled,
numberOfDays,
-                isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod,
isEqualAmortization, false);
+                isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod,
isEqualAmortization, false, false);
 
     }
 
@@ -271,7 +274,8 @@ public final class LoanApplicationTerms {
                 principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType,
restCalendarInstance,
                 compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
loanPreClosureInterestCalculationStrategy,
                 rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate,
loanTermVariations,
-                calendarHistoryDataWrapper, numberOfDays, isSkipRepaymentOnFirstDayOfMonth,
holidayDetailDTO, allowCompoundingOnEod, false);
+                calendarHistoryDataWrapper, numberOfDays, isSkipRepaymentOnFirstDayOfMonth,
holidayDetailDTO, allowCompoundingOnEod, false,
+                false);
     }
 
     public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency,
final Integer loanTermFrequency,
@@ -289,7 +293,7 @@ public final class LoanApplicationTerms {
             BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData>
loanTermVariations,
             final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final Integer numberOfDays,
             final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO,
final boolean allowCompoundingOnEod,
-            final boolean isFirstRepaymentDateAllowedOnHoliday) {
+            final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI)
{
 
         final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
         final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -327,7 +331,8 @@ public final class LoanApplicationTerms {
                 compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
                 loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount,
loanTermVariations, calendarHistoryDataWrapper,
                 isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipRepaymentOnFirstDayOfMonth,
holidayDetailDTO,
-                allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday);
+                allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday,
+                isInterestToBeAppropriatedEquallyWhenGreaterThanEMI);
     }
 
     public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency,
final Integer loanTermFrequency,
@@ -386,7 +391,7 @@ public final class LoanApplicationTerms {
                 recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType,
principalThresholdForLastInstalment,
                 installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy,
loanCalendar, approvedAmount, loanTermVariations,
                 calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled,
numberOfDays,
-                isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod,
isEqualAmortization, false);
+                isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod,
isEqualAmortization, false, false);
 
     }
 
@@ -413,7 +418,8 @@ public final class LoanApplicationTerms {
                 applicationTerms.calendarHistoryDataWrapper, applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled,
                 applicationTerms.numberOfDays, applicationTerms.isSkipRepaymentOnFirstDayOfMonth,
applicationTerms.holidayDetailDTO,
                 applicationTerms.allowCompoundingOnEod, applicationTerms.isEqualAmortization,
-                applicationTerms.isFirstRepaymentDateAllowedOnHoliday);
+                applicationTerms.isFirstRepaymentDateAllowedOnHoliday,
+                applicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI);
     }
 
     private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -437,7 +443,8 @@ public final class LoanApplicationTerms {
             BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations,
             final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled,
             final Integer numberOfDays, final boolean isSkipRepaymentOnFirstDayOfMonth, final
HolidayDetailDTO holidayDetailDTO,
-            final boolean allowCompoundingOnEod, final boolean isEqualAmortization, final
boolean isFirstRepaymentDateAllowedOnHoliday) {
+            final boolean allowCompoundingOnEod, final boolean isEqualAmortization, final
boolean isFirstRepaymentDateAllowedOnHoliday,
+            final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI) {
 
         this.currency = currency;
         this.loanTermFrequency = loanTermFrequency;
@@ -511,6 +518,7 @@ public final class LoanApplicationTerms {
         this.totalPrincipalAccounted = principal.zero();
         this.isEqualAmortization = isEqualAmortization;
         this.isFirstRepaymentDateAllowedOnHoliday = isFirstRepaymentDateAllowedOnHoliday;
+        this.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI = isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
     }
 
     public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final
Money totalCumulativePrincipalToDate,
@@ -1778,4 +1786,16 @@ public final class LoanApplicationTerms {
         return isFirstRepaymentDateAllowedOnHoliday;
     }
 
+    public Money getInterestTobeApproppriated() {
+        return interestTobeApproppriated;
+    }
+
+    public void setInterestTobeApproppriated(Money interestTobeApproppriated) {
+        this.interestTobeApproppriated = interestTobeApproppriated;
+    }
+
+    public boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled() {
+        return isInterestToBeAppropriatedEquallyWhenGreaterThanEMI;
+    }
+
 }
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 3cb035f..b4720b6 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
@@ -154,7 +154,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
     @Override
     public void addInterestAmount(Money interestDue) {
         this.interestDue = this.interestDue.plus(interestDue);
-        this.totalDue = this.totalDue.plus(principalDue);
+        this.totalDue = this.totalDue.plus(interestDue);
     }
 
     @Override
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
index 7cbdc37..b871536 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
@@ -52,7 +52,6 @@ public class RescheduleLoansApiConstants {
     public static final String approveCommandParamName = "approve";
     public static final String pendingCommandParamName = "pending";
     public static final String rejectCommandParamName = "reject";
-
     public static final String endDateParamName = "endDate";
     public static final String emiParamName = "emi";
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
index 2c2c894..1c43021 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java
@@ -143,11 +143,15 @@ public class LoanUtilService {
         boolean isFirstRepaymentDateAllowedOnHoliday = this.configurationDomainService
                 .isFirstRepaymentDateAfterRescheduleAllowedOnHoliday();
 
+        boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI = this.configurationDomainService
+                .isInterestToBeAppropriatedEquallyWhenGreaterThanEMI();
+
         ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory,
applicationCurrency,
                 calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance,
compoundingCalendarInstance, recalculateFrom,
                 overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper,
                 isInterestChargedFromDateAsDisbursementDateEnabled, numberOfDays, isSkipRepaymentOnFirstMonth,
-                isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled, isFirstRepaymentDateAllowedOnHoliday);
+                isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled, isFirstRepaymentDateAllowedOnHoliday,
+                isInterestToBeAppropriatedEquallyWhenGreaterThanEMI);
 
         return scheduleGeneratorDTO;
     }
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V361__conf_interest_appropriations.sql
b/fineract-provider/src/main/resources/sql/migrations/core_db/V361__conf_interest_appropriations.sql
new file mode 100644
index 0000000..a301631
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V361__conf_interest_appropriations.sql
@@ -0,0 +1,22 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+INSERT INTO `c_configuration` (`name`, `value`, `date_value`, `enabled`, `is_trap_door`,
`description`)
+VALUES
+    ('is-interest-to-be-appropriated-equally-when-greater-than-emi', 0, NULL, 0, 0, 'If enabled,
while loan reschedule when interest amount is greater than EMI, the additional interest is
spread equally over remaining installments');


Mime
View raw message