FINERACT-2152: Interest pause during one period and between two periods

This commit is contained in:
Oleksii Novikov
2025-01-24 09:58:02 +01:00
committed by Adam Saghy
parent a9d76ef3dd
commit af37e07f31
18 changed files with 414 additions and 79 deletions
@@ -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() {
@@ -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);
}
}
@@ -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);
}
}
@@ -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 |
@@ -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);
LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation);
final LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation);
loan.getLoanTermVariations().add(savedVariation);
loan.reprocessTransactions();
return new CommandProcessingResultBuilder().withEntityId(savedVariation.getId()).build();
}
@@ -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();
}
@@ -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());
}
@@ -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);
@@ -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());
@@ -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 //
@@ -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;
}
}
@@ -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);
}
@@ -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) {
@@ -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);
}
@@ -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())) //
@@ -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(
@@ -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
@@ -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);
}
}