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
commit 9ab08ec428d54c4a8b7520577df5f95de3f29caf Author: Soma Sörös <[email protected]> AuthorDate: Mon May 12 16:58:13 2025 +0200 FINERACT-2280: Interest pause breaks repayment schedule while loan is added with interest rate zero --- .../InterestPauseWritePlatformServiceImpl.java | 5 ++ .../integrationtests/LoanInterestPauseApiTest.java | 90 ++++++++++++++++++---- .../common/loans/LoanTransactionHelper.java | 9 +++ 3 files changed, 87 insertions(+), 17 deletions(-) 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 18e84c3b0c..1524ae937d 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 @@ -184,6 +184,11 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite "Interest pause is only supported for progressive loans."); } + if (!loan.isInterestBearing()) { + throw new GeneralPlatformDomainRuleException("loan.must.be.interest.bearing", + "Interest pause is only supported for interest bearing loans."); + } + if (!loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) { throw new GeneralPlatformDomainRuleException("loan.must.have.recalculate.interest.enabled", "Interest pause is only supported for loans with recalculate interest enabled."); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java index c85926ca32..e0cd6de3d5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestPauseApiTest.java @@ -27,6 +27,7 @@ import java.math.BigDecimal; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.AdvancedPaymentData; +import org.apache.fineract.client.models.CommandProcessingResult; import org.apache.fineract.client.models.PostLoansLoanIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.integrationtests.common.ClientHelper; @@ -35,23 +36,17 @@ import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; -import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import retrofit2.Response; @Slf4j -@ExtendWith(LoanTestLifecycleExtension.class) public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { - private static final Logger LOG = LoggerFactory.getLogger(LoanInterestPauseApiTest.class); - private static RequestSpecification REQUEST_SPEC; private static ResponseSpecification RESPONSE_SPEC; private static ResponseSpecification RESPONSE_SPEC_403; @@ -65,7 +60,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { private static final Integer nonExistLoanId = 99999; private static String externalId; private static final String nonExistExternalId = "7c4fb86f-a778-4d02-b7a8-ec3ec98941fa"; - private Integer clientId; + private Long clientId; private Integer loanProductId; private Integer loanId; private final String loanPrincipalAmount = "10000.00"; @@ -614,20 +609,81 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { } } + Long loan; + + @Test + public void testInterestPauseOnZeroInterestRate() { + runAt("1 September 2019", () -> { + Long loanProductId = loanProductHelper.createLoanProduct(create4IProgressive()).getResourceId(); + loan = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 September 2019", 1200.0, 0.0, 4, null); + disburseLoan(loan, BigDecimal.valueOf(1200.0), "1 September 2019"); + }); + runAt("1 October 2019", () -> { + loanTransactionHelper.makeLoanRepayment(loan, "Repayment", "1 October 2019", 300.0); + }); + runAt("1 November 2019", () -> { + loanTransactionHelper.makeLoanRepayment(loan, "Repayment", "1 November 2019", 300.0); + }); + runAt("1 December 2019", () -> { + loanTransactionHelper.makeLoanRepayment(loan, "Repayment", "1 December 2019", 300.0); + verifyTransactions(loan, // + transaction(1200.0, "Disbursement", "01 September 2019", 1200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0), // + transaction(300.0, "Repayment", "01 October 2019", 900.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(300.0, "Repayment", "01 November 2019", 600.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(300.0, "Repayment", "01 December 2019", 300.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0) // + ); + Response<CommandProcessingResult> response = loanTransactionHelper.createInterestPause(loan, "1 October 2019", + "15 December 2019"); + Assertions.assertEquals(403, response.code()); + verifyTransactions(loan, // + transaction(1200.0, "Disbursement", "01 September 2019", 1200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0), // + transaction(300.0, "Repayment", "01 October 2019", 900.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(300.0, "Repayment", "01 November 2019", 600.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), // + transaction(300.0, "Repayment", "01 December 2019", 300.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0) // + ); + }); + } + + @Test + public void testInterestPauseOnZeroInterestRateRightAfterDisbursement() { + runAt("1 September 2019", () -> { + Long loanProductId = loanProductHelper.createLoanProduct(create4IProgressive()).getResourceId(); + loan = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 September 2019", 1200.0, 0.0, 4, null); + disburseLoan(loan, BigDecimal.valueOf(1200.0), "1 September 2019"); + verifyRepaymentSchedule(loan, // + installment(1200.0, null, "01 September 2019"), // + installment(300.0, 0.0, 300.0, false, "01 October 2019"), // + installment(300.0, 0.0, 300.0, false, "01 November 2019"), // + installment(300.0, 0.0, 300.0, false, "01 December 2019"), // + installment(300.0, 0.0, 300.0, false, "01 January 2020") // + ); + Response<CommandProcessingResult> response = loanTransactionHelper.createInterestPause(loan, "1 October 2019", + "15 December 2019"); + Assertions.assertEquals(403, response.code()); + verifyRepaymentSchedule(loan, // + installment(1200.0, null, "01 September 2019"), // + installment(300.0, 0.0, 300.0, false, "01 October 2019"), // + installment(300.0, 0.0, 300.0, false, "01 November 2019"), // + installment(300.0, 0.0, 300.0, false, "01 December 2019"), // + installment(300.0, 0.0, 300.0, false, "01 January 2020") // + ); + }); + } + /** * create a new client **/ private void createClientEntity() { - this.clientId = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC); - - ClientHelper.verifyClientCreatedOnServer(REQUEST_SPEC, RESPONSE_SPEC, clientId); + this.clientId = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getResourceId(); + Assertions.assertNotNull(clientId); + ClientHelper.verifyClientCreatedOnServer(REQUEST_SPEC, RESPONSE_SPEC, clientId.intValue()); } /** * create a new loan product **/ private void createLoanProductEntity() { - LOG.info("---------------------------------CREATING LOAN PRODUCT------------------------------------------"); + log.info("---------------------------------CREATING LOAN PRODUCT------------------------------------------"); final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE; final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_ADJUST_LAST_UNPAID_PERIOD; @@ -651,14 +707,14 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { .build(); loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON); - LOG.info("Successfully created loan product (ID:{}) ", loanProductId); + log.info("Successfully created loan product (ID:{}) ", loanProductId); } /** * submit a new loan application, approve and disburse the loan **/ private void createLoanEntity() { - LOG.info("---------------------------------NEW LOAN APPLICATION------------------------------------------"); + log.info("---------------------------------NEW LOAN APPLICATION------------------------------------------"); String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount) .withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsDays().withNumberOfRepayments(numberOfRepayments) @@ -670,7 +726,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { loanId = LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON); - LOG.info("Sucessfully created loan (ID: {} )", loanId); + log.info("Sucessfully created loan (ID: {} )", loanId); approveLoanApplication(); disburseLoan(); @@ -683,7 +739,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { if (loanId != null) { LOAN_TRANSACTION_HELPER.approveLoan(dateString, loanId); - LOG.info("Successfully approved loan (ID: {} )", loanId); + log.info("Successfully approved loan (ID: {} )", loanId); } } @@ -695,7 +751,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest { if (loanId != null) { LOAN_TRANSACTION_HELPER.disburseLoan(externalId, new PostLoansLoanIdRequest().actualDisbursementDate(dateString) .transactionAmount(new BigDecimal(loanPrincipalAmount)).locale("en").dateFormat("dd MMMM yyyy")); - LOG.info("Successfully disbursed loan (ID: {} )", loanId); + log.info("Successfully disbursed loan (ID: {} )", loanId); } } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 6fcc0b9347..87e78d3b5b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -45,6 +45,7 @@ import java.util.Locale; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.AdvancedPaymentData; +import org.apache.fineract.client.models.CommandProcessingResult; import org.apache.fineract.client.models.DeleteLoansLoanIdChargesChargeIdResponse; import org.apache.fineract.client.models.DeleteLoansLoanIdResponse; import org.apache.fineract.client.models.DisbursementDetail; @@ -66,6 +67,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; import org.apache.fineract.client.models.GetLoansResponse; +import org.apache.fineract.client.models.InterestPauseRequestDto; import org.apache.fineract.client.models.PaymentTypeData; import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest; import org.apache.fineract.client.models.PostLoanProductsRequest; @@ -104,6 +106,7 @@ import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Workbook; +import retrofit2.Response; @Slf4j @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -919,6 +922,12 @@ public class LoanTransactionHelper { .transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en")); } + public Response<CommandProcessingResult> createInterestPause(Long loanId, String startDate, String endDate) { + log.info("Creating interest pause for Loan {} from {} to {}", loanId, startDate, endDate); + return Calls.executeU(FineractClientHelper.getFineractClient().loanInterestPauseApi.createInterestPause(loanId, + new InterestPauseRequestDto().startDate(startDate).endDate(endDate).dateFormat(DATE_FORMAT).locale("en"))); + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest)
