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 479a52013 FINERACT-1678 - COB Partitioning tasks creates fully
balanced id ranges where the number of the loans between the min max are
matching with the defined pageSize.
479a52013 is described below
commit 479a52013d9dfbe75ff56904f669a5a6ef13ed3d
Author: Peter Bagrij <[email protected]>
AuthorDate: Thu Jun 29 16:44:23 2023 +0200
FINERACT-1678
- COB Partitioning tasks creates fully balanced id ranges where the number
of the loans between the min max are matching with the defined pageSize.
---
.../apache/fineract/cob/data/LoanCOBParameter.java | 2 +
...LoanCOBParameter.java => LoanCOBPartition.java} | 16 +-
.../loanaccount/domain/LoanRepository.java | 13 -
.../fineract/cob/api/InternalCOBApiResource.java | 80 ++++++
.../cob/loan/LoanCOBManagerConfiguration.java | 24 +-
.../fineract/cob/loan/LoanCOBPartitioner.java | 60 +++--
.../fineract/cob/loan/LoanIdParameterTasklet.java | 58 -----
.../RetrieveAllNonClosedLoanIdServiceImpl.java | 31 ++-
.../cob/loan/RetrieveLoanIdConfiguration.java | 6 +-
.../fineract/cob/loan/RetrieveLoanIdService.java | 3 +-
.../loan/LoanCOBPartitionerStepDefinitions.java | 136 -----------
.../fineract/cob/loan/LoanCOBPartitionerTest.java | 135 +++++++++++
.../RetrieveAllNonClosedLoanIdServiceImplTest.java | 93 +++++++
.../features/cob/loan/cob.loan.partitioner.feature | 32 ---
fineract-provider/src/test/resources/logback.xml | 1 +
.../integrationtests/cob/CobPartitioningTest.java | 268 +++++++++++++++++++++
.../integrationtests/common/loans/CobHelper.java | 38 +++
17 files changed, 704 insertions(+), 292 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
b/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
index b6e2b5ed9..36f5d22a0 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
@@ -20,6 +20,7 @@ package org.apache.fineract.cob.data;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -27,6 +28,7 @@ import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+@EqualsAndHashCode
public class LoanCOBParameter {
private Long minLoanId;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
b/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBPartition.java
similarity index 77%
copy from
fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
copy to
fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBPartition.java
index b6e2b5ed9..9039c1ab6 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/cob/data/LoanCOBPartition.java
@@ -18,17 +18,15 @@
*/
package org.apache.fineract.cob.data;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
+import lombok.Data;
+@Data
@AllArgsConstructor
-@Getter
-@NoArgsConstructor
-@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
-public class LoanCOBParameter {
+public class LoanCOBPartition {
- private Long minLoanId;
- private Long maxLoanId;
+ private Long minId;
+ private Long maxId;
+ private Long pageNo;
+ private Long count;
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 70081a7b7..fce514133 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -22,7 +22,6 @@ import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
-import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndStatus;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
@@ -77,12 +76,6 @@ 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_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE =
"select new org.apache.fineract.cob.data.LoanCOBParameter(min(loan.id),
max(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_MIN_AND_MAX_NON_CLOSED_AND_NON_NULL_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE
= "select new org.apache.fineract.cob.data.LoanCOBParameter(min(loan.id),
max(loan.id)) from Loan loan where loan.loanStatus in (100,200,300,303,304) "
- + "and :businessDate = loan.lastClosedBusinessDate";
-
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";
String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where
loan.accountNumber = :accountNumber";
@@ -211,12 +204,6 @@ public interface LoanRepository extends
JpaRepository<Loan, Long>, JpaSpecificat
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
- @Query(FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE)
- LoanCOBParameter
findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate")
LocalDate businessDate);
-
-
@Query(FIND_MIN_AND_MAX_NON_CLOSED_AND_NON_NULL_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE)
- LoanCOBParameter
findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDateNotNull(@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);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
new file mode 100644
index 000000000..3a4165617
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java
@@ -0,0 +1,80 @@
+/**
+ * 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 jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.UriInfo;
+import java.time.LocalDate;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.data.LoanCOBPartition;
+import org.apache.fineract.cob.loan.LoanCOBConstant;
+import org.apache.fineract.cob.loan.RetrieveLoanIdService;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+@Profile("test")
+@Component
+@Path("/v1/internal/cob")
+@RequiredArgsConstructor
+@Slf4j
+public class InternalCOBApiResource implements InitializingBean {
+
+ private final RetrieveLoanIdService retrieveLoanIdService;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final ToApiJsonSerializer<List> toApiJsonSerializerForList;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+
log.warn("------------------------------------------------------------");
+ log.warn("
");
+ log.warn("DO NOT USE THIS IN PRODUCTION!");
+ log.warn("Internal client services mode is enabled");
+ log.warn("DO NOT USE THIS IN PRODUCTION!");
+ log.warn("
");
+
log.warn("------------------------------------------------------------");
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Path("partitions/{partitionSize}")
+ public String getCobPartitions(@Context final UriInfo uriInfo,
@PathParam("partitionSize") int partitionSize) {
+ LocalDate businessDate =
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.BUSINESS_DATE);
+ log.info("RetrieveLoanCOBPartitions is called with partitionSize {}
for {}", partitionSize, businessDate);
+ List<LoanCOBPartition> loanCOBPartitions =
retrieveLoanIdService.retrieveLoanCOBPartitions(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND,
+ businessDate, false, partitionSize);
+ final ApiRequestJsonSerializationSettings settings =
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return toApiJsonSerializerForList.serialize(settings,
loanCOBPartitions);
+ }
+
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
index f42625a16..de25ef946 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
@@ -20,7 +20,6 @@ package org.apache.fineract.cob.loan;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
-import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
import
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.jobs.service.JobName;
@@ -38,7 +37,6 @@ import
org.springframework.batch.core.step.builder.StepBuilder;
import
org.springframework.batch.integration.config.annotation.EnableBatchIntegration;
import
org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -78,19 +76,15 @@ public class LoanCOBManagerConfiguration {
@Bean
@JobScope
- public LoanCOBPartitioner
partitioner(@Value("#{jobExecutionContext['loanCobParameter']}")
LoanCOBParameter loanCOBParameter) {
- return new LoanCOBPartitioner(propertyService, cobBusinessStepService,
jobOperator, jobExplorer, loanCOBParameter);
+ public LoanCOBPartitioner partitioner() {
+ return new LoanCOBPartitioner(propertyService, cobBusinessStepService,
retrieveLoanIdService, jobOperator, jobExplorer,
+ LoanCOBConstant.NUMBER_OF_DAYS_BEHIND);
}
@Bean
public Step loanCOBStep() {
return
stepBuilderFactory.get(LoanCOBConstant.LOAN_COB_PARTITIONER_STEP)
- .partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP,
partitioner(null)).outputChannel(outboundRequests).build();
- }
-
- @Bean
- public Step loanIdParameterStep() {
- return new StepBuilder("Set loan ID parameter - Step",
jobRepository).tasklet(loanIdParameterTasklet(), transactionManager).build();
+ .partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP,
partitioner()).outputChannel(outboundRequests).build();
}
@Bean
@@ -105,12 +99,6 @@ public class LoanCOBManagerConfiguration {
.build();
}
- @Bean
- @JobScope
- public LoanIdParameterTasklet loanIdParameterTasklet() {
- return new LoanIdParameterTasklet(retrieveLoanIdService,
customJobParameterResolver);
- }
-
@Bean
@JobScope
public ResolveLoanCOBCustomJobParametersTasklet
resolveCustomJobParametersTasklet() {
@@ -128,7 +116,7 @@ public class LoanCOBManagerConfiguration {
return new JobBuilder(JobName.LOAN_COB.name(), jobRepository) //
.listener(new COBExecutionListenerRunner(applicationContext,
JobName.LOAN_COB.name())) //
.start(resolveCustomJobParametersStep()) //
-
.next(loanIdParameterStep()).next(loanCOBStep()).next(stayedLockedStep()) //
+ .next(loanCOBStep()).next(stayedLockedStep()) //
.incrementer(new RunIdIncrementer()) //
.build();
}
@@ -136,7 +124,7 @@ public class LoanCOBManagerConfiguration {
@Bean
public ExecutionContextPromotionListener
customJobParametersPromotionListener() {
ExecutionContextPromotionListener listener = new
ExecutionContextPromotionListener();
- listener.setKeys(new String[] {
LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME });
+ listener.setKeys(new String[] {
LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME });
return listener;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
index cc8da931a..e01ffdc8e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
@@ -18,18 +18,19 @@
*/
package org.apache.fineract.cob.loan;
-import com.google.common.collect.Lists;
-import java.util.HashMap;
+import java.time.LocalDate;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
-import java.util.stream.LongStream;
+import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.infrastructure.springbatch.PropertyService;
import org.jetbrains.annotations.NotNull;
@@ -40,6 +41,8 @@ import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
import org.springframework.batch.core.partition.support.Partitioner;
import org.springframework.batch.item.ExecutionContext;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.StopWatch;
@Slf4j
@RequiredArgsConstructor
@@ -49,10 +52,18 @@ public class LoanCOBPartitioner implements Partitioner {
private final PropertyService propertyService;
private final COBBusinessStepService cobBusinessStepService;
+ private final RetrieveLoanIdService retrieveLoanIdService;
private final JobOperator jobOperator;
private final JobExplorer jobExplorer;
- private final LoanCOBParameter minAndMaxLoanId;
+ private final Long numberOfDays;
+
+ @Value("#{jobExecutionContext['BusinessDate']}")
+ @Setter
+ private LocalDate businessDate;
+ @Value("#{jobExecutionContext['IS_CATCH_UP']}")
+ @Setter
+ private Boolean isCatchUp;
@NotNull
@Override
@@ -64,31 +75,38 @@ public class LoanCOBPartitioner implements Partitioner {
}
private Map<String, ExecutionContext> getPartitions(int partitionSize,
Set<BusinessStepNameAndOrder> cobBusinessSteps) {
- Map<String, ExecutionContext> partitions = new HashMap<>();
if (cobBusinessSteps.isEmpty()) {
stopJobExecution();
return Map.of();
}
- if (!Objects.isNull(minAndMaxLoanId)) {
- List<Long> loanIdsInRange =
LongStream.rangeClosed(minAndMaxLoanId.getMinLoanId(),
minAndMaxLoanId.getMaxLoanId()).boxed()
- .toList();
- List<List<Long>> loanIdPartitions =
Lists.partition(loanIdsInRange, partitionSize);
- for (int i = 0; i < loanIdPartitions.size(); i++) {
- createNewPartition(partitions, i + 1, cobBusinessSteps,
loanIdPartitions.get(i));
- }
- } else {
- createNewPartition(partitions, 1, cobBusinessSteps, List.of(0L));
+ StopWatch sw = new StopWatch();
+ sw.start();
+ List<LoanCOBPartition> loanCOBPartitions = new
ArrayList<>(retrieveLoanIdService.retrieveLoanCOBPartitions(numberOfDays,
+ businessDate, isCatchUp != null ? isCatchUp : false,
partitionSize));
+ sw.stop();
+ // if there is no loan to be closed, we still would like to create at
least one partition
+
+ if (loanCOBPartitions.size() == 0) {
+ loanCOBPartitions.add(new LoanCOBPartition(0L, 0L, 1L, 0L));
}
- return partitions;
+ log.info(
+ "LoanCOBPartitioner found {} loans to be processed as part of
COB. {} partitions were created using partition size {}.
RetrieveLoanCOBPartitions was executed in {} ms.",
+ getLoanCount(loanCOBPartitions), loanCOBPartitions.size(),
partitionSize, sw.getTotalTimeMillis());
+ return loanCOBPartitions.stream()
+ .collect(Collectors.toMap(l -> PARTITION_PREFIX +
l.getPageNo(), l -> createNewPartition(cobBusinessSteps, l)));
+ }
+
+ private long getLoanCount(List<LoanCOBPartition> loanCOBPartitions) {
+ return
loanCOBPartitions.stream().map(LoanCOBPartition::getCount).reduce(0L,
Long::sum);
}
- private void createNewPartition(Map<String, ExecutionContext> partitions,
int partitionIndex,
- Set<BusinessStepNameAndOrder> cobBusinessSteps, List<Long>
loanIds) {
+ private ExecutionContext createNewPartition(Set<BusinessStepNameAndOrder>
cobBusinessSteps, LoanCOBPartition loanCOBPartition) {
ExecutionContext executionContext = new ExecutionContext();
executionContext.put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps);
- executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new
LoanCOBParameter(loanIds.get(0), loanIds.get(loanIds.size() - 1)));
- executionContext.put("partition", PARTITION_PREFIX + partitionIndex);
- partitions.put(PARTITION_PREFIX + partitionIndex, executionContext);
+ executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER,
+ new LoanCOBParameter(loanCOBPartition.getMinId(),
loanCOBPartition.getMaxId()));
+ executionContext.put("partition", PARTITION_PREFIX +
loanCOBPartition.getPageNo());
+ return executionContext;
}
private void stopJobExecution() {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java
deleted file mode 100644
index 781bb051a..000000000
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * 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.loan;
-
-import java.time.LocalDate;
-import java.util.Objects;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.fineract.cob.common.CustomJobParameterResolver;
-import org.apache.fineract.cob.data.LoanCOBParameter;
-import org.springframework.batch.core.StepContribution;
-import org.springframework.batch.core.scope.context.ChunkContext;
-import org.springframework.batch.core.step.tasklet.Tasklet;
-import org.springframework.batch.repeat.RepeatStatus;
-
-@Slf4j
-@RequiredArgsConstructor
-public class LoanIdParameterTasklet implements Tasklet {
-
- private final RetrieveLoanIdService retrieveLoanIdService;
- private final CustomJobParameterResolver customJobParameterResolver;
-
- @Override
- public RepeatStatus execute(StepContribution contribution, ChunkContext
chunkContext) throws Exception {
- String businessDateParameter = (String)
contribution.getStepExecution().getJobExecution().getExecutionContext()
- .get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
- LocalDate businessDate =
LocalDate.parse(Objects.requireNonNull(businessDateParameter));
- LoanCOBParameter minAndMaxLoanId =
retrieveLoanIdService.retrieveMinAndMaxLoanIdsNDaysBehind(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND,
- businessDate,
customJobParameterResolver.getCustomJobParameterById(chunkContext.getStepContext().getStepExecution(),
-
LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME).map(Boolean::parseBoolean).orElse(false));
- if (Objects.isNull(minAndMaxLoanId)
- || (Objects.isNull(minAndMaxLoanId.getMinLoanId()) &&
Objects.isNull(minAndMaxLoanId.getMaxLoanId()))) {
-
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
- new LoanCOBParameter(0L, 0L));
- return RepeatStatus.FINISHED;
- }
-
-
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
minAndMaxLoanId);
-
- return RepeatStatus.FINISHED;
- }
-}
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 ecc1f6ebd..d4553d3de 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
@@ -18,28 +18,53 @@
*/
package org.apache.fineract.cob.loan;
+import java.sql.ResultSet;
+import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@RequiredArgsConstructor
public class RetrieveAllNonClosedLoanIdServiceImpl implements
RetrieveLoanIdService {
private final LoanRepository loanRepository;
+ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
@Override
- public LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long
numberOfDays, LocalDate businessDate, boolean isCatchUp) {
+ public List<LoanCOBPartition> retrieveLoanCOBPartitions(Long numberOfDays,
LocalDate businessDate, boolean isCatchUp,
+ int partitionSize) {
+ StringBuilder sql = new StringBuilder();
+ sql.append("select min(id) as min, max(id) as max, page, count(id) as
count from ");
+ sql.append(" (select floor(((row_number() over(order by id))-1) /
:pageSize) as page, t.* from ");
+ sql.append(" (select id from m_loan where loan_status_id in
(:statusIds) and ");
if (isCatchUp) {
- return
loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDateNotNull(businessDate.minusDays(numberOfDays));
+ sql.append("last_closed_business_date = :businessDate ");
} else {
- return
loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
+ sql.append("(last_closed_business_date = :businessDate or
last_closed_business_date is null) ");
}
+ sql.append("order by id) t) t2 ");
+ sql.append("group by page ");
+ sql.append("order by page");
+
+ MapSqlParameterSource parameters = new MapSqlParameterSource();
+ parameters.addValue("pageSize", partitionSize);
+ parameters.addValue("statusIds", List.of(100, 200, 300, 303, 304));
+ parameters.addValue("businessDate",
businessDate.minusDays(numberOfDays));
+ return namedParameterJdbcTemplate.query(sql.toString(), parameters,
RetrieveAllNonClosedLoanIdServiceImpl::mapRow);
+ }
+
+ private static LoanCOBPartition mapRow(ResultSet rs, int rowNum) throws
SQLException {
+ return new LoanCOBPartition(rs.getLong("min"), rs.getLong("max"),
rs.getLong("page"), rs.getLong("count"));
}
@Override
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java
index d878d0de0..43aa60e6e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@Configuration
public class RetrieveLoanIdConfiguration {
@@ -30,9 +31,12 @@ public class RetrieveLoanIdConfiguration {
@Autowired
private LoanRepository loanRepository;
+ @Autowired
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
@Bean
@ConditionalOnMissingBean
public RetrieveLoanIdService retrieveLoanIdService() {
- return new RetrieveAllNonClosedLoanIdServiceImpl(loanRepository);
+ return new RetrieveAllNonClosedLoanIdServiceImpl(loanRepository,
namedParameterJdbcTemplate);
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
index 681e0ab26..4eef38322 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
@@ -21,13 +21,14 @@ package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.apache.fineract.cob.data.LoanCOBPartition;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.springframework.data.repository.query.Param;
public interface RetrieveLoanIdService {
- LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays,
LocalDate businessDate, boolean isCatchUp);
+ List<LoanCOBPartition> retrieveLoanCOBPartitions(Long numberOfDays,
LocalDate businessDate, boolean isCatchUp, int partitionSize);
List<LoanIdAndLastClosedBusinessDate>
retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java
deleted file mode 100644
index 91b5fbb0f..000000000
---
a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * 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.loan;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import com.google.gson.Gson;
-import io.cucumber.java8.En;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import org.apache.fineract.cob.COBBusinessStepService;
-import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
-import org.apache.fineract.cob.data.LoanCOBParameter;
-import
org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
-import org.apache.fineract.infrastructure.jobs.service.JobName;
-import org.apache.fineract.infrastructure.springbatch.PropertyService;
-import org.mockito.Mockito;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.explore.JobExplorer;
-import org.springframework.batch.core.launch.JobOperator;
-import org.springframework.batch.item.ExecutionContext;
-
-public class LoanCOBPartitionerStepDefinitions implements En {
-
- PropertyService propertyService = mock(PropertyService.class);
- COBBusinessStepService cobBusinessStepService =
mock(COBBusinessStepService.class);
- JobOperator jobOperator = mock(JobOperator.class);
- JobExplorer jobExplorer = mock(JobExplorer.class);
- private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
-
- LoanCOBParameter loanIds;
- private LoanCOBPartitioner loanCOBPartitioner;
-
- private Set<BusinessStepNameAndOrder> cobBusinessSteps = new HashSet<>();
-
- private Map<String, ExecutionContext> resultItem;
- private String action;
-
- public LoanCOBPartitionerStepDefinitions() {
- Given("/^The LoanCOBPartitioner.partition method with action (.*)$/",
(String action) -> {
-
- this.action = action;
-
lenient().when(propertyService.getPartitionSize(LoanCOBConstant.JOB_NAME)).thenReturn(2);
- if ("empty steps".equals(action)) {
-
lenient().when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME))
- .thenReturn(Collections.emptySet());
-
lenient().when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenReturn(Set.of(new
JobExecution(3L)));
- lenient().when(jobOperator.stop(3L)).thenReturn(Boolean.TRUE);
- loanIds = null;
- } else if ("empty loanIds".equals(action)) {
- cobBusinessSteps.add(new BusinessStepNameAndOrder("Business
step", 1L));
-
lenient().when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME))
- .thenReturn(cobBusinessSteps);
- loanIds = null;
- } else if ("good".equals(action)) {
- cobBusinessSteps.add(new BusinessStepNameAndOrder("Business
step", 1L));
-
lenient().when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME))
- .thenReturn(cobBusinessSteps);
- loanIds = new LoanCOBParameter(1L, 3L);
- }
- loanCOBPartitioner = new LoanCOBPartitioner(propertyService,
cobBusinessStepService, jobOperator, jobExplorer, loanIds);
- });
-
- When("LoanCOBPartitioner.partition method executed", () -> {
- resultItem = this.loanCOBPartitioner.partition(2);
- });
-
- Then("LoanCOBPartitioner.partition result should match", () -> {
- if ("empty steps".equals(action)) {
- verify(jobOperator, Mockito.times(1)).stop(3L);
- assertTrue(resultItem.isEmpty());
- } else if ("good".equals(action)) {
- verify(jobOperator, Mockito.times(0)).stop(Mockito.anyLong());
- assertEquals(2, resultItem.size());
-
assertTrue(resultItem.containsKey(LoanCOBPartitioner.PARTITION_PREFIX + "1"));
- Set<BusinessStepNameAndOrder> businessSteps =
(Set<BusinessStepNameAndOrder>) resultItem
- .get(LoanCOBPartitioner.PARTITION_PREFIX +
"1").get(LoanCOBConstant.BUSINESS_STEPS);
-
assertEquals(cobBusinessSteps.stream().findFirst().get().getStepOrder(),
-
businessSteps.stream().findFirst().get().getStepOrder());
-
assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(),
-
businessSteps.stream().findFirst().get().getStepName());
- LoanCOBParameter loanCOBParameter = (LoanCOBParameter)
resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1")
- .get(LoanCOBConstant.LOAN_COB_PARAMETER);
- assertEquals(1, loanCOBParameter.getMaxLoanId() -
loanCOBParameter.getMinLoanId());
- assertEquals(1L, loanCOBParameter.getMinLoanId());
- assertEquals(2L, loanCOBParameter.getMaxLoanId());
-
assertTrue(resultItem.containsKey(LoanCOBPartitioner.PARTITION_PREFIX + "2"));
-
assertEquals(cobBusinessSteps.stream().findFirst().get().getStepOrder(),
-
businessSteps.stream().findFirst().get().getStepOrder());
-
assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(),
-
businessSteps.stream().findFirst().get().getStepName());
- loanCOBParameter = (LoanCOBParameter)
resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "2")
- .get(LoanCOBConstant.LOAN_COB_PARAMETER);
- assertEquals(0, loanCOBParameter.getMaxLoanId() -
loanCOBParameter.getMinLoanId());
- assertEquals(3L, loanCOBParameter.getMinLoanId());
- assertEquals(3L, loanCOBParameter.getMaxLoanId());
- } else if ("empty loanIds".equals(action)) {
- verify(jobOperator, Mockito.times(0)).stop(Mockito.anyLong());
- assertEquals(1, resultItem.size());
-
assertTrue(resultItem.containsKey(LoanCOBPartitioner.PARTITION_PREFIX + "1"));
- Set<BusinessStepNameAndOrder> businessSteps =
(Set<BusinessStepNameAndOrder>) resultItem
- .get(LoanCOBPartitioner.PARTITION_PREFIX +
"1").get(LoanCOBConstant.BUSINESS_STEPS);
-
assertEquals(cobBusinessSteps.stream().findFirst().get().getStepOrder(),
-
businessSteps.stream().findFirst().get().getStepOrder());
-
assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(),
-
businessSteps.stream().findFirst().get().getStepName());
- LoanCOBParameter loanCOBParameter = (LoanCOBParameter)
resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1")
- .get(LoanCOBConstant.LOAN_COB_PARAMETER);
- assertEquals(0L, loanCOBParameter.getMinLoanId());
- assertEquals(0L, loanCOBParameter.getMaxLoanId());
- }
- });
- }
-}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java
new file mode 100644
index 000000000..0ec011905
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java
@@ -0,0 +1,135 @@
+/**
+ * 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.loan;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.fineract.cob.COBBusinessStepService;
+import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
+import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.apache.fineract.cob.data.LoanCOBPartition;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.springbatch.PropertyService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.batch.core.launch.JobExecutionNotRunningException;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.launch.NoSuchJobExecutionException;
+import org.springframework.batch.item.ExecutionContext;
+
+@ExtendWith(MockitoExtension.class)
+class LoanCOBPartitionerTest {
+
+ private static final Set<BusinessStepNameAndOrder> BUSINESS_STEP_SET =
Set.of(new BusinessStepNameAndOrder("Business step", 1L));
+ private static final LocalDate BUSINESS_DATE =
LocalDate.parse("2023-06-28");
+ @Mock
+ private PropertyService propertyService;
+ @Mock
+ private COBBusinessStepService cobBusinessStepService;
+ @Mock
+ private RetrieveLoanIdService retrieveLoanIdService;
+ @Mock
+ private JobOperator jobOperator;
+ @Mock
+ private JobExplorer jobExplorer;
+
+ @Test
+ public void testLoanCOBPartitioner() {
+ //given
+
when(propertyService.getPartitionSize(LoanCOBConstant.JOB_NAME)).thenReturn(5);
+
when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME))
+ .thenReturn(BUSINESS_STEP_SET);
+ when(retrieveLoanIdService.retrieveLoanCOBPartitions(1L,
BUSINESS_DATE, false, 5))
+ .thenReturn(List.of(new LoanCOBPartition(1L,10L, 1L, 5L), new
LoanCOBPartition(11L,20L, 2L, 4L)));
+ LoanCOBPartitioner loanCOBPartitioner = new
LoanCOBPartitioner(propertyService, cobBusinessStepService,
retrieveLoanIdService, jobOperator, jobExplorer, 1L);
+ loanCOBPartitioner.setBusinessDate(BUSINESS_DATE);
+ loanCOBPartitioner.setIsCatchUp(false);
+
+ //when
+ Map<String, ExecutionContext> partitions =
loanCOBPartitioner.partition(1);
+
+ //then
+ Assertions.assertEquals(2, partitions.size());
+ validatePartitions(partitions, 1, 1, 10);
+ validatePartitions(partitions, 2, 11, 20);
+ }
+
+ @Test
+ public void testLoanCOBPartitionerEmptyBusinessSteps() throws
NoSuchJobExecutionException, JobExecutionNotRunningException {
+ //given
+
when(propertyService.getPartitionSize(LoanCOBConstant.JOB_NAME)).thenReturn(5);
+
when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME))
+ .thenReturn(Set.of());
+ JobExecution jobExecution = Mockito.mock(JobExecution.class);
+ when(jobExecution.getId()).thenReturn(123L);
+
when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenReturn(Set.of(jobExecution));
+ LoanCOBPartitioner loanCOBPartitioner = new
LoanCOBPartitioner(propertyService, cobBusinessStepService,
retrieveLoanIdService, jobOperator, jobExplorer, 1L);
+ loanCOBPartitioner.setBusinessDate(BUSINESS_DATE);
+ loanCOBPartitioner.setIsCatchUp(false);
+
+ //when
+ Map<String, ExecutionContext> partitions =
loanCOBPartitioner.partition(1);
+
+ //then
+ Assertions.assertEquals(0, partitions.size());
+ verify(jobExplorer,
times(1)).findRunningJobExecutions(JobName.LOAN_COB.name());
+ verify(jobOperator, times(1)).stop(123L);
+ }
+
+ @Test
+ public void testLoanCOBPartitionerNoLoansFound() {
+ //given
+
when(propertyService.getPartitionSize(LoanCOBConstant.JOB_NAME)).thenReturn(5);
+
when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME))
+ .thenReturn(BUSINESS_STEP_SET);
+ when(retrieveLoanIdService.retrieveLoanCOBPartitions(1L,
BUSINESS_DATE, false, 5))
+ .thenReturn(List.of());
+ LoanCOBPartitioner loanCOBPartitioner = new
LoanCOBPartitioner(propertyService, cobBusinessStepService,
retrieveLoanIdService, jobOperator, jobExplorer, 1L);
+ loanCOBPartitioner.setBusinessDate(BUSINESS_DATE);
+ loanCOBPartitioner.setBusinessDate(BUSINESS_DATE);
+ loanCOBPartitioner.setIsCatchUp(false);
+
+ //when
+ Map<String, ExecutionContext> partitions =
loanCOBPartitioner.partition(1);
+
+ //then
+ Assertions.assertEquals(1, partitions.size());
+ validatePartitions(partitions, 1, 0, 0);
+ }
+
+ private void validatePartitions(Map<String, ExecutionContext> partitions,
int index, long min, long max) {
+ Assertions.assertEquals(BUSINESS_STEP_SET,
+ partitions.get(LoanCOBPartitioner.PARTITION_PREFIX +
index).get(LoanCOBConstant.BUSINESS_STEPS));
+ Assertions.assertEquals(new LoanCOBParameter(min, max),
+ partitions.get(LoanCOBPartitioner.PARTITION_PREFIX +
index).get(LoanCOBConstant.LOAN_COB_PARAMETER));
+ Assertions.assertEquals("partition_" + index,
partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get("partition"));
+ }
+}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java
new file mode 100644
index 000000000..668ebffa4
--- /dev/null
+++
b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java
@@ -0,0 +1,93 @@
+/**
+ * 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.loan;
+
+import static org.mockito.Mockito.times;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.apache.fineract.cob.data.LoanCOBPartition;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+
+@ExtendWith(MockitoExtension.class)
+public class RetrieveAllNonClosedLoanIdServiceImplTest {
+
+ @Mock
+ private LoanRepository loanRepository;
+ @Mock
+ private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+ @Captor
+ private ArgumentCaptor<String> sqlCaptor;
+ @Captor
+ private ArgumentCaptor<SqlParameterSource> paramsCaptor;
+ @Captor
+ private ArgumentCaptor<RowMapper<LoanCOBPartition>> rowMapper;
+
+ @Test
+ public void testRetrieveLoanCOBPartitionsNoCatchup() {
+ String expectedSQL = """
+ select min(id) as min, max(id) as max, page, count(id) as
count from
+ (select floor(((row_number() over(order by id))-1) /
:pageSize) as page, t.* from
+ (select id from m_loan where loan_status_id in
(:statusIds) and (last_closed_business_date = :businessDate or
last_closed_business_date is null) order by id) t) t2
+ group by page
+ order by page
+ """;
+ testRetrieveLoanCOBPartitions(expectedSQL, false);
+ }
+
+ @Test
+ public void testRetrieveLoanCOBPartitionsCatchup() {
+ String expectedSQL = """
+ select min(id) as min, max(id) as max, page, count(id) as
count from
+ (select floor(((row_number() over(order by id))-1) /
:pageSize) as page, t.* from
+ (select id from m_loan where loan_status_id in
(:statusIds) and last_closed_business_date = :businessDate order by id) t) t2
+ group by page
+ order by page
+ """;
+ testRetrieveLoanCOBPartitions(expectedSQL, true);
+ }
+
+ private void testRetrieveLoanCOBPartitions(String expectedSQL, boolean
isCatchup) {
+ RetrieveAllNonClosedLoanIdServiceImpl service = new
RetrieveAllNonClosedLoanIdServiceImpl(loanRepository,
+ namedParameterJdbcTemplate);
+ LocalDate businessDate = LocalDate.parse("2023-06-28");
+ service.retrieveLoanCOBPartitions(1L, businessDate, isCatchup, 5);
+ Mockito.verify(namedParameterJdbcTemplate,
times(1)).query(sqlCaptor.capture(), paramsCaptor.capture(),
rowMapper.capture());
+ Assertions.assertEquals(normalize(expectedSQL),
normalize(sqlCaptor.getValue()));
+ Assertions.assertEquals(5,
paramsCaptor.getValue().getValue("pageSize"));
+ Assertions.assertEquals(List.of(100, 200, 300, 303, 304),
paramsCaptor.getValue().getValue("statusIds"));
+ Assertions.assertEquals(LocalDate.parse("2023-06-27"),
paramsCaptor.getValue().getValue("businessDate"));
+
+ }
+
+ private String normalize(String str) {
+ return str.replaceAll(" +", " ").replaceAll("\r?\n", "");
+ }
+}
diff --git
a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature
b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature
deleted file mode 100644
index 16edca086..000000000
---
a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# 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.
-#
-
-Feature: COB Loan partitioner Step
-
- @cob
- Scenario Outline: LoanCOBPartitioner - run test
- Given The LoanCOBPartitioner.partition method with action <action>
- When LoanCOBPartitioner.partition method executed
- Then LoanCOBPartitioner.partition result should match
-
- Examples:
- |action|
- |empty steps|
- |good|
- |empty loanIds|
diff --git a/fineract-provider/src/test/resources/logback.xml
b/fineract-provider/src/test/resources/logback.xml
index 9f704a984..ec106e90b 100644
--- a/fineract-provider/src/test/resources/logback.xml
+++ b/fineract-provider/src/test/resources/logback.xml
@@ -20,6 +20,7 @@
-->
<configuration scan="false">
+ <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<!-- this hides the warning related to jansi in windows -->
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>false</resetJUL>
</contextListener>
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/cob/CobPartitioningTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/cob/CobPartitioningTest.java
new file mode 100644
index 000000000..37fa51003
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/cob/CobPartitioningTest.java
@@ -0,0 +1,268 @@
+/**
+ * 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.cob;
+
+import static
org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
+
+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.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.accounting.common.AccountingConstants;
+import org.apache.fineract.client.models.GetFinancialActivityAccountsResponse;
+import org.apache.fineract.client.models.PostFinancialActivityAccountsRequest;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.BusinessStepHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.ExternalAssetOwnerHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import
org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.loans.CobHelper;
+import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@SuppressWarnings("rawtypes")
+@Slf4j
+@ExtendWith(LoanTestLifecycleExtension.class)
+public class CobPartitioningTest {
+
+ public static final int N = 10;
+ private static ResponseSpecification RESPONSE_SPEC;
+ private static RequestSpecification REQUEST_SPEC;
+ private static Account ASSET_ACCOUNT;
+ private static Account FEE_PENALTY_ACCOUNT;
+ private static Account TRANSFER_ACCOUNT;
+ private static Account EXPENSE_ACCOUNT;
+ private static Account INCOME_ACCOUNT;
+ private static Account OVERPAYMENT_ACCOUNT;
+ private static FinancialActivityAccountHelper
FINANCIAL_ACTIVITY_ACCOUNT_HELPER;
+ private static ExternalAssetOwnerHelper EXTERNAL_ASSET_OWNER_HELPER;
+ private static LoanTransactionHelper LOAN_TRANSACTION_HELPER;
+ private static SchedulerJobHelper SCHEDULER_JOB_HELPER;
+ private static LocalDate TODAYS_DATE;
+
+ @BeforeAll
+ public static void setupInvestorBusinessStep() {
+ Utils.initializeRESTAssured();
+ REQUEST_SPEC = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ REQUEST_SPEC.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ RESPONSE_SPEC = new
ResponseSpecBuilder().expectStatusCode(200).build();
+ AccountHelper accountHelper = new AccountHelper(REQUEST_SPEC,
RESPONSE_SPEC);
+ EXTERNAL_ASSET_OWNER_HELPER = new ExternalAssetOwnerHelper();
+ SCHEDULER_JOB_HELPER = new SchedulerJobHelper(REQUEST_SPEC);
+ FINANCIAL_ACTIVITY_ACCOUNT_HELPER = new
FinancialActivityAccountHelper(REQUEST_SPEC);
+ LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(REQUEST_SPEC,
RESPONSE_SPEC);
+
+ TODAYS_DATE = Utils.getLocalDateOfTenant();
+ new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS",
"APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
+ "CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE",
"UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
+ "EXTERNAL_ASSET_OWNER_TRANSFER");
+
+ ASSET_ACCOUNT = accountHelper.createAssetAccount();
+ FEE_PENALTY_ACCOUNT = accountHelper.createAssetAccount();
+ TRANSFER_ACCOUNT = accountHelper.createAssetAccount();
+ EXPENSE_ACCOUNT = accountHelper.createExpenseAccount();
+ INCOME_ACCOUNT = accountHelper.createIncomeAccount();
+ OVERPAYMENT_ACCOUNT = accountHelper.createLiabilityAccount();
+
+ setProperFinancialActivity(TRANSFER_ACCOUNT);
+ }
+
+ private static void setProperFinancialActivity(Account transferAccount) {
+ List<GetFinancialActivityAccountsResponse> financialMappings =
FINANCIAL_ACTIVITY_ACCOUNT_HELPER.getAllFinancialActivityAccounts();
+ financialMappings.forEach(mapping ->
FINANCIAL_ACTIVITY_ACCOUNT_HELPER.deleteFinancialActivityAccount(mapping.getId()));
+ FINANCIAL_ACTIVITY_ACCOUNT_HELPER.createFinancialActivityAccount(new
PostFinancialActivityAccountsRequest()
+ .financialActivityId((long)
AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue())
+ .glAccountId((long) transferAccount.getAccountID()));
+ }
+
+ @Test
+ public void testLoanCOBPartitioningQuery() throws InterruptedException {
+ try {
+ ExecutorService executorService = Executors.newFixedThreadPool(10);
+ GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC,
RESPONSE_SPEC,
+
GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID, true);
+ setInitialBusinessDate("2020-03-02");
+
+ List<Integer> loanIds = new CopyOnWriteArrayList<>();
+
+ // Let's create 1, 2, ..., N-1, N loans
+ final CountDownLatch createLatch = new CountDownLatch(N);
+ for (int i = 0; i < N; i++) {
+ Future<?> unused = executorService.submit(() -> {
+ Integer clientID = createClient();
+ Integer loanID = createLoanForClient(clientID);
+ loanIds.add(loanID);
+ createLatch.countDown();
+ });
+ }
+ createLatch.await();
+
+ // Force close loans 3, 4, ... , N-3, N-2
+ Collections.sort(loanIds);
+ final CountDownLatch closeLatch = new CountDownLatch(N - 4);
+ for (int i = 2; i < N - 2; i++) {
+ final int idx = i;
+ Future<?> unused = executorService.submit(() -> {
+ LOAN_TRANSACTION_HELPER.forecloseLoan("02 March 2020",
loanIds.get(idx));
+ closeLatch.countDown();
+ });
+ }
+ closeLatch.await();
+
+ // Let's retrieve the partitions
+ List<Map<String, Object>> cobPartitions =
CobHelper.getCobPartitions(REQUEST_SPEC, RESPONSE_SPEC, 3, "");
+ log.info("\nLoans created: {},\nRetrieved partitions: {}",
loanIds, cobPartitions);
+ Assertions.assertEquals(2, cobPartitions.size());
+
+ Assertions.assertEquals(0, cobPartitions.get(0).get("pageNo"));
+ Assertions.assertEquals(loanIds.get(0),
cobPartitions.get(0).get("minId"));
+ Assertions.assertEquals(loanIds.get(8),
cobPartitions.get(0).get("maxId"));
+ Assertions.assertEquals(3, cobPartitions.get(0).get("count"));
+
+ Assertions.assertEquals(1, cobPartitions.get(1).get("pageNo"));
+ Assertions.assertEquals(loanIds.get(9),
cobPartitions.get(1).get("minId"));
+ Assertions.assertEquals(loanIds.get(9),
cobPartitions.get(1).get("maxId"));
+ Assertions.assertEquals(1, cobPartitions.get(1).get("count"));
+
+ executorService.shutdown();
+ } finally {
+ cleanUpAndRestoreBusinessDate();
+ }
+ }
+
+ private void setInitialBusinessDate(String date) {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC,
RESPONSE_SPEC, Boolean.TRUE);
+ BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC,
BUSINESS_DATE, LocalDate.parse(date));
+
GlobalConfigurationHelper.updateValueForGlobalConfiguration(REQUEST_SPEC,
RESPONSE_SPEC, "10", "0");
+ }
+
+ private void cleanUpAndRestoreBusinessDate() {
+ REQUEST_SPEC = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ REQUEST_SPEC.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ REQUEST_SPEC.header("Fineract-Platform-TenantId", "default");
+ RESPONSE_SPEC = new
ResponseSpecBuilder().expectStatusCode(200).build();
+ BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC,
BUSINESS_DATE, TODAYS_DATE);
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(REQUEST_SPEC,
RESPONSE_SPEC, Boolean.FALSE);
+ GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC,
RESPONSE_SPEC,
+ GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID,
false);
+ }
+
+ @NotNull
+ private Integer createClient() {
+ final Integer clientID = ClientHelper.createClient(REQUEST_SPEC,
RESPONSE_SPEC);
+ Assertions.assertNotNull(clientID);
+ return clientID;
+ }
+
+ @NotNull
+ private Integer createLoanForClient(Integer clientID) {
+ Integer overdueFeeChargeId = ChargesHelper.createCharges(REQUEST_SPEC,
RESPONSE_SPEC,
+
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+ Assertions.assertNotNull(overdueFeeChargeId);
+
+ Integer loanProductID =
createLoanProduct(overdueFeeChargeId.toString());
+ Assertions.assertNotNull(loanProductID);
+ HashMap loanStatusHashMap;
+
+ Integer loanID = applyForLoanApplication(clientID.toString(),
loanProductID.toString(), "10 January 2020");
+
+ Assertions.assertNotNull(loanID);
+
+ loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(REQUEST_SPEC,
RESPONSE_SPEC, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ loanStatusHashMap = LOAN_TRANSACTION_HELPER.approveLoan("01 March
2020", loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+ String loanDetails =
LOAN_TRANSACTION_HELPER.getLoanDetails(REQUEST_SPEC, RESPONSE_SPEC, loanID);
+ loanStatusHashMap =
LOAN_TRANSACTION_HELPER.disburseLoanWithNetDisbursalAmount("02 March 2020",
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+ return loanID;
+ }
+
+ private Integer createLoanProduct(final String chargeId) {
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
+
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
+ .withAccountingRulePeriodicAccrual(new Account[] {
ASSET_ACCOUNT, EXPENSE_ACCOUNT, INCOME_ACCOUNT, OVERPAYMENT_ACCOUNT })
+
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+
.withFeeAndPenaltyAssetAccount(FEE_PENALTY_ACCOUNT).build(chargeId);
+ return LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
+ }
+
+ private Integer applyForLoanApplication(final String clientID, final
String loanProductID, final String date) {
+ List<HashMap> collaterals = new ArrayList<>();
+ Integer collateralId =
CollateralManagementHelper.createCollateralProduct(REQUEST_SPEC, RESPONSE_SPEC);
+ Assertions.assertNotNull(collateralId);
+ Integer clientCollateralId =
CollateralManagementHelper.createClientCollateral(REQUEST_SPEC, RESPONSE_SPEC,
clientID, collateralId);
+ Assertions.assertNotNull(clientCollateralId);
+ addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+ 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, null);
+ return LOAN_TRANSACTION_HELPER.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;
+ }
+
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
new file mode 100644
index 000000000..6b4188a36
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java
@@ -0,0 +1,38 @@
+/**
+ * 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.common.loans;
+
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.integrationtests.client.IntegrationTest;
+import org.apache.fineract.integrationtests.common.Utils;
+
+@Slf4j
+public class CobHelper extends IntegrationTest {
+
+ public static List<Map<String, Object>> getCobPartitions(final
RequestSpecification requestSpec,
+ final ResponseSpecification responseSpec, int partitionSize, final
String jsonReturn) {
+ final String GET_LOAN_URL =
"/fineract-provider/api/v1/internal/cob/partitions/" + partitionSize + "?" +
Utils.TENANT_IDENTIFIER;
+ log.info("---------------------------------GET COB
PARTITIONS---------------------------------------------");
+ return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_URL,
jsonReturn);
+ }
+}