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;
+ }
+}