FINERACT-1971: Internal server error fix for handling batch API hard locked loan
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 c412a2d..6225d7f 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 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 @@
         }
     }
 
+    private boolean isLoanHardLocked(Long... loanIds) {
+        return isLoanHardLocked(Arrays.asList(loanIds));
+    }
+
     private boolean isLoanHardLocked(List<Long> loanIds) {
         return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
     }
@@ -208,7 +213,11 @@
             // 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 1352ff0..7907515 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.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 @@
     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 @@
         }
     }
 
+    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 List<BatchResponse> 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 3fcc49d..0cb2687 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.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 @@
             });
         });
     }
+
+    @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 261f7b9..d842eb5 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 static <T> 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 5bfa988..a9b9f52 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 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);
     }
 
 }