This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 220d9fe1e [FINERACT-1678] Loan COB Catch Up
220d9fe1e is described below

commit 220d9fe1e47d328cbe2c6e92f297e04e4e008551
Author: taskain7 <[email protected]>
AuthorDate: Tue Nov 29 10:35:34 2022 +0100

    [FINERACT-1678] Loan COB Catch Up
---
 .../fineract/client/util/FineractClient.java       |   3 +
 .../cob/api/LoanCOBCatchUpApiResource.java         |  97 ++++++++
 .../cob/api/LoanCOBCatchUpApiResourceSwagger.java  |  53 +++++
 .../IsCatchUpRunningDTO.java}                      |  20 +-
 .../LoanIdAndLastClosedBusinessDate.java}          |  15 +-
 .../OldestCOBProcessedLoanDTO.java}                |  18 +-
 .../apache/fineract/cob/loan/LoanCOBConstant.java  |   2 +
 .../RetrieveAllNonClosedLoanIdServiceImpl.java     |   2 +-
 .../AsyncLoanCOBExecutorService.java}              |  17 +-
 .../service/AsyncLoanCOBExecutorServiceImpl.java   | 107 +++++++++
 .../service/InlineLoanCOBExecutorServiceImpl.java  |  48 +++-
 .../LoanCOBCatchUpService.java}                    |  20 +-
 .../cob/service/LoanCOBCatchUpServiceImpl.java     |  79 +++++++
 .../async/CustomAsyncExceptionHandler.java}        |  24 +-
 .../configuration/async/SpringAsyncConfig.java     |  43 ++++
 .../jobs/data/JobParametersDTO.java}               |  23 +-
 .../jobs/domain/JobExecutionRepository.java        |  14 ++
 .../jobs/domain/ScheduledJobDetailRepository.java  |   2 +
 .../loanaccount/domain/LoanRepository.java         |  21 +-
 .../InlineLoanCOBExecutorServiceImplTest.java      |  39 +++-
 .../inlinecob/InlineLoanCOBHelper.java             |  54 +++++
 .../inlinecob/InlineLoanCOBTest.java               | 259 +++++++++++++++++++++
 22 files changed, 866 insertions(+), 94 deletions(-)

diff --git 
a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
 
b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
index 229ec6ab2..dc38f50be 100644
--- 
a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
+++ 
b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
@@ -80,6 +80,7 @@ import 
org.apache.fineract.client.services.InterestRateSlabAKAInterestBandsApi;
 import org.apache.fineract.client.services.JournalEntriesApi;
 import org.apache.fineract.client.services.ListReportMailingJobHistoryApi;
 import org.apache.fineract.client.services.LoanChargesApi;
+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.LoanProductsApi;
@@ -216,6 +217,7 @@ public final class FineractClient {
     public final JournalEntriesApi journalEntries;
     public final ListReportMailingJobHistoryApi reportMailings;
     public final LoanChargesApi loanCharges;
+    public final LoanCobCatchUpApi loanCobCatchUpApi;
     public final LoanCollateralApi loanCollaterals;
     public final LoanProductsApi loanProducts;
     public final LoanReschedulingApi loanSchedules;
@@ -332,6 +334,7 @@ public final class FineractClient {
         journalEntries = retrofit.create(JournalEntriesApi.class);
         reportMailings = retrofit.create(ListReportMailingJobHistoryApi.class);
         loanCharges = retrofit.create(LoanChargesApi.class);
+        loanCobCatchUpApi = retrofit.create(LoanCobCatchUpApi.class);
         loanCollaterals = retrofit.create(LoanCollateralApi.class);
         loanProducts = retrofit.create(LoanProductsApi.class);
         loanSchedules = retrofit.create(LoanReschedulingApi.class);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java
new file mode 100644
index 000000000..5212c76c7
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java
@@ -0,0 +1,97 @@
+/**
+ * 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.cob.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
+import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
+import org.apache.fineract.cob.service.LoanCOBCatchUpService;
+import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.springframework.stereotype.Component;
+
+@Path("/loans")
+@Component
+@Tag(name = "Loan COB Catch Up", description = "")
+@RequiredArgsConstructor
+public class LoanCOBCatchUpApiResource {
+
+    private final DefaultToApiJsonSerializer<OldestCOBProcessedLoanDTO> 
oldestCOBProcessedLoanSerializeService;
+    private final DefaultToApiJsonSerializer<IsCatchUpRunningDTO> 
isCatchUpRunningSerializer;
+    private final LoanCOBCatchUpService loanCOBCatchUpService;
+
+    @GET
+    @Path("oldest-cob-closed")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Retrieves the oldest COB processed loan", 
description = "Retrieves the COB business date and the oldest COB processed 
loan")
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanCOBCatchUpApiResourceSwagger.GetOldestCOBProcessedLoanResponse.class))) })
+    public String getOldestCOBProcessedLoan() {
+        OldestCOBProcessedLoanDTO response = 
loanCOBCatchUpService.getOldestCOBProcessedLoan();
+
+        return oldestCOBProcessedLoanSerializeService.serialize(response);
+    }
+
+    @POST
+    @Path("catch-up")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Executes Loan COB Catch Up", description = "Executes 
the Loan COB job on every day from the oldest Loan to the current COB business 
date")
+    @ApiResponses({ @ApiResponse(responseCode = "200", description = "All 
loans are up to date"),
+            @ApiResponse(responseCode = "202", description = "Catch Up has 
been started"),
+            @ApiResponse(responseCode = "400", description = "Catch Up is 
already running") })
+    public Response executeLoanCOBCatchUp() {
+        if (loanCOBCatchUpService.isCatchUpRunning().isCatchUpRunning()) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+        OldestCOBProcessedLoanDTO oldestCOBProcessedLoan = 
loanCOBCatchUpService.getOldestCOBProcessedLoan();
+        if 
(oldestCOBProcessedLoan.getCobProcessedDate().equals(oldestCOBProcessedLoan.getCobBusinessDate()))
 {
+            return Response.status(Response.Status.OK).build();
+        }
+        loanCOBCatchUpService.executeLoanCOBCatchUp();
+
+        return Response.status(Response.Status.ACCEPTED).build();
+    }
+
+    @GET
+    @Path("is-catch-up-running")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Retrieves whether Loan COB catch up is running", 
description = "Retrieves whether Loan COB catch up is running, and the current 
execution date if it is running.")
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
LoanCOBCatchUpApiResourceSwagger.IsCatchUpRunningResponse.class))) })
+    public String isCatchUpRunning() {
+        IsCatchUpRunningDTO response = 
loanCOBCatchUpService.isCatchUpRunning();
+
+        return isCatchUpRunningSerializer.serialize(response);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResourceSwagger.java
new file mode 100644
index 000000000..d4706e4dc
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResourceSwagger.java
@@ -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.cob.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDate;
+import java.util.List;
+
+final class LoanCOBCatchUpApiResourceSwagger {
+
+    private LoanCOBCatchUpApiResourceSwagger() {
+
+    }
+
+    @Schema(description = "GetOldestCOBProcessedLoanResponse")
+    public static final class GetOldestCOBProcessedLoanResponse {
+
+        private GetOldestCOBProcessedLoanResponse() {}
+
+        public List<Long> loanIds;
+        @Schema(example = "[2022, 9, 18]")
+        public LocalDate cobProcessedDate;
+        @Schema(example = "[2022, 9, 22]")
+        public LocalDate cobBusinessDate;
+    }
+
+    @Schema(description = "IsCatchUpRunningResponse")
+    public static final class IsCatchUpRunningResponse {
+
+        private IsCatchUpRunningResponse() {}
+
+        @Schema(example = "true")
+        public boolean isCatchUpRunning;
+        @Schema(example = "[2022, 9, 22]")
+        public LocalDate processingDate;
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/IsCatchUpRunningDTO.java
similarity index 60%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/cob/data/IsCatchUpRunningDTO.java
index a62de399a..cef50f56a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/IsCatchUpRunningDTO.java
@@ -16,20 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.cob.data;
 
 import java.time.LocalDate;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import lombok.AllArgsConstructor;
+import lombok.Data;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
+@Data
+@AllArgsConstructor
+public class IsCatchUpRunningDTO {
 
-    private final LoanRepository loanRepository;
-
-    @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
-    }
+    private boolean isCatchUpRunning;
+    private LocalDate processingDate;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdAndLastClosedBusinessDate.java
similarity index 60%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdAndLastClosedBusinessDate.java
index a62de399a..cdebe1185 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdAndLastClosedBusinessDate.java
@@ -16,20 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.cob.data;
 
 import java.time.LocalDate;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
+public interface LoanIdAndLastClosedBusinessDate {
 
-    private final LoanRepository loanRepository;
+    Long getId();
 
-    @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
-    }
+    LocalDate getLastClosedBusinessDate();
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/OldestCOBProcessedLoanDTO.java
similarity index 61%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/cob/data/OldestCOBProcessedLoanDTO.java
index a62de399a..9ec72367c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/data/OldestCOBProcessedLoanDTO.java
@@ -16,20 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.cob.data;
 
 import java.time.LocalDate;
 import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import lombok.Data;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
+@Data
+public class OldestCOBProcessedLoanDTO {
 
-    private final LoanRepository loanRepository;
-
-    @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
-    }
+    private List<Long> loanIds;
+    private LocalDate cobProcessedDate;
+    private LocalDate cobBusinessDate;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
index f8a142826..782abcef7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
@@ -21,6 +21,7 @@ package org.apache.fineract.cob.loan;
 public final class LoanCOBConstant {
 
     public static final String JOB_NAME = "LOAN_COB";
+    public static final String JOB_HUMAN_READABLE_NAME = "Loan COB";
     public static final String LOAN_COB_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
     public static final String LOAN_IDS = "loanIds";
     public static final String BUSINESS_STEP_MAP = "businessStepMap";
@@ -29,6 +30,7 @@ public final class LoanCOBConstant {
     public static final String 
ALREADY_LOCKED_BY_INLINE_COB_OR_PROCESSED_LOAN_IDS = 
"alreadyLockedOrProcessedLoanIds";
     public static final String INLINE_LOAN_COB_JOB_NAME = "INLINE_LOAN_COB";
     public static final String BUSINESS_DATE_PARAMETER_NAME = "BusinessDate";
+    public static final String IS_CATCH_UP_PARAMETER_NAME = "IS_CATCH_UP";
 
     public static final String LOAN_COB_PARTITIONER_STEP = "Loan COB partition 
- Step";
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
index a62de399a..58cf35751 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
@@ -30,6 +30,6 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdServ
 
     @Override
     public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
+        return 
loanRepository.findAllNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java
similarity index 58%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java
index a62de399a..e2001f31f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java
@@ -16,20 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.cob.service;
 
-import java.time.LocalDate;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.infrastructure.core.domain.FineractContext;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
+public interface AsyncLoanCOBExecutorService {
 
-    private final LoanRepository loanRepository;
-
-    @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
-    }
+    void executeLoanCOBCatchUpAsync(FineractContext context);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
new file mode 100644
index 000000000..eef62720e
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
@@ -0,0 +1,107 @@
+/**
+ * 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.cob.service;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
+import org.apache.fineract.cob.loan.LoanCOBConstant;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.domain.FineractContext;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
+import org.apache.fineract.infrastructure.jobs.domain.JobParameter;
+import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository;
+import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
+import 
org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
+import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
+import org.apache.fineract.infrastructure.jobs.service.JobStarter;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobParametersInvalidException;
+import org.springframework.batch.core.configuration.JobLocator;
+import org.springframework.batch.core.launch.NoSuchJobException;
+import 
org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
+import 
org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
+import org.springframework.batch.core.repository.JobRestartException;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class AsyncLoanCOBExecutorServiceImpl implements 
AsyncLoanCOBExecutorService {
+
+    private final LoanRepository loanRepository;
+    private final JobLocator jobLocator;
+    private final ScheduledJobDetailRepository scheduledJobDetailRepository;
+    private final JobStarter jobStarter;
+    private final JobParameterRepository jobParameterRepository;
+
+    @Override
+    @Async("loanCOBCatchUpThreadPoolTaskExecutor")
+    public void executeLoanCOBCatchUpAsync(FineractContext context) {
+        try {
+            ThreadLocalContextUtil.init(context);
+            LocalDate cobBusinessDate = 
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
+            List<LoanIdAndLastClosedBusinessDate> 
loanIdAndLastClosedBusinessDate = loanRepository
+                    .findOldestCOBProcessedLoan(cobBusinessDate);
+            LocalDate oldestCOBProcessedDate = 
!loanIdAndLastClosedBusinessDate.isEmpty()
+                    ? 
loanIdAndLastClosedBusinessDate.get(0).getLastClosedBusinessDate()
+                    : cobBusinessDate;
+            if (oldestCOBProcessedDate.isBefore(cobBusinessDate)) {
+                
executeLoanCOBDayByDayUntilCOBBusinessDate(oldestCOBProcessedDate, 
cobBusinessDate);
+            }
+        } catch (NoSuchJobException e) {
+            throw new JobNotFoundException(LoanCOBConstant.JOB_NAME, e);
+        } catch (JobInstanceAlreadyCompleteException | JobRestartException | 
JobParametersInvalidException
+                | JobExecutionAlreadyRunningException e) {
+            throw new RuntimeException(e);
+        } finally {
+            ThreadLocalContextUtil.reset();
+        }
+    }
+
+    private void executeLoanCOBDayByDayUntilCOBBusinessDate(LocalDate 
oldestCOBProcessedDate, LocalDate cobBusinessDate)
+            throws NoSuchJobException, JobInstanceAlreadyCompleteException, 
JobExecutionAlreadyRunningException,
+            JobParametersInvalidException, JobRestartException {
+        Job job = jobLocator.getJob(LoanCOBConstant.JOB_NAME);
+        ScheduledJobDetail scheduledJobDetail = 
scheduledJobDetailRepository.findByJobName(LoanCOBConstant.JOB_HUMAN_READABLE_NAME);
+        LocalDate executingBusinessDate = oldestCOBProcessedDate.plusDays(1);
+        while (!executingBusinessDate.isAfter(cobBusinessDate)) {
+            JobParameterDTO jobParameterDTO = new 
JobParameterDTO(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
+                    executingBusinessDate.format(DateTimeFormatter.ISO_DATE));
+            Set<JobParameterDTO> jobParameters = 
Collections.singleton(jobParameterDTO);
+            saveCatchUpJobParameter(scheduledJobDetail);
+            jobStarter.run(job, scheduledJobDetail, 
ThreadLocalContextUtil.getContext(), jobParameters);
+            executingBusinessDate = executingBusinessDate.plusDays(1);
+        }
+    }
+
+    private void saveCatchUpJobParameter(ScheduledJobDetail 
scheduledJobDetail) {
+        JobParameter jobParameter = new JobParameter();
+        jobParameter.setJobId(scheduledJobDetail.getId());
+        
jobParameter.setParameterName(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME);
+        jobParameter.setParameterValue("1");
+        jobParameterRepository.save(jobParameter);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
index 4ce8f9efb..4ff06aece 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
@@ -24,6 +24,7 @@ import com.google.gson.Gson;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -31,6 +32,7 @@ import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.cob.domain.LoanAccountLock;
 import org.apache.fineract.cob.domain.LoanAccountLockRepository;
 import org.apache.fineract.cob.domain.LockOwner;
@@ -49,6 +51,7 @@ import 
org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
 import org.apache.fineract.infrastructure.jobs.service.InlineExecutorService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.batch.core.BatchStatus;
 import org.springframework.batch.core.Job;
@@ -73,8 +76,6 @@ import 
org.springframework.transaction.support.TransactionTemplate;
 public class InlineLoanCOBExecutorServiceImpl implements 
InlineExecutorService<Long> {
 
     private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution 
failed for job with name: ";
-
-    private final GoogleGsonSerializerHelper gsonFactory;
     private final LoanAccountLockRepository loanAccountLockRepository;
     private final InlineLoanCOBExecutionDataParser dataParser;
     private final JobLauncher jobLauncher;
@@ -83,6 +84,7 @@ public class InlineLoanCOBExecutorServiceImpl implements 
InlineExecutorService<L
     private final TransactionTemplate transactionTemplate;
     private final CustomJobParameterRepository customJobParameterRepository;
     private final PlatformSecurityContext context;
+    private final LoanRepository loanRepository;
 
     private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
 
@@ -96,6 +98,32 @@ public class InlineLoanCOBExecutorServiceImpl implements 
InlineExecutorService<L
 
     @Override
     public void execute(List<Long> loanIds, String jobName) {
+        LocalDate cobBusinessDate = 
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
+        List<LoanIdAndLastClosedBusinessDate> loansToBeProcessed = 
getLoansToBeProcessed(loanIds, cobBusinessDate);
+        LocalDate executingBusinessDate = 
getOldestCOBBusinessDate(loansToBeProcessed).plusDays(1);
+        if (!loansToBeProcessed.isEmpty()) {
+            while (!executingBusinessDate.isAfter(cobBusinessDate)) {
+                execute(getLoanIdsToBeProcessed(loansToBeProcessed, 
executingBusinessDate), jobName, executingBusinessDate);
+                executingBusinessDate = executingBusinessDate.plusDays(1);
+            }
+        }
+    }
+
+    private List<Long> 
getLoanIdsToBeProcessed(List<LoanIdAndLastClosedBusinessDate> 
loansToBeProcessed, LocalDate executingBusinessDate) {
+        List<Long> loanIdsToBeProcessed = new ArrayList<>();
+        loansToBeProcessed.forEach(loan -> {
+            if (loan.getLastClosedBusinessDate() != null) {
+                if 
(loan.getLastClosedBusinessDate().isBefore(executingBusinessDate)) {
+                    loanIdsToBeProcessed.add(loan.getId());
+                }
+            } else {
+                loanIdsToBeProcessed.add(loan.getId());
+            }
+        });
+        return loanIdsToBeProcessed;
+    }
+
+    private void execute(List<Long> loanIds, String jobName, LocalDate 
businessDate) {
         lockLoanAccounts(loanIds);
         Job inlineLoanCOBJob;
         try {
@@ -104,9 +132,7 @@ public class InlineLoanCOBExecutorServiceImpl implements 
InlineExecutorService<L
             throw new JobNotFoundException(jobName, e);
         }
         JobParameters jobParameters = new 
JobParametersBuilder(jobExplorer).getNextJobParameters(inlineLoanCOBJob)
-                .addJobParameters(new JobParameters(
-                        getJobParametersMap(loanIds, 
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE))))
-                .toJobParameters();
+                .addJobParameters(new 
JobParameters(getJobParametersMap(loanIds, businessDate))).toJobParameters();
         JobExecution jobExecution;
         try {
             jobExecution = jobLauncher.run(inlineLoanCOBJob, jobParameters);
@@ -120,6 +146,18 @@ public class InlineLoanCOBExecutorServiceImpl implements 
InlineExecutorService<L
         }
     }
 
+    private LocalDate 
getOldestCOBBusinessDate(List<LoanIdAndLastClosedBusinessDate> loans) {
+        LoanIdAndLastClosedBusinessDate oldestLoan = 
loans.stream().min(Comparator
+                
.comparing(LoanIdAndLastClosedBusinessDate::getLastClosedBusinessDate, 
Comparator.nullsLast(Comparator.naturalOrder())))
+                .orElse(null);
+        return oldestLoan != null && oldestLoan.getLastClosedBusinessDate() != 
null ? oldestLoan.getLastClosedBusinessDate()
+                : 
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(1);
+    }
+
+    private List<LoanIdAndLastClosedBusinessDate> 
getLoansToBeProcessed(List<Long> loanIds, LocalDate cobBusinessDate) {
+        return 
loanRepository.findAllNonClosedLoansBehindByLoanIds(cobBusinessDate, loanIds);
+    }
+
     private List<LoanAccountLock> getLoanAccountLocks(List<Long> loanIds) {
         List<LoanAccountLock> loanAccountLocks = new ArrayList<>();
         List<Long> alreadyLockedLoanIds = new ArrayList<>();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
similarity index 58%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
index a62de399a..12abcc961 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
@@ -16,20 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.cob.service;
 
-import java.time.LocalDate;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
+import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
+public interface LoanCOBCatchUpService {
 
-    private final LoanRepository loanRepository;
+    OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan();
 
-    @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
-    }
+    void executeLoanCOBCatchUp();
+
+    IsCatchUpRunningDTO isCatchUpRunning();
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java
new file mode 100644
index 000000000..30870bb8a
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java
@@ -0,0 +1,79 @@
+/**
+ * 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.cob.service;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
+import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
+import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
+import org.apache.fineract.cob.loan.LoanCOBConstant;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.domain.FineractContext;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService {
+
+    private final LoanRepository loanRepository;
+    private final AsyncLoanCOBExecutorService asyncLoanCOBExecutorService;
+    private final JobExecutionRepository jobExecutionRepository;
+    private final JobExplorer jobExplorer;
+
+    @Override
+    public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() {
+        List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate 
= loanRepository
+                
.findOldestCOBProcessedLoan(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
+        OldestCOBProcessedLoanDTO oldestCOBProcessedLoanDTO = new 
OldestCOBProcessedLoanDTO();
+        
oldestCOBProcessedLoanDTO.setLoanIds(loanIdAndLastClosedBusinessDate.stream().map(LoanIdAndLastClosedBusinessDate::getId).toList());
+        oldestCOBProcessedLoanDTO.setCobProcessedDate(
+                
loanIdAndLastClosedBusinessDate.stream().map(LoanIdAndLastClosedBusinessDate::getLastClosedBusinessDate).findFirst()
+                        
.orElse(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)));
+        
oldestCOBProcessedLoanDTO.setCobBusinessDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
+        return oldestCOBProcessedLoanDTO;
+    }
+
+    @Override
+    public void executeLoanCOBCatchUp() {
+        FineractContext context = ThreadLocalContextUtil.getContext();
+        asyncLoanCOBExecutorService.executeLoanCOBCatchUpAsync(context);
+    }
+
+    @Override
+    public IsCatchUpRunningDTO isCatchUpRunning() {
+        List<Long> runningCatchUpExecutionIds = 
jobExecutionRepository.getRunningJobsByExecutionParameter(LoanCOBConstant.JOB_NAME,
+                LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "1");
+        if (CollectionUtils.isNotEmpty(runningCatchUpExecutionIds)) {
+            JobExecution jobExecution = 
jobExplorer.getJobExecution(runningCatchUpExecutionIds.get(0));
+            String executionDateString = (String) 
jobExecution.getExecutionContext().get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
+            return new IsCatchUpRunningDTO(true, 
LocalDate.parse(executionDateString, DateTimeFormatter.ISO_DATE));
+        } else {
+            return new IsCatchUpRunningDTO(false, null);
+        }
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/CustomAsyncExceptionHandler.java
similarity index 57%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/CustomAsyncExceptionHandler.java
index a62de399a..a96305f76 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/CustomAsyncExceptionHandler.java
@@ -16,20 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.infrastructure.configuration.async;
 
-import java.time.LocalDate;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import java.lang.reflect.Method;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
-
-    private final LoanRepository loanRepository;
+@Slf4j
+public class CustomAsyncExceptionHandler implements 
AsyncUncaughtExceptionHandler {
 
     @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
+    public void handleUncaughtException(Throwable throwable, Method method, 
Object... obj) {
+
+        log.error("Exception message - {}", throwable.getMessage());
+        log.error("Method name - {}", method.getName());
+        for (Object param : obj) {
+            log.error("Parameter value - {}", param);
+        }
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java
new file mode 100644
index 000000000..a731937e5
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java
@@ -0,0 +1,43 @@
+/**
+ * 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.infrastructure.configuration.async;
+
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+@Configuration
+@EnableAsync
+public class SpringAsyncConfig implements AsyncConfigurer {
+
+    @Bean(name = "loanCOBCatchUpThreadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor loanCOBCatchUpThreadPoolTaskExecutor() {
+        ThreadPoolTaskExecutor threadPoolTaskExecutor = new 
ThreadPoolTaskExecutor();
+        threadPoolTaskExecutor.setMaxPoolSize(1);
+        return threadPoolTaskExecutor;
+    }
+
+    @Override
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return new CustomAsyncExceptionHandler();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParametersDTO.java
similarity index 58%
copy from 
fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParametersDTO.java
index a62de399a..ca1eff751 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParametersDTO.java
@@ -16,20 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.infrastructure.jobs.data;
 
-import java.time.LocalDate;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import java.util.Set;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 
-@RequiredArgsConstructor
-public class RetrieveAllNonClosedLoanIdServiceImpl implements 
RetrieveLoanIdService {
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class JobParametersDTO {
 
-    private final LoanRepository loanRepository;
-
-    @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate 
businessDate) {
-        return 
loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
-    }
+    private Set<JobParameterDTO> jobParameters;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
index 620961d0d..016a92b0f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.jobs.domain;
 import static org.springframework.batch.core.BatchStatus.COMPLETED;
 import static org.springframework.batch.core.BatchStatus.FAILED;
 import static org.springframework.batch.core.BatchStatus.STARTED;
+import static org.springframework.batch.core.BatchStatus.STARTING;
 
 import java.util.List;
 import java.util.Map;
@@ -84,4 +85,17 @@ public class JobExecutionRepository {
                 "UPDATE BATCH_JOB_EXECUTION SET STATUS = :status, START_TIME = 
null, END_TIME = null WHERE JOB_EXECUTION_ID = :jobExecutionId",
                 Map.of("status", FAILED.name(), "jobExecutionId", stuckJobId));
     }
+
+    public List<Long> getRunningJobsByExecutionParameter(String jobName, 
String parameterKeyName, String parameterValue) {
+        return namedParameterJdbcTemplate.queryForList("SELECT 
bje.JOB_EXECUTION_ID FROM BATCH_JOB_INSTANCE bji "
+                + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = 
bje.JOB_INSTANCE_ID "
+                + "INNER JOIN BATCH_JOB_EXECUTION_PARAMS bjep ON 
bje.JOB_EXECUTION_ID = bjep.JOB_EXECUTION_ID "
+                + "WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = :jobName 
AND bjep.KEY_NAME = :parameterKeyName AND bjep.STRING_VAL = :parameterValue AND 
bje.JOB_INSTANCE_ID NOT IN ("
+                + "SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji "
+                + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = 
bje.JOB_INSTANCE_ID "
+                + "WHERE bje.STATUS = :completedStatus AND bji.JOB_NAME = 
:jobName)",
+                Map.of("statuses", List.of(STARTED.name(), STARTING.name()), 
"jobName", jobName, "completedStatus", COMPLETED.name(),
+                        "parameterKeyName", parameterKeyName, 
"parameterValue", parameterValue),
+                Long.class);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
index c22cc17db..aec1ea688 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/ScheduledJobDetailRepository.java
@@ -45,4 +45,6 @@ public interface ScheduledJobDetailRepository
     @Query("select jobDetail from ScheduledJobDetail jobDetail where 
jobDetail.nodeId = :nodeId or jobDetail.nodeId = 0")
     List<ScheduledJobDetail> findAllJobs(@Param("nodeId") Integer nodeId);
 
+    ScheduledJobDetail findByJobName(String jobName);
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 9714de9aa..605377861 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.domain;
 import java.time.LocalDate;
 import java.util.Collection;
 import java.util.List;
+import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -72,7 +73,7 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
 
     String FIND_ALL_NON_CLOSED = "select loan.id from Loan loan where 
loan.loanStatus in (100,200,300,303,304)";
 
-    String FIND_ALL_NON_CLOSED_ONE_DAY_BEHIND = "select loan.id from Loan loan 
where loan.loanStatus in (100,200,300,303,304) and (:last_closed_business_date 
= loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
+    String FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE = "select 
loan.id from Loan loan where loan.loanStatus in (100,200,300,303,304) and 
(:businessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is 
NULL)";
 
     String FIND_NON_CLOSED_LOAN_THAT_BELONGS_TO_CLIENT = "select loan from 
Loan loan where loan.id = :loanId and loan.loanStatus = 300 and loan.client.id 
= :clientId";
 
@@ -80,6 +81,13 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
 
     String FIND_ID_BY_EXTERNAL_ID = "SELECT loan.id FROM Loan loan WHERE 
loan.externalId = :externalId";
 
+    // should follow the logic of 
`FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE` query
+    String FIND_OLDEST_COB_PROCESSED_LOAN = "select loan.id, 
loan.lastClosedBusinessDate from Loan loan where loan.lastClosedBusinessDate = 
(select min(l.lastClosedBusinessDate) from Loan l where loan.loanStatus in 
(100,200,300,303,304) and l"
+            + ".lastClosedBusinessDate <> " + ":cobBusinessDate)";
+
+    String FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS = "select loan.id, 
loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and 
loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate <> 
:cobBusinessDate or "
+            + "loan.lastClosedBusinessDate is null)";
+
     @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
     List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") 
LocalDate disbursementDate, @Param("groupId") Long groupId,
             @Param("loanType") Integer loanType);
@@ -176,7 +184,14 @@ public interface LoanRepository extends 
JpaRepository<Loan, Long>, JpaSpecificat
     @Query(FIND_ID_BY_EXTERNAL_ID)
     Long findIdByExternalId(@Param("externalId") ExternalId externalId);
 
-    @Query(FIND_ALL_NON_CLOSED_ONE_DAY_BEHIND)
-    List<Long> 
findAllNonClosedLoanIdsOneDayBehind(@Param("last_closed_business_date") 
LocalDate businessDate);
+    @Query(FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE)
+    List<Long> 
findAllNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") 
LocalDate businessDate);
+
+    @Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS)
+    List<LoanIdAndLastClosedBusinessDate> 
findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate 
cobBusinessDate,
+            @Param("loanIds") List<Long> loanIds);
+
+    @Query(FIND_OLDEST_COB_PROCESSED_LOAN)
+    List<LoanIdAndLastClosedBusinessDate> 
findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate);
 
 }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java
index 00ee9da1f..d940fd6fb 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java
@@ -18,16 +18,26 @@
  */
 package org.apache.fineract.cob.service;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.HashMap;
 import java.util.List;
+import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import 
org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
@@ -45,17 +55,42 @@ class InlineLoanCOBExecutorServiceImplTest {
     private InlineLoanCOBExecutorServiceImpl testObj;
     @Mock
     private TransactionTemplate transactionTemplate;
-
     @Mock
     private InlineLoanCOBExecutionDataParser dataParser;
+    @Mock
+    private LoanRepository loanRepository;
 
     @Test
     void shouldExceptionThrownIfLoanIsAlreadyLocked() {
         JsonCommand command = mock(JsonCommand.class);
+        LoanIdAndLastClosedBusinessDate loan = 
mock(LoanIdAndLastClosedBusinessDate.class);
         ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, 
"default", "Default", "Asia/Kolkata", null));
+        HashMap<BusinessDateType, LocalDate> businessDates = new HashMap<>();
+        LocalDate businessDate = LocalDate.now(ZoneId.systemDefault());
+        businessDates.put(BusinessDateType.BUSINESS_DATE, businessDate);
+        businessDates.put(BusinessDateType.COB_DATE, 
businessDate.minusDays(1));
+        ThreadLocalContextUtil.setBusinessDates(businessDates);
 
-        when(dataParser.parseExecution(any())).thenReturn(List.of(3L));
         when(transactionTemplate.execute(any())).thenThrow(new 
LoanAccountLockCannotBeOverruledException(""));
+        when(loanRepository.findAllNonClosedLoansBehindByLoanIds(any(), 
anyList())).thenReturn(List.of(loan));
         assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> 
testObj.executeInlineJob(command, "INLINE_LOAN_COB"));
     }
+
+    @Test
+    void shouldOldestCloseBusinessDateReturnWithCorrectDate()
+            throws NoSuchMethodException, InvocationTargetException, 
IllegalAccessException {
+        LoanIdAndLastClosedBusinessDate loan1 = 
mock(LoanIdAndLastClosedBusinessDate.class);
+        LoanIdAndLastClosedBusinessDate loan2 = 
mock(LoanIdAndLastClosedBusinessDate.class);
+        LoanIdAndLastClosedBusinessDate loan3 = 
mock(LoanIdAndLastClosedBusinessDate.class);
+        when(loan1.getLastClosedBusinessDate()).thenReturn(null);
+        when(loan2.getLastClosedBusinessDate()).thenReturn(LocalDate.of(2023, 
1, 10));
+        when(loan3.getLastClosedBusinessDate()).thenReturn(LocalDate.of(2023, 
1, 11));
+        assertEquals(LocalDate.of(2023, 1, 10), 
getOldestCOBBusinessDate().invoke(testObj, List.of(loan1, loan2, loan3)));
+    }
+
+    private Method getOldestCOBBusinessDate() throws NoSuchMethodException {
+        Method method = 
InlineLoanCOBExecutorServiceImpl.class.getDeclaredMethod("getOldestCOBBusinessDate",
 List.class);
+        method.setAccessible(true);
+        return method;
+    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java
new file mode 100644
index 000000000..000e5bd4e
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java
@@ -0,0 +1,54 @@
+/**
+ * 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.inlinecob;
+
+import com.google.gson.Gson;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.HashMap;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.integrationtests.client.IntegrationTest;
+import org.apache.fineract.integrationtests.common.Utils;
+
+@Slf4j
+public class InlineLoanCOBHelper extends IntegrationTest {
+
+    private final RequestSpecification requestSpec;
+    private final ResponseSpecification responseSpec;
+
+    public InlineLoanCOBHelper(final RequestSpecification requestSpec, final 
ResponseSpecification responseSpec) {
+        this.requestSpec = requestSpec;
+        this.responseSpec = responseSpec;
+    }
+
+    public String executeInlineCOB(List<Long> loanIds) {
+        final String EXECUTE_INLINE_COB_API = 
"/fineract-provider/api/v1/jobs/LOAN_COB/inline";
+        log.info("------------------EXECUTE INLINE COB----------------------");
+        log.info("------------------Loan IDs: {}----------------------", 
loanIds);
+        return Utils.performServerPost(requestSpec, responseSpec, 
EXECUTE_INLINE_COB_API, buildInlineCOBRequest(loanIds));
+    }
+
+    private static String buildInlineCOBRequest(List<Long> loanIds) {
+        final HashMap<String, List<Long>> map = new HashMap<>();
+        map.put("loanIds", loanIds);
+        log.info("map :  {}", map);
+        return new Gson().toJson(map);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java
new file mode 100644
index 000000000..cf7cb6c40
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java
@@ -0,0 +1,259 @@
+/**
+ * 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.inlinecob;
+
+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.path.json.JsonPath;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
+import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
+import org.apache.fineract.client.models.GetDelinquencyTagHistoryResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
+import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+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.LoanTransactionHelper;
+import 
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import 
org.apache.fineract.integrationtests.common.products.DelinquencyRangesHelper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class InlineLoanCOBTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private InlineLoanCOBHelper inlineLoanCOBHelper;
+    private LoanTransactionHelper loanTransactionHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        requestSpec.header("Fineract-Platform-TenantId", "default");
+        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, 
responseSpec);
+    }
+
+    @Test
+    public void testInlineCOB() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 2));
+        
GlobalConfigurationHelper.updateValueForGlobalConfiguration(this.requestSpec, 
this.responseSpec, "10", "0");
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec);
+        Assertions.assertNotNull(clientID);
+
+        Integer overdueFeeChargeId = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+        Assertions.assertNotNull(overdueFeeChargeId);
+
+        final Integer loanProductID = 
createLoanProduct(overdueFeeChargeId.toString());
+        Assertions.assertNotNull(loanProductID);
+        HashMap loanStatusHashMap;
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), 
loanProductID.toString(), null, "10 January 2020");
+
+        Assertions.assertNotNull(loanID);
+
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, 
responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 2020", 
loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        String loanDetails = loanTransactionHelper.getLoanDetails(requestSpec, 
responseSpec, loanID);
+        loanStatusHashMap = 
loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", 
loanID,
+                
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 2));
+        inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
+        GetLoansLoanIdResponse loan = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 3, 2), 
loan.getLastClosedBusinessDate());
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 3));
+        inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
+
+        loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, 
loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 3, 3), 
loan.getLastClosedBusinessDate());
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 10));
+        inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
+
+        loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, 
loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 3, 10), 
loan.getLastClosedBusinessDate());
+
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+    }
+
+    @Test
+    public void testInlineCOBCatchUpLoans() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec);
+        Assertions.assertNotNull(clientID);
+
+        Integer overdueFeeChargeId = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+        Assertions.assertNotNull(overdueFeeChargeId);
+
+        ArrayList<Integer> rangeIds = new ArrayList<>();
+        // First Range
+        String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
+        PostDelinquencyRangeResponse delinquencyRangeResponse = 
DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+                jsonRange);
+        rangeIds.add(delinquencyRangeResponse.getResourceId());
+        jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
+
+        GetDelinquencyRangesResponse range = 
DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
+                delinquencyRangeResponse.getResourceId());
+
+        // Second Range
+        delinquencyRangeResponse = 
DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, 
jsonRange);
+        rangeIds.add(delinquencyRangeResponse.getResourceId());
+
+        range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, 
responseSpec, delinquencyRangeResponse.getResourceId());
+        final String classificationExpected = range.getClassification();
+        log.info("Expected Delinquency Range classification after Disbursement 
{}", classificationExpected);
+
+        String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
+        PostDelinquencyBucketResponse delinquencyBucketResponse = 
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
+                responseSpec, jsonBucket);
+        assertNotNull(delinquencyBucketResponse);
+        final GetDelinquencyBucketsResponse delinquencyBucket = 
DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+                delinquencyBucketResponse.getResourceId());
+
+        final Integer loanProductID = createLoanProduct(loanTransactionHelper, 
delinquencyBucket.getId());
+
+        Assertions.assertNotNull(loanProductID);
+        HashMap loanStatusHashMap;
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), 
loanProductID.toString(), null, "10 January 2020");
+
+        Assertions.assertNotNull(loanID);
+
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, 
responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 2020", 
loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        String loanDetails = loanTransactionHelper.getLoanDetails(requestSpec, 
responseSpec, loanID);
+        loanStatusHashMap = 
loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", 
loanID,
+                
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 2));
+        inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
+        GetLoansLoanIdResponse loan = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        ArrayList<GetDelinquencyTagHistoryResponse> loanDelinquencyTags = 
loanTransactionHelper.getLoanDelinquencyTags(requestSpec,
+                responseSpec, loanID);
+        Assertions.assertTrue(loanDelinquencyTags.isEmpty());
+        Assertions.assertEquals(LocalDate.of(2020, 3, 2), 
loan.getLastClosedBusinessDate());
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.COB_DATE, LocalDate.of(2020, 4, 5));
+        inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
+
+        loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, 
loanID);
+        loanDelinquencyTags = 
loanTransactionHelper.getLoanDelinquencyTags(requestSpec, responseSpec, loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 4, 5), 
loan.getLastClosedBusinessDate());
+        Assertions.assertEquals(1, loanDelinquencyTags.size());
+        Assertions.assertEquals(LocalDate.of(2020, 4, 3), 
loanDelinquencyTags.get(0).getAddedOnDate());
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.COB_DATE, LocalDate.of(2020, 4, 10));
+        inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue()));
+
+        loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, 
loanID);
+        loanDelinquencyTags = 
loanTransactionHelper.getLoanDelinquencyTags(requestSpec, responseSpec, loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 4, 10), 
loan.getLastClosedBusinessDate());
+        Assertions.assertEquals(2, loanDelinquencyTags.size());
+        Assertions.assertEquals(LocalDate.of(2020, 4, 3), 
loanDelinquencyTags.get(1).getAddedOnDate());
+        Assertions.assertEquals(LocalDate.of(2020, 4, 6), 
loanDelinquencyTags.get(0).getAddedOnDate());
+
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
+    }
+
+    private Integer createLoanProduct(final String chargeId) {
+        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
+                
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+                .build(chargeId);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
+    private Integer createLoanProduct(final LoanTransactionHelper 
loanTransactionHelper, final Integer delinquencyBucketId) {
+        final HashMap<String, Object> loanProductMap = new 
LoanProductTestBuilder().build(null, delinquencyBucketId);
+        return 
loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+    }
+
+    private Integer applyForLoanApplication(final String clientID, final 
String loanProductID, final String savingsID, final String date) {
+
+        List<HashMap> collaterals = new ArrayList<>();
+        final Integer collateralId = 
CollateralManagementHelper.createCollateralProduct(this.requestSpec, 
this.responseSpec);
+        Assertions.assertNotNull(collateralId);
+        final Integer clientCollateralId = 
CollateralManagementHelper.createClientCollateral(this.requestSpec, 
this.responseSpec, clientID,
+                collateralId);
+        Assertions.assertNotNull(clientCollateralId);
+        addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("15,000.00").withLoanTermFrequency("4")
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("4").withRepaymentEveryAfter("1")
+                
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments()
+                
.withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+                
.withExpectedDisbursementDate(date).withSubmittedOnDate(date).withCollaterals(collaterals)
+                .build(clientID, loanProductID, savingsID);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private void addCollaterals(List<HashMap> collaterals, Integer 
collateralId, BigDecimal quantity) {
+        collaterals.add(collaterals(collateralId, quantity));
+    }
+
+    private HashMap<String, String> collaterals(Integer collateralId, 
BigDecimal quantity) {
+        HashMap<String, String> collateral = new HashMap<>(2);
+        collateral.put("clientCollateralId", collateralId.toString());
+        collateral.put("quantity", quantity.toString());
+        return collateral;
+    }
+}

Reply via email to