This is an automated email from the ASF dual-hosted git repository.
adamsaghy 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 af37e07f3 FINERACT-2152: Interest pause during one period and between
two periods
af37e07f3 is described below
commit af37e07f31a97154e1ccae2c678ba6ca8ef5c08f
Author: Oleksii Novikov <[email protected]>
AuthorDate: Thu Jan 23 18:15:33 2025 +0200
FINERACT-2152: Interest pause during one period and between two periods
---
.../fineract/client/util/FineractClient.java | 3 +
.../apache/fineract/test/api/ApiConfiguration.java | 6 ++
.../stepdef/loan/LoanInterestPauseStepDef.java | 53 +++++++++++++
.../resources/features/LoanInterestPause.feature | 74 +++++++++++++++++
.../InterestPauseWritePlatformServiceImpl.java | 30 ++++---
.../data/LoanTermVariationsDataWrapper.java | 33 ++++----
.../loanaccount/domain/LoanTermVariationType.java | 4 +
.../loanaccount/service/LoanAssembler.java | 7 ++
...dvancedPaymentScheduleTransactionProcessor.java | 18 ++++-
.../loanschedule/data/InterestPeriod.java | 24 +++---
.../data/ProgressiveLoanInterestScheduleModel.java | 92 ++++++++++++++++++----
.../loanschedule/data/RepaymentPeriod.java | 2 +-
.../domain/ProgressiveLoanScheduleGenerator.java | 24 +++---
.../portfolio/loanproduct/calc/EMICalculator.java | 2 +
.../loanproduct/calc/ProgressiveEMICalculator.java | 14 ++++
.../calc/ProgressiveEMICalculatorTest.java | 76 ++++++++++++++++++
.../loanaccount/service/LoanAssemblerImpl.java | 27 ++++++-
.../starter/LoanAccountConfiguration.java | 4 +-
18 files changed, 414 insertions(+), 79 deletions(-)
diff --git
a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
index f722c3488..ec6aa161b 100644
---
a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
+++
b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
@@ -88,6 +88,7 @@ import org.apache.fineract.client.services.LoanChargesApi;
import org.apache.fineract.client.services.LoanCobCatchUpApi;
import org.apache.fineract.client.services.LoanCollateralApi;
import org.apache.fineract.client.services.LoanDisbursementDetailsApi;
+import org.apache.fineract.client.services.LoanInterestPauseApi;
import org.apache.fineract.client.services.LoanProductsApi;
import org.apache.fineract.client.services.LoanReschedulingApi;
import org.apache.fineract.client.services.LoanTransactionsApi;
@@ -290,6 +291,7 @@ public final class FineractClient {
public final UserGeneratedDocumentsApi templates;
public final UsersApi users;
public final WorkingDaysApi workingDays;
+ public final LoanInterestPauseApi loanInterestPauseApi;
public final ExternalAssetOwnersApi externalAssetOwners;
public final ExternalAssetOwnerLoanProductAttributesApi
externalAssetOwnerLoanProductAttributes;
@@ -415,6 +417,7 @@ public final class FineractClient {
templates = retrofit.create(UserGeneratedDocumentsApi.class);
users = retrofit.create(UsersApi.class);
workingDays = retrofit.create(WorkingDaysApi.class);
+ loanInterestPauseApi = retrofit.create(LoanInterestPauseApi.class);
}
public static Builder builder() {
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java
index 0607b76b9..8054d746a 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java
@@ -40,6 +40,7 @@ import org.apache.fineract.client.services.JournalEntriesApi;
import org.apache.fineract.client.services.LoanAccountLockApi;
import org.apache.fineract.client.services.LoanChargesApi;
import org.apache.fineract.client.services.LoanCobCatchUpApi;
+import org.apache.fineract.client.services.LoanInterestPauseApi;
import org.apache.fineract.client.services.LoanProductsApi;
import org.apache.fineract.client.services.LoanTransactionsApi;
import org.apache.fineract.client.services.LoansApi;
@@ -244,4 +245,9 @@ public class ApiConfiguration {
public RescheduleLoansApi rescheduleLoansApi() {
return fineractClient.createService(RescheduleLoansApi.class);
}
+
+ @Bean
+ public LoanInterestPauseApi loanInterestPauseApi() {
+ return fineractClient.createService(LoanInterestPauseApi.class);
+ }
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanInterestPauseStepDef.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanInterestPauseStepDef.java
new file mode 100644
index 000000000..39932fdaf
--- /dev/null
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanInterestPauseStepDef.java
@@ -0,0 +1,53 @@
+/**
+ * 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.test.stepdef.loan;
+
+import io.cucumber.java.en.And;
+import java.io.IOException;
+import org.apache.fineract.client.models.CommandProcessingResult;
+import org.apache.fineract.client.models.InterestPauseRequestDto;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.services.LoanInterestPauseApi;
+import org.apache.fineract.test.helper.ErrorHelper;
+import org.apache.fineract.test.stepdef.AbstractStepDef;
+import org.apache.fineract.test.support.TestContextKey;
+import org.springframework.beans.factory.annotation.Autowired;
+import retrofit2.Response;
+
+public class LoanInterestPauseStepDef extends AbstractStepDef {
+
+ @Autowired
+ private LoanInterestPauseApi loanInterestPauseApi;
+
+ @And("Customer creates interest pause with start date {string} and end
date {string}")
+ public void createInterestPause(final String startDate, final String
endDate) throws IOException {
+ final Response<PostLoansResponse> loanResponse =
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ assert loanResponse.body() != null;
+ final long loanId = loanResponse.body().getLoanId();
+
+ final InterestPauseRequestDto request = new InterestPauseRequestDto()//
+ .startDate(startDate)//
+ .endDate(endDate)//
+ .dateFormat("dd MMMM yyyy")//
+ .locale("en");//
+
+ final Response<CommandProcessingResult> createResponse =
loanInterestPauseApi.createInterestPause(loanId, request).execute();
+ ErrorHelper.checkSuccessfulApiCall(createResponse);
+ }
+}
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature
new file mode 100644
index 000000000..311e7ba25
--- /dev/null
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature
@@ -0,0 +1,74 @@
+@InterestPauseFeature
+Feature: Loan interest pause on repayment schedule
+
+ Scenario: S1 - pause calculation within same period, interestRecalculation =
true
+ When Admin sets the business date to "1 January 2024"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct |
submitted on date | with Principal | ANNUAL interest rate % | interest type
| interest calculation period | amortization type | loanTermFrequency |
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType |
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment |
interest free period | Payment strategy |
+ | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01
January 2024 | 100 | 7 | DECLINING_BALANCE |
DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS
| 1 | MONTHS | 6 | 0
| 0 | 0 |
ADVANCED_PAYMENT_ALLOCATION |
+ Then Loan Repayment schedule has 6 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan | Principal
due | Interest | Fees | Penalties | Due | Paid | In advance | Late |
Outstanding |
+ | | | 01 January 2024 | | 100.0 |
| | 0.0 | | 0.0 | | | | 0.0
|
+ | 1 | 31 | 01 February 2024 | | 83.57 | 16.43
| 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 2 | 29 | 01 March 2024 | | 67.05 | 16.52
| 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 3 | 31 | 01 April 2024 | | 50.43 | 16.62
| 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 4 | 30 | 01 May 2024 | | 33.71 | 16.72
| 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 5 | 31 | 01 June 2024 | | 16.9 | 16.81
| 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 6 | 30 | 01 July 2024 | | 0.0 | 16.9
| 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0
|
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0
| 0 | 102.05 |
+ And Admin successfully approves the loan on "1 January 2024" with "100"
amount and expected disbursement date on "1 January 2024"
+ And Admin successfully disburse the loan on "1 January 2024" with "100"
EUR transaction amount
+ When Admin sets the business date to "1 February 2024"
+ And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01
EUR transaction amount
+ And Customer creates interest pause with start date "05 February 2024" and
end date "10 February 2024"
+ Then Loan Repayment schedule has 6 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 01 January 2024 | | 100.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 |
16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0
| 0.0 |
+ | 2 | 29 | 01 March 2024 | | 66.95 |
16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 3 | 31 | 01 April 2024 | | 50.33 |
16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 4 | 30 | 01 May 2024 | | 33.61 |
16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 5 | 31 | 01 June 2024 | | 16.8 |
16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 6 | 30 | 01 July 2024 | | 0.0 |
16.8 | 0.1 | 0.0 | 0.0 | 16.9 | 0.0 | 0.0 | 0.0
| 16.9 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 100 | 1.95 | 0 | 0 | 101.95 | 17.01 | 0
| 0 | 84.94 |
+
+ Scenario: S2 - pause calculation between two periods, interestRecalculation
= true
+ When Admin sets the business date to "1 January 2024"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct |
submitted on date | with Principal | ANNUAL interest rate % | interest type
| interest calculation period | amortization type | loanTermFrequency |
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType |
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment |
interest free period | Payment strategy |
+ | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01
January 2024 | 100 | 7 | DECLINING_BALANCE |
DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS
| 1 | MONTHS | 6 | 0
| 0 | 0 |
ADVANCED_PAYMENT_ALLOCATION |
+ Then Loan Repayment schedule has 6 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan | Principal
due | Interest | Fees | Penalties | Due | Paid | In advance | Late |
Outstanding |
+ | | | 01 January 2024 | | 100.0 |
| | 0.0 | | 0.0 | | | | 0.0
|
+ | 1 | 31 | 01 February 2024 | | 83.57 | 16.43
| 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 2 | 29 | 01 March 2024 | | 67.05 | 16.52
| 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 3 | 31 | 01 April 2024 | | 50.43 | 16.62
| 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 4 | 30 | 01 May 2024 | | 33.71 | 16.72
| 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 5 | 31 | 01 June 2024 | | 16.9 | 16.81
| 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01
|
+ | 6 | 30 | 01 July 2024 | | 0.0 | 16.9
| 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0
|
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0
| 0 | 102.05 |
+ And Admin successfully approves the loan on "1 January 2024" with "100"
amount and expected disbursement date on "1 January 2024"
+ And Admin successfully disburse the loan on "1 January 2024" with "100"
EUR transaction amount
+ When Admin sets the business date to "1 February 2024"
+ And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01
EUR transaction amount
+ And Customer creates interest pause with start date "10 February 2024" and
end date "10 March 2024"
+ Then Loan Repayment schedule has 6 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 01 January 2024 | | 100.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 |
16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0
| 0.0 |
+ | 2 | 29 | 01 March 2024 | | 66.69 |
16.88 | 0.13 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 3 | 31 | 01 April 2024 | | 49.96 |
16.73 | 0.28 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 4 | 30 | 01 May 2024 | | 33.24 |
16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 5 | 31 | 01 June 2024 | | 16.42 |
16.82 | 0.19 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0
| 17.01 |
+ | 6 | 30 | 01 July 2024 | | 0.0 |
16.42 | 0.1 | 0.0 | 0.0 | 16.52 | 0.0 | 0.0 | 0.0
| 16.52 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 100 | 1.57 | 0 | 0 | 101.57 | 17.01 | 0
| 0 | 84.56 |
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java
index 3d5b0e520..b7e1ec1fa 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java
@@ -22,6 +22,7 @@ import static
org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.ACTIVE
import static
org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType.INTEREST_PAUSE;
import static
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE;
+import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@@ -43,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.springframework.transaction.annotation.Transactional;
@AllArgsConstructor
@@ -51,24 +53,26 @@ public class InterestPauseWritePlatformServiceImpl
implements InterestPauseWrite
private final LoanTermVariationsRepository loanTermVariationsRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper;
+ private final LoanAssembler loanAssembler;
@Override
- public CommandProcessingResult createInterestPause(ExternalId
loanExternalId, String startDateString, String endDateString,
- String dateFormat, String locale) {
+ public CommandProcessingResult createInterestPause(final ExternalId
loanExternalId, final String startDateString,
+ final String endDateString, final String dateFormat, final String
locale) {
final LocalDate startDate = parseDate(startDateString, dateFormat,
locale);
final LocalDate endDate = parseDate(endDateString, dateFormat, locale);
+ final Loan loan = loanAssembler.assembleFrom(loanExternalId, false);
- return
processInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanExternalId),
startDate, endDate, dateFormat,
- locale);
+ return processInterestPause(loan, startDate, endDate, dateFormat,
locale);
}
@Override
- public CommandProcessingResult createInterestPause(Long loanId, String
startDateString, String endDateString, String dateFormat,
- String locale) {
+ public CommandProcessingResult createInterestPause(final Long loanId,
final String startDateString, final String endDateString,
+ final String dateFormat, final String locale) {
final LocalDate startDate = parseDate(startDateString, dateFormat,
locale);
final LocalDate endDate = parseDate(endDateString, dateFormat, locale);
+ final Loan loan = loanAssembler.assembleFrom(loanId, false);
- return
processInterestPause(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId),
startDate, endDate, dateFormat, locale);
+ return processInterestPause(loan, startDate, endDate, dateFormat,
locale);
}
@Override
@@ -131,14 +135,18 @@ public class InterestPauseWritePlatformServiceImpl
implements InterestPauseWrite
.with(Map.of("startDate", startDate.toString(), "endDate",
endDate.toString())).build();
}
- private CommandProcessingResult processInterestPause(Loan loan, LocalDate
startDate, LocalDate endDate, String dateFormat,
- String locale) {
+ private CommandProcessingResult processInterestPause(final Loan loan,
final LocalDate startDate, final LocalDate endDate,
+ String dateFormat, String locale) {
validateActiveLoan(loan);
validateInterestPauseDates(loan, startDate, endDate, dateFormat,
locale, null);
- LoanTermVariations variation = new
LoanTermVariations(INTEREST_PAUSE.getValue(), startDate, null, endDate, false,
loan);
+ final LoanTermVariations variation = new
LoanTermVariations(INTEREST_PAUSE.getValue(), startDate, BigDecimal.ZERO,
endDate, false,
+ loan);
+
+ final LoanTermVariations savedVariation =
loanTermVariationsRepository.saveAndFlush(variation);
- LoanTermVariations savedVariation =
loanTermVariationsRepository.saveAndFlush(variation);
+ loan.getLoanTermVariations().add(savedVariation);
+ loan.reprocessTransactions();
return new
CommandProcessingResultBuilder().withEntityId(savedVariation.getId()).build();
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java
index 8a9320154..5b8e0e79a 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsDataWrapper.java
@@ -23,16 +23,23 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
+import lombok.Getter;
import org.apache.fineract.infrastructure.core.service.DateUtils;
public class LoanTermVariationsDataWrapper {
+ @Getter
private final List<LoanTermVariationsData> exceptionData;
private ListIterator<LoanTermVariationsData> iterator;
+ @Getter
private final List<LoanTermVariationsData> interestRateChanges;
+ @Getter
private final List<LoanTermVariationsData> interestRateFromInstallment;
+ @Getter
private final List<LoanTermVariationsData> dueDateVariation;
private ListIterator<LoanTermVariationsData> dueDateIterator;
+ @Getter
+ private final List<LoanTermVariationsData> interestPauseVariations;
public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData>
exceptionData) {
if (exceptionData == null) {
@@ -43,6 +50,7 @@ public class LoanTermVariationsDataWrapper {
this.interestRateChanges = new ArrayList<>();
this.dueDateVariation = new ArrayList<>();
this.interestRateFromInstallment = new ArrayList<>();
+ this.interestPauseVariations = new ArrayList<>();
deriveLoanTermVariations();
}
@@ -80,18 +88,6 @@ public class LoanTermVariationsDataWrapper {
return this.dueDateIterator.previous();
}
- public List<LoanTermVariationsData> getInterestRateChanges() {
- return this.interestRateChanges;
- }
-
- public List<LoanTermVariationsData> getDueDateVariation() {
- return this.dueDateVariation;
- }
-
- public List<LoanTermVariationsData> getExceptionData() {
- return this.exceptionData;
- }
-
public void setExceptionData(final List<LoanTermVariationsData>
exceptionData) {
clearTerms();
this.exceptionData.addAll(exceptionData);
@@ -103,10 +99,7 @@ public class LoanTermVariationsDataWrapper {
this.interestRateChanges.clear();
this.dueDateVariation.clear();
this.interestRateFromInstallment.clear();
- }
-
- public List<LoanTermVariationsData> getInterestRateFromInstallment() {
- return this.interestRateFromInstallment;
+ this.interestPauseVariations.clear();
}
public int adjustNumberOfRepayments() {
@@ -133,12 +126,11 @@ public class LoanTermVariationsDataWrapper {
}
public boolean hasExceptionVariation(final LocalDate date,
ListIterator<LoanTermVariationsData> exceptionDataListIterator) {
- ListIterator<LoanTermVariationsData> iterator =
exceptionDataListIterator;
- return hasNext(date, iterator);
+ return hasNext(date, exceptionDataListIterator);
}
public void updateLoanTermVariationsData(final
List<LoanTermVariationsData> exceptionData) {
- if (this.exceptionData != null && exceptionData != null &&
exceptionData.size() > 0) {
+ if (this.exceptionData != null && exceptionData != null &&
!exceptionData.isEmpty()) {
this.exceptionData.addAll(exceptionData);
deriveLoanTermVariations();
}
@@ -153,12 +145,15 @@ public class LoanTermVariationsDataWrapper {
this.dueDateVariation.add(loanTermVariationsData);
} else if
(loanTermVariationsData.getTermVariationType().isInterestRateFromInstallment())
{
this.interestRateFromInstallment.add(loanTermVariationsData);
+ } else if
(loanTermVariationsData.getTermVariationType().isInterestPauseVariation()) {
+ this.interestPauseVariations.add(loanTermVariationsData);
}
}
Collections.sort(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
this.exceptionData.removeAll(this.interestRateFromInstallment);
+ this.exceptionData.removeAll(this.interestPauseVariations);
this.iterator = this.exceptionData.listIterator();
this.dueDateIterator = this.dueDateVariation.listIterator();
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
index 21e4655d3..67fd9bf1f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTermVariationType.java
@@ -98,6 +98,10 @@ public enum LoanTermVariationType {
return
this.value.equals(LoanTermVariationType.INTEREST_RATE.getValue());
}
+ public boolean isInterestPauseVariation() {
+ return
this.value.equals(LoanTermVariationType.INTEREST_PAUSE.getValue());
+ }
+
public boolean isPrincipalAmountVariation() {
return
this.value.equals(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue());
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
index d8c12da83..59f8dc92b 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.util.Map;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -30,6 +31,12 @@ public interface LoanAssembler {
Loan assembleFrom(Long accountId);
+ Loan assembleFrom(Long accountId, boolean loadLazyCollections);
+
+ Loan assembleFrom(ExternalId externalId);
+
+ Loan assembleFrom(ExternalId externalId, boolean loadLazyCollections);
+
Loan assembleFrom(JsonCommand command);
void setHelpers(Loan loanAccount);
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 57b50552b..e743db6fd 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -261,7 +261,12 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
final LoanTermVariationsData interestRateChange, final
ProgressiveLoanInterestScheduleModel scheduleModel) {
final LocalDate interestRateChangeSubmittedOnDate =
interestRateChange.getTermVariationApplicableFrom();
final BigDecimal newInterestRate =
interestRateChange.getDecimalValue();
- emiCalculator.changeInterestRate(scheduleModel,
interestRateChangeSubmittedOnDate, newInterestRate);
+ if
(interestRateChange.getTermVariationType().isInterestPauseVariation()) {
+ final LocalDate pauseEndDate = interestRateChange.getDateValue();
+ emiCalculator.applyInterestPause(scheduleModel,
interestRateChangeSubmittedOnDate, pauseEndDate);
+ } else {
+ emiCalculator.changeInterestRate(scheduleModel,
interestRateChangeSubmittedOnDate, newInterestRate);
+ }
processInterestRateChangeOnInstallments(scheduleModel,
interestRateChangeSubmittedOnDate, installments);
}
@@ -820,9 +825,14 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
@NotNull
private List<ChangeOperation> createSortedChangeList(final
LoanTermVariationsDataWrapper loanTermVariations,
final List<LoanTransaction> loanTransactions, final
Set<LoanCharge> charges) {
- List<ChangeOperation> changeOperations = new ArrayList<>();
- if (loanTermVariations != null &&
!loanTermVariations.getInterestRateFromInstallment().isEmpty()) {
-
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().stream().map(ChangeOperation::new).toList());
+ final List<ChangeOperation> changeOperations = new ArrayList<>();
+ if (loanTermVariations != null) {
+ if (!loanTermVariations.getInterestPauseVariations().isEmpty()) {
+
changeOperations.addAll(loanTermVariations.getInterestPauseVariations().stream().map(ChangeOperation::new).toList());
+ }
+ if
(!loanTermVariations.getInterestRateFromInstallment().isEmpty()) {
+
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().stream().map(ChangeOperation::new).toList());
+ }
}
if (charges != null) {
changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList());
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java
index 3d4824195..d21b1defa 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java
@@ -23,6 +23,7 @@ import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.util.Optional;
+import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@@ -34,6 +35,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
@Getter
@ToString(exclude = { "repaymentPeriod" })
@EqualsAndHashCode(exclude = { "repaymentPeriod" })
+@AllArgsConstructor
public class InterestPeriod implements Comparable<InterestPeriod> {
private final RepaymentPeriod repaymentPeriod;
@@ -49,25 +51,13 @@ public class InterestPeriod implements
Comparable<InterestPeriod> {
private Money balanceCorrectionAmount;
private Money outstandingLoanBalance;
private final MathContext mc;
-
- public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate,
LocalDate dueDate, BigDecimal rateFactor,
- BigDecimal rateFactorTillPeriodDueDate, Money disbursementAmount,
Money balanceCorrectionAmount, Money outstandingLoanBalance,
- MathContext mc) {
- this.repaymentPeriod = repaymentPeriod;
- this.fromDate = fromDate;
- this.dueDate = dueDate;
- this.rateFactor = rateFactor;
- this.rateFactorTillPeriodDueDate = rateFactorTillPeriodDueDate;
- this.disbursementAmount = disbursementAmount;
- this.balanceCorrectionAmount = balanceCorrectionAmount;
- this.outstandingLoanBalance = outstandingLoanBalance;
- this.mc = mc;
- }
+ private final boolean isPaused;
public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod
interestPeriod) {
this(repaymentPeriod, interestPeriod.getFromDate(),
interestPeriod.getDueDate(), interestPeriod.getRateFactor(),
interestPeriod.getRateFactorTillPeriodDueDate(),
interestPeriod.getDisbursementAmount(),
- interestPeriod.getBalanceCorrectionAmount(),
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc());
+ interestPeriod.getBalanceCorrectionAmount(),
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(),
+ interestPeriod.isPaused());
}
@Override
@@ -84,6 +74,10 @@ public class InterestPeriod implements
Comparable<InterestPeriod> {
}
public Money getCalculatedDueInterest() {
+ if (isPaused) {
+ return Money.zero(outstandingLoanBalance.getCurrencyData(), mc);
+ }
+
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
final BigDecimal interestDueTillRepaymentDueDate =
lengthTillPeriodDueDate == 0 //
? BigDecimal.ZERO //
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
index 543ecb776..8c9e88091 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
@@ -160,6 +160,24 @@ public class ProgressiveLoanInterestScheduleModel {
.findFirst();//
}
+ public Optional<RepaymentPeriod>
updateInterestPeriodsForInterestPause(final LocalDate fromDate, final LocalDate
endDate) {
+ if (fromDate == null || endDate == null) {
+ return Optional.empty();
+ }
+
+ final List<RepaymentPeriod> affectedPeriods =
repaymentPeriods.stream()//
+ .filter(period -> isPeriodInRange(period, fromDate, endDate))//
+ .toList();
+ affectedPeriods.forEach(period -> insertInterestPausePeriods(period,
fromDate, endDate));
+
+ return affectedPeriods.stream().findFirst();
+ }
+
+ private boolean isPeriodInRange(final RepaymentPeriod repaymentPeriod,
final LocalDate fromDate, final LocalDate endDate) {
+ return DateUtils.isDateInRangeFromExclusiveToInclusive(fromDate,
repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate())
+ || DateUtils.isDateInRangeFromExclusiveToInclusive(endDate,
repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate());
+ }
+
Optional<RepaymentPeriod> findRepaymentPeriodForBalanceChange(final
LocalDate balanceChangeDate) {
if (balanceChangeDate == null) {
return Optional.empty();
@@ -192,7 +210,8 @@ public class ProgressiveLoanInterestScheduleModel {
};
}
- Optional<InterestPeriod> findInterestPeriodForBalanceChange(final
RepaymentPeriod repaymentPeriod, final LocalDate balanceChangeDate) {
+ private Optional<InterestPeriod> findInterestPeriodForBalanceChange(final
RepaymentPeriod repaymentPeriod,
+ final LocalDate balanceChangeDate) {
if (repaymentPeriod == null || balanceChangeDate == null) {
return Optional.empty();
}
@@ -203,26 +222,63 @@ public class ProgressiveLoanInterestScheduleModel {
void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final
LocalDate balanceChangeDate, final Money disbursedAmount,
final Money correctionAmount) {
- final InterestPeriod previousInterestPeriod;
- if (balanceChangeDate.isAfter(repaymentPeriod.getFromDate())) {
- previousInterestPeriod =
repaymentPeriod.getInterestPeriods().get(repaymentPeriod.getInterestPeriods().size()
- 1);//
- } else {
- previousInterestPeriod =
repaymentPeriod.getInterestPeriods().stream()
- .filter(ip -> balanceChangeDate.isAfter(ip.getFromDate())
&& !balanceChangeDate.isAfter(ip.getDueDate()))//
- .reduce((first, second) -> second)//
- .orElse(repaymentPeriod.getInterestPeriods().get(0));
- }
- LocalDate originalDueDate = previousInterestPeriod.getDueDate();
- LocalDate newDueDate =
balanceChangeDate.isBefore(previousInterestPeriod.getFromDate()) ?
previousInterestPeriod.getFromDate()
- :
balanceChangeDate.isAfter(previousInterestPeriod.getDueDate()) ?
previousInterestPeriod.getDueDate() : balanceChangeDate;
+ final InterestPeriod previousInterestPeriod =
findPreviousInterestPeriod(repaymentPeriod, balanceChangeDate);
+ final LocalDate originalDueDate = previousInterestPeriod.getDueDate();
+ final LocalDate newDueDate =
calculateNewDueDate(previousInterestPeriod, balanceChangeDate);
+
previousInterestPeriod.setDueDate(newDueDate);
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
- final InterestPeriod interestPeriod = new
InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(),
originalDueDate,
- BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, mc);
+
+ final InterestPeriod interestPeriod = new
InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO,
+ BigDecimal.ZERO, zero, zero, zero, mc, false);
repaymentPeriod.getInterestPeriods().add(interestPeriod);
}
+ private void insertInterestPausePeriods(final RepaymentPeriod
repaymentPeriod, final LocalDate fromDate, final LocalDate endDate) {
+ final InterestPeriod previousInterestPeriod =
findPreviousInterestPeriod(repaymentPeriod, fromDate);
+ final LocalDate originalFromDate =
previousInterestPeriod.getFromDate();
+ final LocalDate originalDueDate = previousInterestPeriod.getDueDate();
+ final LocalDate newDueDate =
calculateNewDueDate(previousInterestPeriod, fromDate.minusDays(1));
+
+ if (fromDate.isAfter(originalFromDate) &&
endDate.isBefore(originalDueDate)) {
+ previousInterestPeriod.setDueDate(newDueDate);
+ final InterestPeriod interestPausePeriod = new
InterestPeriod(repaymentPeriod, newDueDate, endDate, BigDecimal.ZERO,
+ BigDecimal.ZERO, zero, zero, zero, mc, true);
+ repaymentPeriod.getInterestPeriods().add(interestPausePeriod);
+ final InterestPeriod interestAfterPausePeriod = new
InterestPeriod(repaymentPeriod, endDate, originalDueDate, BigDecimal.ZERO,
+ BigDecimal.ZERO, zero, zero, zero, mc, false);
+ repaymentPeriod.getInterestPeriods().add(interestAfterPausePeriod);
+ }
+
+ if (fromDate.isAfter(originalFromDate) &&
endDate.isAfter(originalDueDate)) {
+ previousInterestPeriod.setDueDate(newDueDate);
+ final InterestPeriod interestPausePeriod = new
InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO,
+ BigDecimal.ZERO, zero, zero, zero, mc, true);
+ repaymentPeriod.getInterestPeriods().add(interestPausePeriod);
+ }
+
+ if (fromDate.isBefore(originalFromDate) &&
endDate.isBefore(originalDueDate)) {
+ repaymentPeriod.getInterestPeriods().clear();
+ final InterestPeriod interestPausePeriod = new
InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO,
+ BigDecimal.ZERO, zero, zero, zero, mc, true);
+ repaymentPeriod.getInterestPeriods().add(interestPausePeriod);
+ InterestPeriod interestAfterPausePeriod = new
InterestPeriod(repaymentPeriod, endDate, originalDueDate, BigDecimal.ZERO,
+ BigDecimal.ZERO, zero, zero, zero, mc, false);
+ repaymentPeriod.getInterestPeriods().add(interestAfterPausePeriod);
+ }
+ }
+
+ private InterestPeriod findPreviousInterestPeriod(final RepaymentPeriod
repaymentPeriod, final LocalDate date) {
+ if (date.isAfter(repaymentPeriod.getFromDate())) {
+ return
repaymentPeriod.getInterestPeriods().get(repaymentPeriod.getInterestPeriods().size()
- 1);
+ } else {
+ return repaymentPeriod.getInterestPeriods().stream()
+ .filter(ip -> date.isAfter(ip.getFromDate()) &&
!date.isAfter(ip.getDueDate())).reduce((first, second) -> second)
+ .orElse(repaymentPeriod.getInterestPeriods().get(0));
+ }
+ }
+
public Money getTotalDueInterest() {
return repaymentPeriods().stream().flatMap(rp ->
rp.getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest))
.reduce(zero(), Money::plus);
@@ -282,4 +338,10 @@ public class ProgressiveLoanInterestScheduleModel {
}
}
}
+
+ private LocalDate calculateNewDueDate(final InterestPeriod
previousInterestPeriod, final LocalDate date) {
+ return date.isBefore(previousInterestPeriod.getFromDate()) ?
previousInterestPeriod.getFromDate()
+ : date.isAfter(previousInterestPeriod.getDueDate()) ?
previousInterestPeriod.getDueDate() : date;
+ }
+
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java
index 5ecd47ca6..9edba49c0 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java
@@ -73,7 +73,7 @@ public class RepaymentPeriod {
this.interestPeriods = new ArrayList<>();
// There is always at least 1 interest period, by default with same
from-due date as repayment period
getInterestPeriods().add(new InterestPeriod(this, getFromDate(),
getDueDate(), BigDecimal.ZERO, BigDecimal.ZERO, getZero(mc),
- getZero(mc), getZero(mc), mc));
+ getZero(mc), getZero(mc), mc, false));
this.paidInterest = getZero(mc);
this.paidPrincipal = getZero(mc);
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
index d6053752c..18126360b 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
@@ -209,16 +209,22 @@ public class ProgressiveLoanScheduleGenerator implements
LoanScheduleGenerator {
private void applyInterestRateChangesOnPeriod(final LoanApplicationTerms
loanApplicationTerms,
final LoanScheduleModelRepaymentPeriod repaymentPeriod, final
ProgressiveLoanInterestScheduleModel interestScheduleModel) {
- if (loanApplicationTerms.getLoanTermVariations() != null) {
- for (var interestRateChange :
loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) {
- final LocalDate interestRateSubmittedOnDate =
interestRateChange.getTermVariationApplicableFrom();
- final BigDecimal newInterestRate =
interestRateChange.getDecimalValue();
- if
(interestRateSubmittedOnDate.isAfter(repaymentPeriod.getFromDate())
- &&
!interestRateSubmittedOnDate.isAfter(repaymentPeriod.getDueDate())) {
- emiCalculator.changeInterestRate(interestScheduleModel,
interestRateSubmittedOnDate, newInterestRate);
- }
- }
+ if (loanApplicationTerms.getLoanTermVariations() == null) {
+ return;
}
+
+
loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment().stream()
+ .filter(change ->
isDateWithinPeriod(change.getTermVariationApplicableFrom(), repaymentPeriod))
+ .forEach(change ->
emiCalculator.changeInterestRate(interestScheduleModel,
change.getTermVariationApplicableFrom(),
+ change.getDecimalValue()));
+
+
loanApplicationTerms.getLoanTermVariations().getInterestPauseVariations().stream()
+ .filter(pause ->
isDateWithinPeriod(pause.getTermVariationApplicableFrom(),
repaymentPeriod)).forEach(pause -> emiCalculator
+ .applyInterestPause(interestScheduleModel,
pause.getTermVariationApplicableFrom(), pause.getDateValue()));
+ }
+
+ private boolean isDateWithinPeriod(final LocalDate date, final
LoanScheduleModelRepaymentPeriod period) {
+ return date.isAfter(period.getFromDate()) &&
!date.isAfter(period.getDueDate());
}
private void prepareDisbursementsOnLoanApplicationTerms(final
LoanApplicationTerms loanApplicationTerms) {
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
index 193e859da..f9b206a69 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
@@ -77,4 +77,6 @@ public interface EMICalculator {
OutstandingDetails
getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model,
LocalDate targetDate);
Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate subjectDate);
+
+ void applyInterestPause(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate fromDate, LocalDate endDate);
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
index e9d67f72d..fa349cbf0 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
@@ -792,6 +792,20 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
.reduce(scheduleModel.zero(), Money::add); //
}
+ @Override
+ public void applyInterestPause(final ProgressiveLoanInterestScheduleModel
scheduleModel, final LocalDate fromDate,
+ final LocalDate endDate) {
+ scheduleModel.updateInterestPeriodsForInterestPause(fromDate, endDate)
+ .ifPresent(repaymentPeriod ->
calculateRateFactorsForInterestPause(scheduleModel,
repaymentPeriod.getFromDate()));
+ }
+
+ private void calculateRateFactorsForInterestPause(final
ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate startDate) {
+ final List<RepaymentPeriod> relatedRepaymentPeriods =
scheduleModel.getRelatedRepaymentPeriods(startDate);
+ calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
+ calculateOutstandingBalance(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ }
+
private long getUncountablePeriods(final List<RepaymentPeriod>
relatedRepaymentPeriods, final Money originalEmi) {
return relatedRepaymentPeriods.stream() //
.filter(repaymentPeriod ->
originalEmi.isLessThan(repaymentPeriod.getTotalPaidAmount())) //
diff --git
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
index 852e778a6..694cb11c9 100644
---
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
+++
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
@@ -1342,6 +1342,82 @@ class ProgressiveEMICalculatorTest {
checkDailyInterest(interestModel, dueDate, startDay, 31, 0.38, 9.03);
}
+ @Test
+ public void
test_singleInterestPauseAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2,
1)),
+ repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3,
1)),
+ repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4,
1)),
+ repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5,
1)),
+ repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6,
1)),
+ repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7,
1)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(7.0);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(100.0);
+ emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
+ emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024,
2, 5), LocalDate.of(2024, 2, 10));
+
+ checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43,
83.57);
+ checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58,
16.43, 83.57);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.000603448276, 0.05, 0.39,
16.62, 66.95);
+ checkPeriod(interestSchedule, 1, 1, 17.01, 0.001206896552, 0.0, 0.39,
16.62, 66.95);
+ checkPeriod(interestSchedule, 1, 2, 17.01, 0.004022988506, 0.34, 0.39,
16.62, 66.95);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39,
16.62, 50.33);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29,
16.72, 33.61);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.2, 0.2,
16.81, 16.8);
+ checkPeriod(interestSchedule, 5, 0, 16.9, 0.005833333333, 0.1, 0.1,
16.8, 0.0);
+ }
+
+ @Test
+ public void
test_interestPauseBetweenTwoPeriodsAmt100_dayInYears360_daysInMonth30_repayEvery1Month()
{
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2,
1)),
+ repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3,
1)),
+ repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4,
1)),
+ repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5,
1)),
+ repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6,
1)),
+ repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7,
1)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(7.0);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(100.0);
+ emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
+ emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024,
2, 10), LocalDate.of(2024, 3, 10));
+
+ checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43,
83.57);
+ checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58,
16.43, 83.57);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.001609195402, 0.13, 0.13,
16.88, 66.69);
+ checkPeriod(interestSchedule, 1, 1, 17.01, 0.004224137931, 0.0, 0.13,
16.88, 66.69);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.0, 0.28,
16.73, 49.96);
+ checkPeriod(interestSchedule, 2, 1, 17.01, 0.004139784946, 0.28, 0.28,
16.73, 49.96);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29,
16.72, 33.24);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19,
16.82, 16.42);
+ checkPeriod(interestSchedule, 5, 0, 16.52, 0.005833333333, 0.1, 0.1,
16.42, 0.0);
+ }
+
@Test
public void
test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
index 8c08bae3a..d56285305 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
@@ -139,15 +139,36 @@ public class LoanAssemblerImpl implements LoanAssembler {
@Override
public Loan assembleFrom(final Long accountId) {
- final Loan loanAccount =
this.loanRepository.findOneWithNotFoundDetection(accountId, true);
- loanAccount.setHelpers(defaultLoanLifecycleStateMachine,
this.loanRepaymentScheduleTransactionProcessorFactory);
+ return assembleFrom(accountId, true);
+ }
+
+ @Override
+ public Loan assembleFrom(final Long accountId, final boolean
loadLazyCollections) {
+ final Loan loanAccount =
loanRepository.findOneWithNotFoundDetection(accountId, loadLazyCollections);
+ setHelpers(loanAccount);
+
+ return loanAccount;
+ }
+
+ @Override
+ public Loan assembleFrom(final ExternalId externalId) {
+ final Loan loanAccount =
loanRepository.findOneWithNotFoundDetection(externalId, true);
+ setHelpers(loanAccount);
+
+ return loanAccount;
+ }
+
+ @Override
+ public Loan assembleFrom(final ExternalId externalId, final boolean
loadLazyCollections) {
+ final Loan loanAccount =
loanRepository.findOneWithNotFoundDetection(externalId, loadLazyCollections);
+ setHelpers(loanAccount);
return loanAccount;
}
@Override
public void setHelpers(final Loan loanAccount) {
- loanAccount.setHelpers(defaultLoanLifecycleStateMachine,
this.loanRepaymentScheduleTransactionProcessorFactory);
+ loanAccount.setHelpers(defaultLoanLifecycleStateMachine,
loanRepaymentScheduleTransactionProcessorFactory);
}
@Override
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index b7b9a955d..1c1e75fe7 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -471,7 +471,7 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(InterestPauseWritePlatformService.class)
public InterestPauseWritePlatformService
interestPauseWritePlatformService(LoanTermVariationsRepository
loanTermVariationsRepository,
- LoanRepositoryWrapper loanRepositoryWrapper) {
- return new
InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository,
loanRepositoryWrapper);
+ LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler
loanAssembler) {
+ return new
InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository,
loanRepositoryWrapper, loanAssembler);
}
}