From e530fd1a5599bdf722cbde1abcf4513378ebd6a8 Mon Sep 17 00:00:00 2001 From: Ruchi Dhamankar Date: Mon, 12 Jun 2023 17:08:39 +0530 Subject: [PATCH] FINERACT-1724-COB-hardlocked-loans-and-inline-cob-issue --- ...AccountLockCannotBeOverruledException.java | 5 ++ .../cob/domain/LoanAccountLockRepository.java | 2 + .../LoanLockCannotBeAppliedException.java | 26 +++++++++ .../cob/loan/ApplyLoanLockTasklet.java | 42 ++++++++++++-- .../cob/loan/LoanCOBWorkerConfiguration.java | 5 +- .../fineract/cob/loan/LoanItemReader.java | 15 ++++- .../fineract/cob/loan/LoanLockingService.java | 2 + .../cob/loan/LoanLockingServiceImpl.java | 5 ++ .../InlineLoanCOBExecutorServiceImpl.java | 10 +++- .../ApplyLoanLockTaskletStepDefinitions.java | 37 +++++++++++- .../loan/LoanItemReaderStepDefinitions.java | 19 ++++++- .../cob/loan/cob.loan.applylock.step.feature | 21 ++++++- ...oanCOBAccountLockCatchupInlineCOBTest.java | 57 +++++++++++++++++++ 13 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java diff --git a/fineract-loan/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java b/fineract-loan/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java index 686ae4c863e..9a10fc49a51 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java +++ b/fineract-loan/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java @@ -23,4 +23,9 @@ public class LoanAccountLockCannotBeOverruledException extends RuntimeException public LoanAccountLockCannotBeOverruledException(String message) { super(message); } + + public LoanAccountLockCannotBeOverruledException(String message, Exception e) { + super(message, e); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java index 9705fff6020..40550a51796 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java @@ -55,4 +55,6 @@ lck.lockOwner in (org.apache.fineract.cob.domain.LockOwner.LOAN_COB_CHUNK_PROCES """) @Modifying(flushAutomatically = true) void removeLockByOwner(); + + List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java b/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java new file mode 100644 index 00000000000..5d5c6e1c89c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java @@ -0,0 +1,26 @@ +/** + * 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.exceptions; + +public class LoanLockCannotBeAppliedException extends Exception { + + public LoanLockCannotBeAppliedException(String message, Throwable cause) { + super(message, cause); + } +} 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 443455d9f5d..00397a9e787 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 @@ -18,6 +18,8 @@ */ package org.apache.fineract.cob.loan; +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; + import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Collections; @@ -29,6 +31,7 @@ import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.exceptions.LoanLockCannotBeAppliedException; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.jetbrains.annotations.NotNull; import org.springframework.batch.core.StepContribution; @@ -36,19 +39,26 @@ import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; @Slf4j @RequiredArgsConstructor public class ApplyLoanLockTasklet implements Tasklet { + private static final long NUMBER_OF_RETRIES = 3; private final FineractProperties fineractProperties; private final LoanLockingService loanLockingService; private final RetrieveLoanIdService retrieveLoanIdService; private final CustomJobParameterResolver customJobParameterResolver; + private final TransactionTemplate transactionTemplate; @Override - public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception { + public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) + throws LoanLockCannotBeAppliedException { ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext(); + long numberOfExecutions = contribution.getStepExecution().getCommitCount(); LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER); List loanIds; if (Objects.isNull(loanCOBParameter) @@ -66,16 +76,36 @@ public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull Chu List accountLocks = new ArrayList<>(); loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(loanLockingService.findAllByLoanIdIn(loanIdPartition))); - List alreadyLockedByChunkProcessingAccountIds = accountLocks.stream() - .filter(e -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); - List toBeProcessedLoanIds = new ArrayList<>(loanIds); - toBeProcessedLoanIds.removeAll(alreadyLockedByChunkProcessingAccountIds); + List alreadyLockedAccountIds = accountLocks.stream().map(LoanAccountLock::getLoanId).toList(); + + toBeProcessedLoanIds.removeAll(alreadyLockedAccountIds); + try { + applyLocks(toBeProcessedLoanIds); + } catch (Exception e) { + if (numberOfExecutions > NUMBER_OF_RETRIES) { + String message = "There was an error applying lock to loan accounts."; + log.error("{}", message, e); + throw new LoanLockCannotBeAppliedException(message, e); + } else { + return RepeatStatus.CONTINUABLE; + } + } - loanLockingService.applyLock(toBeProcessedLoanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING); return RepeatStatus.FINISHED; } + private void applyLocks(List toBeProcessedLoanIds) { + transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + + @Override + protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) { + loanLockingService.applyLock(toBeProcessedLoanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING); + } + }); + } + private int getInClauseParameterSizeLimit() { return fineractProperties.getQuery().getInClauseParameterSizeLimit(); } 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 a9723ce9e64..c9e417c5398 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 @@ -131,7 +131,8 @@ public ChunkProcessingLoanItemListener loanItemListener() { @Bean public ApplyLoanLockTasklet applyLock() { - return new ApplyLoanLockTasklet(fineractProperties, loanLockingService, retrieveLoanIdService, customJobParameterResolver); + return new ApplyLoanLockTasklet(fineractProperties, loanLockingService, retrieveLoanIdService, customJobParameterResolver, + transactionTemplate); } @Bean @@ -142,7 +143,7 @@ public ResetContextTasklet resetContext() { @Bean @StepScope public LoanItemReader cobWorkerItemReader() { - return new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver); + return new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver, loanLockingService); } @Bean 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 4dd4df7809b..8e5c0a3ec32 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 @@ -24,6 +24,8 @@ import java.util.Objects; import org.apache.fineract.cob.common.CustomJobParameterResolver; import org.apache.fineract.cob.data.LoanCOBParameter; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LockOwner; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.jetbrains.annotations.NotNull; import org.springframework.batch.core.StepExecution; @@ -34,12 +36,14 @@ public class LoanItemReader extends AbstractLoanItemReader { private final RetrieveLoanIdService retrieveLoanIdService; private final CustomJobParameterResolver customJobParameterResolver; + private final LoanLockingService loanLockingService; public LoanItemReader(LoanRepository loanRepository, RetrieveLoanIdService retrieveLoanIdService, - CustomJobParameterResolver customJobParameterResolver) { + CustomJobParameterResolver customJobParameterResolver, LoanLockingService loanLockingService) { super(loanRepository); this.retrieveLoanIdService = retrieveLoanIdService; this.customJobParameterResolver = customJobParameterResolver; + this.loanLockingService = loanLockingService; } @BeforeStep @@ -56,7 +60,16 @@ public void beforeStep(@NotNull StepExecution stepExecution) { loanIds = retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, customJobParameterResolver.getCustomJobParameterById(stepExecution, LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME) .map(Boolean::parseBoolean).orElse(false)); + + List lockedByCOBChunkProcessingAccountIds = getLoanIdsLockedWithChunkProcessingLock(loanIds); + loanIds.retainAll(lockedByCOBChunkProcessingAccountIds); } setRemainingData(new ArrayList<>(loanIds)); } + + private List getLoanIdsLockedWithChunkProcessingLock(List loanIds) { + List accountLocks = new ArrayList<>(); + accountLocks.addAll(loanLockingService.findAllByLoanIdInAndLockOwner(loanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING)); + return accountLocks.stream().map(LoanAccountLock::getLoanId).toList(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java index 140a6e5007d..00830ba4e5f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java @@ -32,5 +32,7 @@ public interface LoanLockingService { LoanAccountLock findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); + List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); + void applyLock(List loanIds, LockOwner lockOwner); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java index dfb19ddc956..c3758b0f503 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java @@ -85,6 +85,11 @@ public LoanAccountLock findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner }); } + @Override + public List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner) { + return loanAccountLockRepository.findAllByLoanIdInAndLockOwner(loanIds, lockOwner); + } + @Override public void applyLock(List loanIds, LockOwner lockOwner) { LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java index feef43ae980..6c75b22765d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java @@ -218,8 +218,14 @@ private void lockLoanAccounts(List loanIds, LocalDate businessDate) { protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) { List loanAccountLocks = getLoanAccountLocks(loanIds, businessDate); loanAccountLocks.forEach(loanAccountLock -> { - loanAccountLock.setNewLockOwner(LockOwner.LOAN_INLINE_COB_PROCESSING); - loanAccountLockRepository.save(loanAccountLock); + try { + loanAccountLock.setNewLockOwner(LockOwner.LOAN_INLINE_COB_PROCESSING); + loanAccountLockRepository.saveAndFlush(loanAccountLock); + } catch (Exception e) { + String message = "Error updating lock on loan account. Locked loan ID: %s".formatted(loanAccountLock.getLoanId()); + log.error("{}", message, e); + throw new LoanAccountLockCannotBeOverruledException(message, e); + } }); } }); 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 7e2968ec4f6..ead01296a3b 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 @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import io.cucumber.java8.En; @@ -35,6 +36,7 @@ import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.exceptions.LoanLockCannotBeAppliedException; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; @@ -45,6 +47,8 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; public class ApplyLoanLockTaskletStepDefinitions implements En { @@ -54,10 +58,11 @@ public class ApplyLoanLockTaskletStepDefinitions implements En { private FineractProperties fineractProperties = mock(FineractProperties.class); private FineractProperties.FineractQueryProperties fineractQueryProperties = mock(FineractProperties.FineractQueryProperties.class); private RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class); + private TransactionTemplate transactionTemplate = spy(TransactionTemplate.class); private CustomJobParameterResolver customJobParameterResolver = mock(CustomJobParameterResolver.class); private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(fineractProperties, loanLockingService, - retrieveLoanIdService, customJobParameterResolver); + retrieveLoanIdService, customJobParameterResolver, transactionTemplate); private RepeatStatus resultItem; private StepContribution stepContribution; @@ -81,6 +86,25 @@ public ApplyLoanLockTaskletStepDefinitions() { lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); lenient().when(loanLockingService.findAllByLoanIdIn(Mockito.anyList())).thenThrow(new RuntimeException("fail")); + } else if ("db-error-first-try".equals(action)) { + LoanAccountLock lock1 = new LoanAccountLock(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())); + LoanAccountLock lock3 = new LoanAccountLock(3L, LockOwner.LOAN_INLINE_COB_PROCESSING, + LocalDate.now(ZoneId.systemDefault())); + List accountLocks = List.of(lock1, lock3); + lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); + lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); + lenient().when(loanLockingService.findAllByLoanIdIn(Mockito.anyList())).thenReturn(accountLocks); + Mockito.doThrow(new RuntimeException("db error")).when(loanLockingService).applyLock(Mockito.anyList(), any()); + } else if ("db-error-not-recoverable".equals(action)) { + LoanAccountLock lock1 = new LoanAccountLock(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())); + LoanAccountLock lock3 = new LoanAccountLock(3L, LockOwner.LOAN_INLINE_COB_PROCESSING, + LocalDate.now(ZoneId.systemDefault())); + List accountLocks = List.of(lock1, lock3); + stepContribution.getStepExecution().setCommitCount(4); + lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); + lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); + lenient().when(loanLockingService.findAllByLoanIdIn(Mockito.anyList())).thenReturn(accountLocks); + Mockito.doThrow(new RuntimeException("db error")).when(loanLockingService).applyLock(Mockito.anyList(), any()); } else { LoanAccountLock lock1 = new LoanAccountLock(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())); LoanAccountLock lock3 = new LoanAccountLock(3L, LockOwner.LOAN_INLINE_COB_PROCESSING, @@ -90,6 +114,7 @@ public ApplyLoanLockTaskletStepDefinitions() { lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); lenient().when(loanLockingService.findAllByLoanIdIn(Mockito.anyList())).thenReturn(accountLocks); } + transactionTemplate.setTransactionManager(mock(PlatformTransactionManager.class)); lenient().when(customJobParameterResolver.getCustomJobParameterSet(any())).thenReturn(Optional.empty()); }); @@ -111,5 +136,15 @@ public ApplyLoanLockTaskletStepDefinitions() { resultItem = applyLoanLockTasklet.execute(stepContribution, null); }); }); + + Then("throw LoanLockCannotBeAppliedException exception ApplyLoanLockTasklet.execute method", () -> { + assertThrows(LoanLockCannotBeAppliedException.class, () -> { + resultItem = applyLoanLockTasklet.execute(stepContribution, null); + }); + }); + + Then("ApplyLoanLockTasklet.execute result should be retry", () -> { + assertEquals(RepeatStatus.CONTINUABLE, resultItem); + }); } } 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 04b456a1cf9..562d4b9a854 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 @@ -33,8 +33,11 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.fineract.cob.common.CustomJobParameterResolver; import org.apache.fineract.cob.data.LoanCOBParameter; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LockOwner; import org.apache.fineract.cob.exceptions.LoanReadException; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; @@ -52,7 +55,10 @@ public class LoanItemReaderStepDefinitions implements En { private CustomJobParameterResolver customJobParameterResolver = mock(CustomJobParameterResolver.class); - private LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver); + private LoanLockingService lockingService = mock(LoanLockingService.class); + + private LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, customJobParameterResolver, + lockingService); private Loan loan = mock(Loan.class); @@ -70,7 +76,7 @@ public LoanItemReaderStepDefinitions() { List splitAccounts = new ArrayList<>(); if (!loanIds.isEmpty()) { List splitStr = Splitter.on(',').splitToList(loanIds); - splitAccounts = splitStr.stream().map(Long::parseLong).toList(); + splitAccounts = splitStr.stream().map(Long::parseLong).collect(Collectors.toList()); minLoanId = splitAccounts.get(0); maxLoanId = splitAccounts.get(splitAccounts.size() - 1); } @@ -87,7 +93,16 @@ public LoanItemReaderStepDefinitions() { businessDates.put(BusinessDateType.BUSINESS_DATE, businessDate); businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1)); ThreadLocalContextUtil.setBusinessDates(businessDates); + LoanAccountLock loanAccountLock = new LoanAccountLock(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, businessDate.minusDays(1)); + LoanAccountLock loanAccountLockNegativeNumberTest = new LoanAccountLock(-1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, + businessDate.minusDays(1)); lenient().when(customJobParameterResolver.getCustomJobParameterSet(any())).thenReturn(Optional.empty()); + lenient().when(lockingService.findAllByLoanIdInAndLockOwner(List.of(1L), LockOwner.LOAN_COB_CHUNK_PROCESSING)) + .thenReturn(List.of(loanAccountLock)); + lenient().when(lockingService.findAllByLoanIdInAndLockOwner(List.of(1L, 2L), LockOwner.LOAN_COB_CHUNK_PROCESSING)) + .thenReturn(List.of(loanAccountLock)); + lenient().when(lockingService.findAllByLoanIdInAndLockOwner(List.of(-1L), LockOwner.LOAN_COB_CHUNK_PROCESSING)) + .thenReturn(List.of(loanAccountLockNegativeNumberTest)); loanItemReader.beforeStep(stepExecution); diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature index e0423cf61ea..22edba1db50 100644 --- a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature +++ b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature @@ -36,4 +36,23 @@ Feature: COB Apply Loan Lock Step Examples: |action| - |error| \ No newline at end of file + |error| + + @cob + Scenario Outline: ApplyLoanLockTasklet - run test: database exception not recoverable after retries + Given The ApplyLoanLockTasklet.execute method with action + Then throw LoanLockCannotBeAppliedException exception ApplyLoanLockTasklet.execute method + + Examples: + |action| + |db-error-not-recoverable| + + @cob + Scenario Outline: ApplyLoanLockTasklet - run test: database exception first try + Given The ApplyLoanLockTasklet.execute method with action + When ApplyLoanLockTasklet.execute method executed + Then ApplyLoanLockTasklet.execute result should be retry + + Examples: + |action| + |db-error-first-try| \ No newline at end of file diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBAccountLockCatchupInlineCOBTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBAccountLockCatchupInlineCOBTest.java index 2ef563e7770..75a4850ba4b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBAccountLockCatchupInlineCOBTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBAccountLockCatchupInlineCOBTest.java @@ -358,6 +358,63 @@ public void testLoanCOBNoLock() { } } + @Test + public void testLoanCOBWithLoanAccountLockedWithInlineCOB() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 2)); + GlobalConfigurationHelper.updateValueForGlobalConfiguration(this.requestSpec, this.responseSpec, "10", "0"); + loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + loanAccountLockHelper = new LoanAccountLockHelper(requestSpec, new ResponseSpecBuilder().expectStatusCode(202).build()); + final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec); + + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec); + Assertions.assertNotNull(clientID); + + Integer overdueFeeChargeId = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1")); + Assertions.assertNotNull(overdueFeeChargeId); + + final Integer loanProductID = createLoanProduct(overdueFeeChargeId.toString()); + Assertions.assertNotNull(loanProductID); + HashMap loanStatusHashMap; + final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "10 January 2020"); + + Assertions.assertNotNull(loanID); + + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 2020", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + String loanDetails = loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); + loanStatusHashMap = loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 2)); + loanAccountLockHelper.placeSoftLockOnLoanAccount(loanID, "LOAN_INLINE_COB_PROCESSING"); + + final String jobName = "Loan COB"; + schedulerJobHelper.executeAndAwaitJob(jobName); + + loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + + GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID); + + Assertions.assertNull(loan.getLastClosedBusinessDate()); + + } finally { + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + requestSpec.header("Fineract-Platform-TenantId", "default"); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + GlobalConfigurationHelper.updateValueForGlobalConfiguration(this.requestSpec, this.responseSpec, "10", "2"); + } + } + private Integer createLoanProduct(final String chargeId) { final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4") .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")