Skip to content

Commit

Permalink
FINERACT-2152: Interest pause during one period and between two periods
Browse files Browse the repository at this point in the history
  • Loading branch information
oleksii-novikov-onix authored and adamsaghy committed Jan 24, 2025
1 parent a9d76ef commit af37e07
Show file tree
Hide file tree
Showing 18 changed files with 414 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -415,6 +417,7 @@ private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) {
templates = retrofit.create(UserGeneratedDocumentsApi.class);
users = retrofit.create(UsersApi.class);
workingDays = retrofit.create(WorkingDaysApi.class);
loanInterestPauseApi = retrofit.create(LoanInterestPauseApi.class);
}

public static Builder builder() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
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;
Expand Down Expand Up @@ -244,4 +245,9 @@ public DefaultApi defaultApi() {
public RescheduleLoansApi rescheduleLoansApi() {
return fineractClient.createService(RescheduleLoansApi.class);
}

@Bean
public LoanInterestPauseApi loanInterestPauseApi() {
return fineractClient.createService(LoanInterestPauseApi.class);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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 |
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -43,6 +44,7 @@
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
Expand All @@ -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
Expand Down Expand Up @@ -131,14 +135,18 @@ private CommandProcessingResult processUpdateInterestPause(Loan loan, Long varia
.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();
}
Expand Down
Loading

0 comments on commit af37e07

Please sign in to comment.