diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java new file mode 100644 index 000000000..b6e2b5ed9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java @@ -0,0 +1,34 @@ +/** + * 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.data; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Getter +@NoArgsConstructor +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public class LoanCOBParameter { + + private Long minLoanId; + private Long maxLoanId; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java index 13ca8d34c..c92fdbf8d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java @@ -37,7 +37,7 @@ import org.springframework.batch.item.ItemReader; @RequiredArgsConstructor public abstract class AbstractLoanItemReader implements ItemReader { - private final LoanRepository loanRepository; + protected final LoanRepository loanRepository; @Setter(AccessLevel.PROTECTED) private List alreadyLockedOrProcessedAccounts; diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java index 66049cf9b..06a23060b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java @@ -20,17 +20,23 @@ package org.apache.fineract.cob.loan; import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LoanAccountLockRepository; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.jetbrains.annotations.NotNull; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; @@ -46,11 +52,21 @@ public class ApplyLoanLockTasklet implements Tasklet { private final LoanAccountLockRepository accountLockRepository; private final FineractProperties fineractProperties; private final JdbcTemplate jdbcTemplate; + private final LoanRepository loanRepository; @Override public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception { ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext(); - List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER); + List loanIds; + if (Objects.isNull(loanCOBParameter) + || (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId())) + || (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) { + loanIds = Collections.emptyList(); + } else { + loanIds = new ArrayList<>(loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(loanCOBParameter.getMinLoanId(), + loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE))); + } List> loanIdPartitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit()); List accountLocks = new ArrayList<>(); loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(accountLockRepository.findAllByLoanIdIn(loanIdPartition))); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java deleted file mode 100644 index e7c758677..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java +++ /dev/null @@ -1,112 +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 com.google.common.collect.Lists; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.cob.domain.LoanAccountLock; -import org.apache.fineract.cob.domain.LoanAccountLockRepository; -import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.jetbrains.annotations.NotNull; -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; -import org.springframework.jdbc.core.JdbcTemplate; - -@Slf4j -@RequiredArgsConstructor -public class FetchAndLockLoanTasklet implements Tasklet { - - private static final Long NUMBER_OF_DAYS_BEHIND = 1L; - - private final LoanAccountLockRepository loanAccountLockRepository; - - private final RetrieveLoanIdService retrieveLoanIdService; - - private final FineractProperties fineractProperties; - - private final JdbcTemplate jdbcTemplate; - - @Override - public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception { - String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext() - .get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME); - LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter)); - List allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIdsNDaysBehind(NUMBER_OF_DAYS_BEHIND, businessDate); - if (allNonClosedLoanIds.isEmpty()) { - contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, Collections.emptyList()); - return RepeatStatus.FINISHED; - } - List remainingIds = new ArrayList<>(allNonClosedLoanIds); - - List> remainingIdPartitions = Lists.partition(remainingIds, getInClauseParameterSizeLimit()); - List loanAccountLocks = new ArrayList<>(); - remainingIdPartitions.forEach( - remainingIdPartition -> loanAccountLocks.addAll(loanAccountLockRepository.findAllByLoanIdIn(remainingIdPartition))); - - List alreadySoftLockedAccounts = loanAccountLocks.stream() - .filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); - List alreadyMarkedForInlineCOBLockedAccounts = loanAccountLocks.stream() - .filter(e -> LockOwner.LOAN_INLINE_COB_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); - List alreadyMarkedForChunkProcessingLockedAccounts = loanAccountLocks.stream() - .filter(e -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); - - // Remove already hard locked accounts - remainingIds.removeAll(alreadyMarkedForChunkProcessingLockedAccounts); - remainingIds.removeAll(alreadyMarkedForInlineCOBLockedAccounts); - - List lockableLoanAccounts = new ArrayList<>(remainingIds); - lockableLoanAccounts.removeAll(alreadySoftLockedAccounts); - - applySoftLock(lockableLoanAccounts); - - contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, remainingIds); - - return RepeatStatus.FINISHED; - } - - private void applySoftLock(List accountsToLock) { - LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - jdbcTemplate.batchUpdate(""" - INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date) - VALUES (?, ?, ?, ?, ?) - """, accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> { - ps.setLong(1, id); - ps.setLong(2, 1); - ps.setString(3, LockOwner.LOAN_COB_PARTITIONING.name()); - ps.setObject(4, DateUtils.getOffsetDateTimeOfTenant()); - ps.setObject(5, cobBusinessDate); - }); - } - - private int getInClauseParameterSizeLimit() { - return fineractProperties.getQuery().getInClauseParameterSizeLimit(); - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java index 4da7b1398..deaa9aaee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java @@ -36,7 +36,7 @@ public class InlineCOBLoanItemReader extends AbstractLoanItemReader { @SuppressWarnings({ "unchecked" }) public void beforeStep(@NotNull StepExecution stepExecution) { ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext(); - List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); + List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER); setRemainingData(new ArrayList<>(loanIds)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java index 276d7c702..3eb01ef41 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java @@ -58,7 +58,8 @@ public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet { ThreadLocalContextUtil.setActionContext(ActionContext.COB); Set cobBusinessSteps = cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME); - contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, getLoanIdsFromJobParameters(chunkContext)); + contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, + getLoanIdsFromJobParameters(chunkContext)); contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps); String businessDateString = getBusinessDateFromJobParameters(chunkContext); contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDateString); 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 35563e02b..d7e5fedd7 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 @@ -23,11 +23,10 @@ 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 LOAN_COB_PARAMETER = "loanCobParameter"; public static final String BUSINESS_STEPS = "businessSteps"; public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep"; - 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"; 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 27a9cf91c..bb944c509 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 @@ -18,12 +18,10 @@ */ package org.apache.fineract.cob.loan; -import java.util.List; import org.apache.fineract.cob.COBBusinessStepService; import org.apache.fineract.cob.common.CustomJobParameterResolver; -import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.listener.COBExecutionListenerRunner; -import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; @@ -74,23 +72,19 @@ public class LoanCOBManagerConfiguration { @Autowired private RetrieveLoanIdService retrieveLoanIdService; @Autowired - private LoanAccountLockRepository accountLockRepository; - @Autowired private BusinessEventNotifierService businessEventNotifierService; @Autowired private CustomJobParameterResolver customJobParameterResolver; @Autowired private LoanRepository loanRepository; - @Autowired - private FineractProperties fineractProperties; @Autowired private JdbcTemplate jdbcTemplate; @Bean @JobScope - public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") List loanIds) { - return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds); + public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") LoanCOBParameter minAndMaxLoanId) { + return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, minAndMaxLoanId); } @Bean @@ -100,8 +94,13 @@ public class LoanCOBManagerConfiguration { } @Bean - public Step fetchAndLockStep() { - return localStepBuilderFactory.get("Fetch and Lock loan accounts - Step").tasklet(fetchAndLockLoanTasklet()).build(); + public Step loanIdParameterStep() { + return localStepBuilderFactory.get("Set loan ID parameter - Step").tasklet(loanIdParameterTasklet()).build(); + } + + @Bean + public Step lockStep() { + return localStepBuilderFactory.get("Lock loan accounts - Step").tasklet(lockLoanTasklet()).build(); } @Bean @@ -117,8 +116,14 @@ public class LoanCOBManagerConfiguration { @Bean @JobScope - public FetchAndLockLoanTasklet fetchAndLockLoanTasklet() { - return new FetchAndLockLoanTasklet(accountLockRepository, retrieveLoanIdService, fineractProperties, jdbcTemplate); + public LoanIdParameterTasklet loanIdParameterTasklet() { + return new LoanIdParameterTasklet(retrieveLoanIdService); + } + + @Bean + @JobScope + public LockLoanTasklet lockLoanTasklet() { + return new LockLoanTasklet(jdbcTemplate); } @Bean @@ -138,7 +143,7 @@ public class LoanCOBManagerConfiguration { return jobBuilderFactory.get(JobName.LOAN_COB.name()) // .listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) // .start(resolveCustomJobParametersStep()) // - .next(fetchAndLockStep()).next(loanCOBStep()).next(stayedLockedStep()) // + .next(loanIdParameterStep()).next(lockStep()).next(loanCOBStep()).next(stayedLockedStep()) // .incrementer(new RunIdIncrementer()) // .build(); } 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 2a851130a..25733d7cd 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,15 +18,15 @@ */ package org.apache.fineract.cob.loan; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import lombok.RequiredArgsConstructor; 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.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; import org.jetbrains.annotations.NotNull; @@ -49,7 +49,7 @@ public class LoanCOBPartitioner implements Partitioner { private final JobOperator jobOperator; private final JobExplorer jobExplorer; - private final List loanIds; + private final LoanCOBParameter minAndMaxLoanId; @NotNull @Override @@ -67,19 +67,25 @@ public class LoanCOBPartitioner implements Partitioner { return Map.of(); } int partitionIndex = 1; - int remainingSpace = 0; createNewPartition(partitions, partitionIndex, cobBusinessSteps); - for (Long loanId : loanIds) { - if (remainingSpace == partitionSize) { - partitionIndex++; - createNewPartition(partitions, partitionIndex, cobBusinessSteps); - remainingSpace = 0; - } - String key = PARTITION_PREFIX + partitionIndex; - ExecutionContext executionContext = partitions.get(key); - List data = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); - data.add(loanId); - remainingSpace++; + if (!Objects.isNull(minAndMaxLoanId)) { + long remainingLoanIdCount = minAndMaxLoanId.getMaxLoanId() - minAndMaxLoanId.getMinLoanId() + 1; + long startLoanId = minAndMaxLoanId.getMinLoanId(); + long endLoanId; + do { + String key = PARTITION_PREFIX + partitionIndex; + ExecutionContext executionContext = partitions.get(key); + if (remainingLoanIdCount > partitionSize) { + endLoanId = startLoanId + partitionSize - 1; + partitionIndex++; + createNewPartition(partitions, partitionIndex, cobBusinessSteps); + } else { + endLoanId = minAndMaxLoanId.getMaxLoanId(); + } + executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(startLoanId, endLoanId)); + startLoanId = startLoanId + partitionSize; + remainingLoanIdCount = minAndMaxLoanId.getMaxLoanId() - endLoanId; + } while (remainingLoanIdCount > 0); } return partitions; } @@ -87,7 +93,6 @@ public class LoanCOBPartitioner implements Partitioner { private void createNewPartition(Map partitions, int partitionIndex, Set cobBusinessSteps) { ExecutionContext executionContext = new ExecutionContext(); - executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList()); executionContext.put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps); executionContext.put("partition", PARTITION_PREFIX + partitionIndex); partitions.put(PARTITION_PREFIX + partitionIndex, executionContext); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java index 3cc751867..252fb9dce 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java @@ -123,7 +123,7 @@ public class LoanCOBWorkerConfiguration { @Bean public ApplyLoanLockTasklet applyLock() { - return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate); + return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate, loanRepository); } @Bean 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 new file mode 100644 index 000000000..a463503b9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java @@ -0,0 +1,56 @@ +/** + * 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.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 static final Long NUMBER_OF_DAYS_BEHIND = 1L; + + private final RetrieveLoanIdService retrieveLoanIdService; + + @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(NUMBER_OF_DAYS_BEHIND, businessDate); + 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/LoanInlineCOBConfig.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java index 64dcecfcb..78a9aa28d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java @@ -121,8 +121,8 @@ public class LoanInlineCOBConfig { @Bean public ExecutionContextPromotionListener inlineCobPromotionListener() { ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); - listener.setKeys( - new String[] { LoanCOBConstant.LOAN_IDS, LoanCOBConstant.BUSINESS_STEPS, LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME }); + listener.setKeys(new String[] { LoanCOBConstant.LOAN_COB_PARAMETER, LoanCOBConstant.BUSINESS_STEPS, + LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME }); return listener; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java index ecb737e83..8dd6b3b2d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java @@ -19,7 +19,12 @@ package org.apache.fineract.cob.loan; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import org.apache.fineract.cob.data.LoanCOBParameter; +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.jetbrains.annotations.NotNull; import org.springframework.batch.core.StepExecution; @@ -35,8 +40,17 @@ public class LoanItemReader extends AbstractLoanItemReader { @BeforeStep @SuppressWarnings({ "unchecked" }) public void beforeStep(@NotNull StepExecution stepExecution) { - ExecutionContext executionContext = stepExecution.getExecutionContext(); - List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); + ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext(); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER); + List loanIds; + if (Objects.isNull(loanCOBParameter) + || (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId())) + || (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) { + loanIds = Collections.emptyList(); + } else { + loanIds = loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(loanCOBParameter.getMinLoanId(), + loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)); + } setRemainingData(new ArrayList<>(loanIds)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LockLoanTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LockLoanTasklet.java new file mode 100644 index 000000000..efe944f65 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LockLoanTasklet.java @@ -0,0 +1,78 @@ +/** + * 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.data.LoanCOBParameter; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.jetbrains.annotations.NotNull; +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; +import org.springframework.jdbc.core.JdbcTemplate; + +@Slf4j +@RequiredArgsConstructor +public class LockLoanTasklet implements Tasklet { + + private final JdbcTemplate jdbcTemplate; + + @Override + public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull 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 loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution().getExecutionContext() + .get(LoanCOBConstant.LOAN_COB_PARAMETER); + if (Objects.isNull(loanCOBParameter) + || (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))) { + loanCOBParameter = new LoanCOBParameter(0L, 0L); + } + applySoftLock(businessDate, loanCOBParameter); + + return RepeatStatus.FINISHED; + } + + private void applySoftLock(LocalDate businessDate, LoanCOBParameter loanCOBParameter) { + LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); + jdbcTemplate.update(""" + INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date) + SELECT loan.id, ?, ?, ?, ? FROM m_loan loan + WHERE loan.id NOT IN (SELECT loan_id FROM m_loan_account_locks) + AND loan.id BETWEEN ? AND ? + AND loan.loan_status_id IN (100,200,300,303,304) + AND (? = loan.last_closed_business_date OR loan.last_closed_business_date IS NULL) + """, ps -> { + ps.setLong(1, 1); + ps.setString(2, LockOwner.LOAN_COB_PARTITIONING.name()); + ps.setObject(3, DateUtils.getOffsetDateTimeOfTenant()); + ps.setObject(4, cobBusinessDate); + ps.setObject(5, loanCOBParameter.getMinLoanId()); + ps.setObject(6, loanCOBParameter.getMaxLoanId()); + ps.setObject(7, businessDate); + }); + } +} 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 24a484e0c..c175b2f7b 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 @@ -21,6 +21,7 @@ package org.apache.fineract.cob.loan; import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; @@ -30,8 +31,8 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ private final LoanRepository loanRepository; @Override - public List retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) { - return loanRepository.findAllNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays)); + public LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) { + return loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays)); } @Override 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 f5a9c7b52..b89fad5d7 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 @@ -20,11 +20,12 @@ 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.LoanIdAndLastClosedBusinessDate; public interface RetrieveLoanIdService { - List retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate); + LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate); List retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List loanIds); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java index f34d80739..471098921 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java @@ -53,7 +53,8 @@ public class StayedLockedLoansTasklet implements Tasklet { private LoanAccountsStayedLockedData buildLoanAccountData() { LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - List stayedLockedLoanAccounts = loanRepository.findAllStayedLockedByLoanIds(cobBusinessDate); + List stayedLockedLoanAccounts = loanRepository + .findAllStayedLockedByCobBusinessDate(cobBusinessDate); List loanAccounts = new ArrayList<>(); stayedLockedLoanAccounts.forEach(loanAccount -> { loanAccounts.add(new LoanAccountStayedLockedData(loanAccount.getId(), loanAccount.getExternalId(), loanAccount.getAccountNo())); 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 08e3cd0ef..8204618c7 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.LoanCOBParameter; import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo; import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -74,7 +75,8 @@ public interface LoanRepository extends JpaRepository, 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_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_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_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"; @@ -89,9 +91,12 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_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)"; + String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_MIN_AND_MAX_LOAN_ID = "select loan.id from Loan loan where loan.id BETWEEN :minLoanId and :maxLoanId and loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate < " + + ":cobBusinessDate or loan.lastClosedBusinessDate is null)"; + 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"; - String FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate"; + String FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate"; @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId, @@ -189,8 +194,8 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat @Query(FIND_ID_BY_EXTERNAL_ID) Long findIdByExternalId(@Param("externalId") ExternalId externalId); - @Query(FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE) - List findAllNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate); + @Query(FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE) + LoanCOBParameter findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate); @Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS) List findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate, @@ -200,10 +205,14 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat List findAllNonClosedLoansBehindOrNullByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate, @Param("loanIds") List loanIds); + @Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_MIN_AND_MAX_LOAN_ID) + List findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(@Param("minLoanId") Long minLoanId, @Param("maxLoanId") Long maxLoanId, + @Param("cobBusinessDate") LocalDate cobBusinessDate); + @Query(FIND_OLDEST_COB_PROCESSED_LOAN) List findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate); - @Query(FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS) - List findAllStayedLockedByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate); + @Query(FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE) + List findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java index bc7271c8e..47165937e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java @@ -20,6 +20,8 @@ package org.apache.fineract.cob.loan; 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.anyLong; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -27,9 +29,9 @@ import static org.mockito.Mockito.verify; import io.cucumber.java8.En; import java.time.LocalDate; import java.time.ZoneId; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LoanAccountLockRepository; import org.apache.fineract.cob.domain.LockOwner; @@ -37,6 +39,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; 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.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.batch.core.StepContribution; @@ -53,7 +56,9 @@ public class ApplyLoanLockTaskletStepDefinitions implements En { private FineractProperties fineractProperties = mock(FineractProperties.class); private JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); private FineractProperties.FineractQueryProperties fineractQueryProperties = mock(FineractProperties.FineractQueryProperties.class); - private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate); + private LoanRepository loanRepository = mock(LoanRepository.class); + private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate, + loanRepository); private RepeatStatus resultItem; private StepContribution stepContribution; @@ -65,7 +70,9 @@ public class ApplyLoanLockTaskletStepDefinitions implements En { ThreadLocalContextUtil.setBusinessDates(businessDateMap); StepExecution stepExecution = new StepExecution("test", null); ExecutionContext executionContext = new ExecutionContext(); - executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<>(List.of(1L, 2L, 3L, 4L))); + executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(1L, 4L)); + lenient().when(this.loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(anyLong(), anyLong(), any())) + .thenReturn(List.of(1L, 2L, 3L, 4L)); stepExecution.setExecutionContext(executionContext); this.stepContribution = new StepContribution(stepExecution); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java index 3f5afdab0..51ef56fa7 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java @@ -20,8 +20,6 @@ package org.apache.fineract.cob.loan; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -33,6 +31,7 @@ import java.time.ZoneId; import java.util.Collections; import java.util.HashMap; import java.util.List; +import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LoanAccountLockRepository; import org.apache.fineract.cob.domain.LockOwner; @@ -46,17 +45,17 @@ import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; public class FetchAndLockLoanStepDefinitions implements En { private final LoanAccountLockRepository loanAccountLockRepository = mock(LoanAccountLockRepository.class); - private final RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class); private final FineractProperties fineractProperties = mock(FineractProperties.class); private final FineractProperties.FineractQueryProperties fineractQueryProperties = mock( FineractProperties.FineractQueryProperties.class); StepContribution contribution; - private FetchAndLockLoanTasklet fetchAndLockLoanTasklet; + private LockLoanTasklet lockLoanTasklet; private String action; private RepeatStatus result; private JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); @@ -69,105 +68,87 @@ public class FetchAndLockLoanStepDefinitions implements En { ThreadLocalContextUtil.setBusinessDates(businessDateMap); this.action = action; + JobExecution jobExecution = new JobExecution(1L); + StepExecution stepExecution = new StepExecution("step", jobExecution); + contribution = new StepContribution(stepExecution); + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, + LocalDate.now(ZoneId.systemDefault()).toString()); + if ("empty loanIds".equals(action)) { - lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(Collections.emptyList()); + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, + new LoanCOBParameter(0L, 0L)); } else if ("good".equals(action)) { - lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L)); + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, + new LoanCOBParameter(1L, 3L)); lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(Collections.emptyList()); } else if ("soft lock".equals(action)) { - lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L)); + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, + new LoanCOBParameter(1L, 3L)); lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn( List.of(new LoanAccountLock(1L, LockOwner.LOAN_COB_PARTITIONING, LocalDate.now(ZoneId.systemDefault())))); } else if ("inline cob".equals(action)) { - lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L)); + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, + new LoanCOBParameter(1L, 3L)); lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn( List.of(new LoanAccountLock(2L, LockOwner.LOAN_INLINE_COB_PROCESSING, LocalDate.now(ZoneId.systemDefault())))); } else if ("chunk processing".equals(action)) { - lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L)); + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, + new LoanCOBParameter(1L, 3L)); lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn( List.of(new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())))); } - JobExecution jobExecution = new JobExecution(1L); - StepExecution stepExecution = new StepExecution("step", jobExecution); - contribution = new StepContribution(stepExecution); - contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, - LocalDate.now(ZoneId.systemDefault()).toString()); - fetchAndLockLoanTasklet = new FetchAndLockLoanTasklet(loanAccountLockRepository, retrieveLoanIdService, fineractProperties, - jdbcTemplate); + lockLoanTasklet = new LockLoanTasklet(jdbcTemplate); }); When("FetchAndLockLoanTasklet.execute method executed", () -> { - result = this.fetchAndLockLoanTasklet.execute(contribution, null); + result = this.lockLoanTasklet.execute(contribution, null); }); Then("FetchAndLockLoanTasklet.execute result should match", () -> { if ("empty steps".equals(action)) { assertEquals(RepeatStatus.FINISHED, result); } else if ("good".equals(action)) { - verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any()); + verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any()); assertEquals(RepeatStatus.FINISHED, result); - assertEquals(3, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .size()); - assertEquals(1L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(0)); - assertEquals(2L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(1)); - assertEquals(3L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(2)); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution() + .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER); + assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId()); + assertEquals(1L, loanCOBParameter.getMinLoanId()); + assertEquals(3L, loanCOBParameter.getMaxLoanId()); } else if ("soft lock".equals(action)) { - verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any()); + verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any()); assertEquals(RepeatStatus.FINISHED, result); - assertEquals(3, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .size()); - assertEquals(1L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(0)); - assertEquals(2L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(1)); - assertEquals(3L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(2)); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution() + .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER); + assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId()); + assertEquals(1L, loanCOBParameter.getMinLoanId()); + assertEquals(3L, loanCOBParameter.getMaxLoanId()); } else if ("inline cob".equals(action)) { - verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any()); + verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any()); assertEquals(RepeatStatus.FINISHED, result); - assertEquals(2, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .size()); - assertEquals(1L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(0)); - assertEquals(3L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(1)); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution() + .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER); + assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId()); + assertEquals(1L, loanCOBParameter.getMinLoanId()); + assertEquals(3L, loanCOBParameter.getMaxLoanId()); } else if ("chunk processing".equals(action)) { - verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any()); + verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any()); assertEquals(RepeatStatus.FINISHED, result); - assertEquals(2, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .size()); - assertEquals(1L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(0)); - assertEquals(2L, - ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) - .get(1)); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution() + .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER); + assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId()); + assertEquals(1L, loanCOBParameter.getMinLoanId()); + assertEquals(3L, loanCOBParameter.getMaxLoanId()); } }); - } } 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 index 03a045ac3..012dbd317 100644 --- 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 @@ -28,11 +28,11 @@ import com.google.gson.Gson; import io.cucumber.java8.En; import java.util.Collections; import java.util.HashSet; -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.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; @@ -50,7 +50,7 @@ public class LoanCOBPartitionerStepDefinitions implements En { JobExplorer jobExplorer = mock(JobExplorer.class); private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); - List loanIds; + LoanCOBParameter loanIds; private LoanCOBPartitioner loanCOBPartitioner; private Set cobBusinessSteps = new HashSet<>(); @@ -68,17 +68,17 @@ public class LoanCOBPartitionerStepDefinitions implements En { .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 = Collections.emptyList(); + 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 = Collections.emptyList(); + 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 = List.of(1L, 2L, 3L); + loanIds = new LoanCOBParameter(1L, 3L); } loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds); }); @@ -101,16 +101,21 @@ public class LoanCOBPartitionerStepDefinitions implements En { businessSteps.stream().findFirst().get().getStepOrder()); assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(), businessSteps.stream().findFirst().get().getStepName()); - assertEquals(2, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).size()); - assertEquals(1L, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).get(0)); - assertEquals(2L, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).get(1)); + 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()); - assertEquals(1, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "2").get(LoanCOBConstant.LOAN_IDS)).size()); - assertEquals(3L, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "2").get(LoanCOBConstant.LOAN_IDS)).get(0)); + 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()); @@ -121,7 +126,9 @@ public class LoanCOBPartitionerStepDefinitions implements En { businessSteps.stream().findFirst().get().getStepOrder()); assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(), businessSteps.stream().findFirst().get().getStepName()); - assertEquals(0, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).size()); + LoanCOBParameter loanCOBParameter = (LoanCOBParameter) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1") + .get(LoanCOBConstant.LOAN_COB_PARAMETER); + assertEquals(null, loanCOBParameter); } }); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java index c2b0a8700..b35b0ec38 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java @@ -21,15 +21,23 @@ package org.apache.fineract.cob.loan; import static org.junit.Assert.assertNull; 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.anyLong; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import com.google.common.base.Splitter; import io.cucumber.java8.En; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Optional; +import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.exceptions.LoanReadException; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.springframework.batch.core.JobExecution; @@ -53,18 +61,29 @@ public class LoanItemReaderStepDefinitions implements En { jobExecution.setExecutionContext(jobExecutionContext); StepExecution stepExecution = new StepExecution("test", jobExecution); ExecutionContext stepExecutionContext = new ExecutionContext(); - List splitAccounts; - if (loanIds.isEmpty()) { - splitAccounts = new ArrayList<>(); - } else { + Long minLoanId = null; + Long maxLoanId = null; + List splitAccounts = new ArrayList<>(); + if (!loanIds.isEmpty()) { List splitStr = Splitter.on(',').splitToList(loanIds); splitAccounts = splitStr.stream().map(Long::parseLong).toList(); + minLoanId = splitAccounts.get(0); + maxLoanId = splitAccounts.get(splitAccounts.size() - 1); } - stepExecutionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<>(splitAccounts)); - stepExecution.setExecutionContext(stepExecutionContext); + stepExecutionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(minLoanId, maxLoanId)); + jobExecution.setExecutionContext(stepExecutionContext); + + lenient().when(this.loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(anyLong(), anyLong(), any())) + .thenReturn(splitAccounts); + + HashMap 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); + loanItemReader.beforeStep(stepExecution); - lenient().when(this.loanRepository.findById(0L)).thenReturn(Optional.empty()); lenient().when(this.loanRepository.findById(1L)).thenReturn(Optional.of(loan)); lenient().when(this.loanRepository.findById(-1L)).thenThrow(new RuntimeException("fail")); diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature index e775f3a86..2453f779a 100644 --- a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature +++ b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature @@ -48,4 +48,3 @@ Feature: COB Reader Examples: |loanIds| |-1 | - |0 |