From eb5f136e677ad2237effb01d66e7f01d204c42d6 Mon Sep 17 00:00:00 2001 From: airajena Date: Thu, 19 Mar 2026 09:58:43 +0530 Subject: [PATCH] FINERACT-2399: Add global config to block transactions on closed/overpaid loans --- .../api/GlobalConfigurationConstants.java | 1 + .../domain/ConfigurationDomainService.java | 2 + .../LoanTransactionValidator.java | 9 + ...ogressiveLoanTransactionValidatorImpl.java | 10 + .../domain/ConfigurationDomainServiceJpa.java | 5 + .../domain/LoanAccountDomainServiceJpa.java | 1 + .../LoanTransactionValidatorImpl.java | 21 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 8 +- .../db/changelog/tenant/changelog-tenant.xml | 1 + ..._transactions_on_closed_overpaid_loans.xml | 37 ++++ ...TransactionsOnClosedOverpaidLoansTest.java | 188 ++++++++++++++++++ .../common/GlobalConfigurationHelper.java | 7 + 12 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_add_configuration_block_transactions_on_closed_overpaid_loans.xml create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/BlockTransactionsOnClosedOverpaidLoansTest.java diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java index 79ba77853c2..d8df075519d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java @@ -85,6 +85,7 @@ public final class GlobalConfigurationConstants { public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT = "allow-force-withdrawal-on-savings-account"; public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT = "force-withdrawal-on-savings-account-limit"; public static final String FORCE_PASSWORD_RESET_ON_FIRST_LOGIN = "force-password-reset-on-first-login"; + public static final String BLOCK_TRANSACTIONS_ON_CLOSED_OVERPAID_LOANS = "block-transactions-on-closed-overpaid-loans"; private GlobalConfigurationConstants() {} } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index f091017bd4d..0c28e746824 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -163,4 +163,6 @@ public interface ConfigurationDomainService { boolean isMaxLoginRetriesEnabled(); Integer retrieveMaxLoginRetries(); + + boolean isBlockTransactionsOnClosedOverpaidLoansEnabled(); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java index 43527f9a335..c0a749d4a6b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java @@ -40,6 +40,11 @@ public interface LoanTransactionValidator { void validateTransaction(String json); + default void validateTransaction(Loan loan, LoanTransactionType loanTransactionType, String json) { + validateTransaction(json); + validateLoanNotClosedOrOverpaidForTransactions(loan, loanTransactionType); + } + void validateChargebackTransaction(String json); void validateNewRepaymentTransaction(String json); @@ -98,4 +103,8 @@ void validateRefund(Loan loan, LoanTransactionType loanTransactionType, LocalDat void validateManualInterestRefundTransaction(String json); void validateClassificationCodeValue(String codeName, Long transactionClassificationId, DataValidatorBuilder baseDataValidator); + + void validateLoanNotClosedOrOverpaidForTransactions(Loan loan); + + void validateLoanNotClosedOrOverpaidForTransactions(Loan loan, LoanTransactionType loanTransactionType); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java index 385d07220c7..ab54f8afd8b 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java @@ -506,6 +506,16 @@ public void validateLoanGroupIsActive(Loan loan) { loanTransactionValidator.validateLoanGroupIsActive(loan); } + @Override + public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan) { + loanTransactionValidator.validateLoanNotClosedOrOverpaidForTransactions(loan); + } + + @Override + public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan, LoanTransactionType loanTransactionType) { + loanTransactionValidator.validateLoanNotClosedOrOverpaidForTransactions(loan, loanTransactionType); + } + @Override public void validateActivityNotBeforeLastTransactionDate(Loan loan, LocalDate activityDate, LoanEvent event) { loanTransactionValidator.validateActivityNotBeforeLastTransactionDate(loan, activityDate, event); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 1b0d7fa7253..3c80dd252f7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -586,4 +586,9 @@ public Integer retrieveMaxLoginRetries() { GlobalConfigurationConstants.MAX_LOGIN_RETRY_ATTEMPTS); return property.getValue() == null ? null : property.getValue().intValue(); } + + @Override + public boolean isBlockTransactionsOnClosedOverpaidLoansEnabled() { + return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.BLOCK_TRANSACTIONS_ON_CLOSED_OVERPAID_LOANS).isEnabled(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 7b8a8100e7c..0b7814337c4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -218,6 +218,7 @@ public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransact final boolean isRecoveryRepayment, final String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone, final boolean isLoanToLoanTransfer) { checkClientOrGroupActive(loan); + loanTransactionValidator.validateLoanNotClosedOrOverpaidForTransactions(loan, repaymentTransactionType); LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan); businessEventNotifierService.notifyPreBusinessEvent(repaymentEvent); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java index 43132cc3286..a2795e600fd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java @@ -37,6 +37,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; @@ -111,6 +112,7 @@ public class LoanTransactionValidatorImpl implements LoanTransactionValidator { private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator; private final LoanDisbursementValidator loanDisbursementValidator; private final CodeValueRepository codeValueRepository; + private final ConfigurationDomainService configurationDomainService; private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { @@ -667,6 +669,23 @@ public void validateLoanGroupIsActive(final Loan loan) { } } + @Override + public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan) { + validateLoanNotClosedOrOverpaidForTransactions(loan, null); + } + + @Override + public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan, LoanTransactionType loanTransactionType) { + boolean blockTransactions = configurationDomainService.isBlockTransactionsOnClosedOverpaidLoansEnabled(); + if (LoanTransactionType.CREDIT_BALANCE_REFUND.equals(loanTransactionType)) { + return; + } + if (blockTransactions && (loan.isClosed() || loan.getStatus().isOverpaid())) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.allowed.on.closed.or.overpaid", + "Monetary transactions are not allowed on closed or overpaid loan accounts", loan.getId()); + } + } + protected void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate, String reversedOrCreated) { for (LoanTransaction txn : loan.getLoanTransactions()) { @@ -789,6 +808,7 @@ public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) { validateLoanClientIsActive(loan); validateLoanHasCurrency(loan); validateLoanGroupIsActive(loan); + validateLoanNotClosedOrOverpaidForTransactions(loan, LoanTransactionType.INTEREST_PAYMENT_WAIVER); loanDownPaymentTransactionValidator.validateLoanStatusIsActiveOrFullyPaidOrOverpaid(loan); validateLoanDisbursementIsBeforeTransactionDate(loan, transactionDate); validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(loan, transactionDate, "created"); @@ -826,6 +846,7 @@ public void validateRefund(String json) { public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate, ScheduleGeneratorDTO scheduleGeneratorDTO) { checkClientOrGroupActive(loan); + validateLoanNotClosedOrOverpaidForTransactions(loan, loanTransactionType); loanDownPaymentTransactionValidator.validateLoanStatusIsActiveOrFullyPaidOrOverpaid(loan); validateActivityNotBeforeClientOrGroupTransferDate(loan, transactionDate); validateRepaymentTypeTransactionNotBeforeAChargeRefund(loan, loanTransactionType, transactionDate); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index dd5e434438e..1986e9bc45b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -687,7 +687,7 @@ private Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { final Throwable realCause = e.getCause(); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); - if (realCause.getMessage().toLowerCase(java.util.Locale.ROOT).contains("external_id_unique")) { + if (realCause.getMessage().toLowerCase(Locale.ROOT).contains("external_id_unique")) { baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).failWithCode("value.must.be.unique"); } if (!dataValidationErrors.isEmpty()) { @@ -705,7 +705,7 @@ private void saveLoanWithDataIntegrityViolationChecks(final Loan loan) { final Throwable realCause = e.getCause(); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); - if (realCause.getMessage().toLowerCase(java.util.Locale.ROOT).contains("external_id_unique")) { + if (realCause.getMessage().toLowerCase(Locale.ROOT).contains("external_id_unique")) { baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).failWithCode("value.must.be.unique"); } if (!dataValidationErrors.isEmpty()) { @@ -1377,9 +1377,6 @@ private void validateLoanTransactionAmountChargeBack(LoanTransaction loanTransac @Transactional @Override public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) { - - this.loanTransactionValidator.validateTransaction(command.json()); - final Map changes = new LinkedHashMap<>(); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); @@ -1390,6 +1387,7 @@ public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final Json final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName); Loan loan = this.loanAssembler.assembleFrom(loanId); + loanTransactionValidator.validateTransaction(loan, LoanTransactionType.WAIVE_INTEREST, command.json()); checkClientOrGroupActive(loan); final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount); diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 5597a2e341e..1a08de513c5 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -240,4 +240,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_add_configuration_block_transactions_on_closed_overpaid_loans.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_add_configuration_block_transactions_on_closed_overpaid_loans.xml new file mode 100644 index 00000000000..aea648110d9 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_add_configuration_block_transactions_on_closed_overpaid_loans.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BlockTransactionsOnClosedOverpaidLoansTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BlockTransactionsOnClosedOverpaidLoansTest.java new file mode 100644 index 00000000000..6bc090db5d0 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BlockTransactionsOnClosedOverpaidLoansTest.java @@ -0,0 +1,188 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.util.ArrayList; +import java.util.HashMap; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.Utils; +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.LoanStatusChecker; +import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.junit.jupiter.api.AfterEach; +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; + +@ExtendWith(LoanTestLifecycleExtension.class) +@Slf4j +public class BlockTransactionsOnClosedOverpaidLoansTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private LoanTransactionHelper loanTransactionHelper; + private LoanTransactionHelper loanTransactionHelperForError; + private GlobalConfigurationHelper globalConfigurationHelper; + private AccountHelper accountHelper; + private ResponseSpecification responseSpecForError; + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.responseSpecForError = new ResponseSpecBuilder().expectStatusCode(403).build(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.loanTransactionHelperForError = new LoanTransactionHelper(this.requestSpec, this.responseSpecForError); + this.globalConfigurationHelper = new GlobalConfigurationHelper(); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + } + + @AfterEach + public void tearDown() { + this.globalConfigurationHelper.manageConfigurations("block-transactions-on-closed-overpaid-loans", false); + } + + @Test + public void testTransactionsOnOverpaidLoan() { + this.globalConfigurationHelper.manageConfigurations("block-transactions-on-closed-overpaid-loans", true); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + + final Integer loanProductID = createLoanProduct(); + final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "01 January 2024"); + + this.loanTransactionHelper.approveLoan("01 January 2024", loanID); + this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("01 January 2024", loanID, "1000"); + + this.loanTransactionHelper.makeRepayment("01 February 2024", 2000.0f, loanID); + + HashMap loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "status"); + LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap); + + ArrayList repaymentErrors = (ArrayList) this.loanTransactionHelperForError.makeRepaymentTypePayment("repayment", + "02 February 2024", 10.0f, loanID, CommonConstants.RESPONSE_ERROR); + Assertions.assertEquals("error.msg.loan.transaction.not.allowed.on.closed.or.overpaid", + repaymentErrors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + assertBlockedForClosedOrOverpaid("goodwillCredit", loanID, "02 February 2024", 10.0f); + assertBlockedForClosedOrOverpaid("merchantIssuedRefund", loanID, "02 February 2024", 10.0f); + assertBlockedForClosedOrOverpaid("payoutRefund", loanID, "02 February 2024", 10.0f); + assertBlockedForClosedOrOverpaid("waiveinterest", loanID, "02 February 2024", 10.0f); + + Float totalOverpaid = (Float) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "totalOverpaid"); + assertNotNull(totalOverpaid); + Assertions.assertTrue(totalOverpaid > 0); + + this.loanTransactionHelper.creditBalanceRefund("03 February 2024", totalOverpaid, null, loanID, ""); + + loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); + LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); + } + + @Test + public void testTransactionsOnClosedLoan() { + this.globalConfigurationHelper.manageConfigurations("block-transactions-on-closed-overpaid-loans", true); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + final Integer loanProductID = createLoanProduct(); + final Integer loanID = applyForLoanApplication(clientID, loanProductID, "1000", "01 January 2024"); + + this.loanTransactionHelper.approveLoan("01 January 2024", loanID); + this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("01 January 2024", loanID, "1000"); + + HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID); + Float totalOutstanding = (Float) loanSummary.get("totalOutstanding"); + HashMap repaymentTransaction = this.loanTransactionHelper.makeRepayment("01 February 2024", totalOutstanding, loanID); + Integer repaymentTransactionId = ((Number) repaymentTransaction.get("resourceId")).intValue(); + + HashMap loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "status"); + LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); + + ArrayList repaymentErrors = (ArrayList) this.loanTransactionHelperForError.makeRepaymentTypePayment("repayment", + "02 February 2024", 10.0f, loanID, CommonConstants.RESPONSE_ERROR); + Assertions.assertEquals("error.msg.loan.transaction.not.allowed.on.closed.or.overpaid", + repaymentErrors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + this.loanTransactionHelper.reverseRepayment(loanID, repaymentTransactionId, "03 February 2024"); + + this.globalConfigurationHelper.manageConfigurations("block-transactions-on-closed-overpaid-loans", false); + this.loanTransactionHelper.makeRepayment("04 February 2024", 10.0f, loanID); + } + + private void assertBlockedForClosedOrOverpaid(final String command, final Integer loanId, final String date, final Float amount) { + ArrayList errors = (ArrayList) this.loanTransactionHelperForError.makeRepaymentTypePayment(command, date, amount, + loanId, CommonConstants.RESPONSE_ERROR); + Assertions.assertEquals("error.msg.loan.transaction.not.allowed.on.closed.or.overpaid", + errors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + } + + private Integer createLoanProduct() { + final String principal = "1000.00"; + LoanProductTestBuilder loanProductTestBuilder = new LoanProductTestBuilder() // + .withPrincipal(principal) // + .withShortName(Utils.uniqueRandomStringGenerator("", 4)) // + .withNumberOfRepayments("4") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("1") // + .withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualInstallments() // + .withInterestTypeAsDecliningBalance(); + + final String loanProductJSON = loanProductTestBuilder.build(null); + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal, String submitDate) { + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withLoanTermFrequency("4") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("4") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("1") // + .withAmortizationTypeAsEqualInstallments() // + .withInterestTypeAsDecliningBalance() // + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // + .withExpectedDisbursementDate(submitDate) // + .withSubmittedOnDate(submitDate) // + .build(clientID.toString(), loanProductID.toString(), null); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java index e6f4d9672a3..fd66d4e0a65 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java @@ -572,6 +572,13 @@ private static ArrayList getAllDefaultGlobalConfigurations() { enableImmediateChargeAccrualPostMaturity.put("trapDoor", false); defaults.add(enableImmediateChargeAccrualPostMaturity); + HashMap blockTransactionsOnClosedOverpaidLoans = new HashMap<>(); + blockTransactionsOnClosedOverpaidLoans.put("name", GlobalConfigurationConstants.BLOCK_TRANSACTIONS_ON_CLOSED_OVERPAID_LOANS); + blockTransactionsOnClosedOverpaidLoans.put("value", 0L); + blockTransactionsOnClosedOverpaidLoans.put("enabled", false); + blockTransactionsOnClosedOverpaidLoans.put("trapDoor", false); + defaults.add(blockTransactionsOnClosedOverpaidLoans); + HashMap assetOwnerTransferInterestOutstandingStrategy = new HashMap<>(); assetOwnerTransferInterestOutstandingStrategy.put("name", GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY);