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 <[email protected]>
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');