diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java index c412a2dea..6225d7f9c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java @@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Predicate; @@ -173,6 +174,10 @@ public class LoanCOBFilterHelper { } } + private boolean isLoanHardLocked(Long... loanIds) { + return isLoanHardLocked(Arrays.asList(loanIds)); + } + private boolean isLoanHardLocked(List loanIds) { return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked); } @@ -208,7 +213,11 @@ public class LoanCOBFilterHelper { // check the body for Loan ID Long loanId = getTopLevelLoanIdFromBatchRequest(batchRequest); if (loanId != null) { - loanIds.add(loanId); + if (isLoanHardLocked(loanId)) { + throw new LoanIdsHardLockedException(loanId); + } else { + loanIds.add(loanId); + } } } return loanIds; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index 1352ff0cb..790751528 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -75,6 +75,8 @@ import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper; import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.apache.fineract.integrationtests.common.error.ErrorResponse; +import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; @@ -113,6 +115,9 @@ public abstract class BaseLoanIntegrationTest { protected final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); protected BusinessDateHelper businessDateHelper = new BusinessDateHelper(); + + protected final LoanAccountLockHelper loanAccountLockHelper = new LoanAccountLockHelper(requestSpec, + createResponseSpecification(Matchers.is(202))); protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); // asset @@ -342,6 +347,10 @@ public abstract class BaseLoanIntegrationTest { } } + protected void placeHardLockOnLoan(Long loanId) { + loanAccountLockHelper.placeSoftLockOnLoanAccount(loanId.intValue(), "LOAN_COB_CHUNK_PROCESSING"); + } + protected void executeInlineCOB(Long loanId) { inlineLoanCOBHelper.executeInlineCOB(List.of(loanId)); } @@ -634,6 +643,11 @@ public abstract class BaseLoanIntegrationTest { public List executeEnclosingTransaction() { return BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, BatchHelper.toJsonString(requests)); } + + public ErrorResponse executeEnclosingTransactionError(ResponseSpecification responseSpec) { + return BatchHelper.postBatchRequestsWithoutEnclosingTransactionError(requestSpec, responseSpec, + BatchHelper.toJsonString(requests)); + } } @ToString diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java index 3fcc49dd7..0cb26879c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchLoanIntegrationTest.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import io.restassured.builder.ResponseSpecBuilder; import java.math.BigDecimal; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -28,6 +29,7 @@ import org.apache.fineract.client.models.PostLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoansRequest; import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.error.ErrorResponse; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -98,4 +100,65 @@ public class BatchLoanIntegrationTest extends BaseLoanIntegrationTest { }); }); } + + @Test + public void test_InlineLoanCOB_ShouldExecute_WhenLoanIsHardLocked_And_RescheduleIsRequestedViaBatchApi() { + AtomicLong createdLoanId = new AtomicLong(); + + runAt("01 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + int numberOfRepayments = 24; + int repaymentEvery = 1; + + // Create Loan Product + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() // + .numberOfRepayments(numberOfRepayments) // + .repaymentEvery(repaymentEvery) // + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); // + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + Long loanProductId = loanProductResponse.getResourceId(); + + // Apply and Approve Loan + double amount = 1250.0; + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)// + .repaymentEvery(repaymentEvery)// + .loanTermFrequency(numberOfRepayments)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(RepaymentFrequencyType.MONTHS); + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + + PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), + approveLoanRequest(amount, "01 January 2023")); + + Long loanId = approvedLoanResult.getLoanId(); + + // disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023"); + + createdLoanId.set(loanId); + }); + + runAt("02 January 2023", () -> { + executeInlineCOB(createdLoanId.get()); + }); + + runAt("05 January 2023", () -> { + long loanId = createdLoanId.get(); + placeHardLockOnLoan(loanId); + runAsNonByPass(() -> { + + ErrorResponse response = batchRequest() // + .rescheduleLoan(1L, loanId, "01 January 2023", "01 February 2023", "01 March 2023") // + .approveRescheduleLoan(2L, 1L, "01 January 2023") // + .executeEnclosingTransactionError(new ResponseSpecBuilder().expectStatusCode(409).build()); // + + Assertions.assertEquals(HttpStatus.SC_CONFLICT, Integer.parseInt(response.getHttpStatusCode())); + }); + }); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java index 261f7b976..d842eb55b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java @@ -251,8 +251,11 @@ public final class Utils { public static T performServerPost(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String postURL, final String jsonBodyToSend, final String jsonAttributeToGetBack) { LOG.info("JSON {}", jsonBodyToSend); - final String json = given().spec(requestSpec).body(jsonBodyToSend).expect().spec(responseSpec).log().ifError().when().post(postURL) - .andReturn().asString(); + RequestSpecification spec = given().spec(requestSpec); + if (StringUtils.isNotBlank(jsonBodyToSend)) { + spec = spec.body(jsonBodyToSend); + } + final String json = spec.expect().spec(responseSpec).log().ifError().when().post(postURL).andReturn().asString(); if (jsonAttributeToGetBack == null) { return (T) json; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java index 5bfa9887f..a9b9f52a4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java @@ -43,8 +43,7 @@ public class LoanAccountLockHelper extends IntegrationTest { public String placeSoftLockOnLoanAccount(Integer loanId, String lockOwner, String error) { return Utils.performServerPost(requestSpec, responseSpec, - INTERNAL_PLACE_LOCK_ON_LOAN_ACCOUNT_URL + loanId + "/place-lock/" + lockOwner + "?" + Utils.TENANT_IDENTIFIER, - error == null ? GSON.toJson(null) : error); + INTERNAL_PLACE_LOCK_ON_LOAN_ACCOUNT_URL + loanId + "/place-lock/" + lockOwner + "?" + Utils.TENANT_IDENTIFIER, error); } }