You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ta...@apache.org on 2023/04/26 12:11:31 UTC
[fineract] branch develop updated: FINERACT-1724 - Loan CatchUp fix - [x] Update null loan cob date when hard lock - [x] Update hard lock to soft lock - [x] Ignore null loan cob date when catch up
This is an automated email from the ASF dual-hosted git repository.
taskain pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 88ab3dedb FINERACT-1724 - Loan CatchUp fix - [x] Update null loan cob date when hard lock - [x] Update hard lock to soft lock - [x] Ignore null loan cob date when catch up
88ab3dedb is described below
commit 88ab3dedbbf1418f0b48a693f4cba3f6f8f84436
Author: Janos Haber <ja...@finesolution.hu>
AuthorDate: Thu Apr 6 08:59:51 2023 +0200
FINERACT-1724 - Loan CatchUp fix
- [x] Update null loan cob date when hard lock
- [x] Update hard lock to soft lock
- [x] Ignore null loan cob date when catch up
---
.../api/InternalLoanAccountLockApiResource.java | 9 +-
.../cob/api/LoanCOBCatchUpApiResource.java | 2 +-
.../cob/domain/LoanAccountLockRepository.java | 25 +++
.../fineract/cob/loan/ApplyLoanLockTasklet.java | 6 +-
.../LoanCatchUpSupport.java} | 18 +-
.../fineract/cob/loan/LoanIdParameterTasklet.java | 4 +-
.../apache/fineract/cob/loan/LoanItemReader.java | 5 +-
.../fineract/cob/loan/LoanLockingService.java | 2 +-
.../fineract/cob/loan/LoanLockingServiceImpl.java | 28 ++-
.../apache/fineract/cob/loan/LockLoanTasklet.java | 4 +-
.../RetrieveAllNonClosedLoanIdServiceImpl.java | 23 ++-
.../fineract/cob/loan/RetrieveLoanIdService.java | 4 +-
.../service/AsyncLoanCOBExecutorServiceImpl.java | 3 +-
.../cob/service/LoanAccountLockService.java | 2 +
.../cob/service/LoanAccountLockServiceImpl.java | 10 ++
.../cob/service/LoanCOBCatchUpService.java | 2 +
.../cob/service/LoanCOBCatchUpServiceImpl.java | 7 +
.../loanaccount/domain/LoanRepository.java | 11 ++
.../loan/ApplyLoanLockTaskletStepDefinitions.java | 3 +-
.../cob/loan/FetchAndLockLoanStepDefinitions.java | 18 +-
.../cob/loan/LoanItemReaderStepDefinitions.java | 3 +-
.../LoanCatchUpIntegrationTest.java | 191 +++++++++++++++++++++
.../fineract/integrationtests/common/Utils.java | 18 ++
.../common/loans/LoanAccountLockHelper.java | 7 +-
.../common/loans/LoanCOBCatchUpHelper.java | 7 +
25 files changed, 366 insertions(+), 46 deletions(-)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
index 07447a2c3..ec29a0ff1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalLoanAccountLockApiResource.java
@@ -29,6 +29,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
@@ -37,6 +38,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestBody;
@Profile("test")
@Component
@@ -64,7 +66,7 @@ public class InternalLoanAccountLockApiResource implements InitializingBean {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response placeLockOnLoanAccount(@Context final UriInfo uriInfo, @PathParam("loanId") Long loanId,
- @PathParam("lockOwner") String lockOwner) {
+ @PathParam("lockOwner") String lockOwner, @RequestBody(required = false) String error) {
log.warn("------------------------------------------------------------");
log.warn(" ");
log.warn("Placing lock on loan: {}", loanId);
@@ -73,7 +75,12 @@ public class InternalLoanAccountLockApiResource implements InitializingBean {
LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.valueOf(lockOwner),
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
+
+ if (StringUtils.isNotBlank(error)) {
+ loanAccountLock.setError(error, error);
+ }
loanAccountLockRepository.save(loanAccountLock);
return Response.status(Response.Status.ACCEPTED).build();
}
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java
index 5212c76c7..b76a601c9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java
@@ -57,7 +57,6 @@ public class LoanCOBCatchUpApiResource {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanCOBCatchUpApiResourceSwagger.GetOldestCOBProcessedLoanResponse.class))) })
public String getOldestCOBProcessedLoan() {
OldestCOBProcessedLoanDTO response = loanCOBCatchUpService.getOldestCOBProcessedLoan();
-
return oldestCOBProcessedLoanSerializeService.serialize(response);
}
@@ -73,6 +72,7 @@ public class LoanCOBCatchUpApiResource {
if (loanCOBCatchUpService.isCatchUpRunning().isCatchUpRunning()) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
+ loanCOBCatchUpService.unlockHardLockedLoans();
OldestCOBProcessedLoanDTO oldestCOBProcessedLoan = loanCOBCatchUpService.getOldestCOBProcessedLoan();
if (oldestCOBProcessedLoan.getCobProcessedDate().equals(oldestCOBProcessedLoan.getCobBusinessDate())) {
return Response.status(Response.Status.OK).build();
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 6ba104ffb..d2dc59e05 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
@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
public interface LoanAccountLockRepository extends JpaRepository<LoanAccountLock, Long>, JpaSpecificationExecutor<LoanAccountLock> {
@@ -32,4 +34,27 @@ public interface LoanAccountLockRepository extends JpaRepository<LoanAccountLock
List<LoanAccountLock> findAllByLoanIdIn(List<Long> loanIds);
boolean existsByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner);
+
+ @Query(value = """
+ update m_loan set last_closed_business_date = (select lck.lock_placed_on_cob_business_date - 1
+ from m_loan_account_locks lck
+ where lck.loan_id = id
+ and lck.lock_placed_on_cob_business_date is not null
+ and lck.error is not null
+ and lck.lock_owner in ('LOAN_COB_CHUNK_PROCESSING','LOAN_INLINE_COB_PROCESSING'))
+ where last_closed_business_date is null and exists (select lck.loan_id
+ from m_loan_account_locks lck where lck.loan_id = id
+ and lck.lock_placed_on_cob_business_date is not null and lck.error is not null
+ and lck.lock_owner in ('LOAN_COB_CHUNK_PROCESSING','LOAN_INLINE_COB_PROCESSING'))""", nativeQuery = true)
+ @Modifying(flushAutomatically = true)
+ void updateLoanFromAccountLocks();
+
+ @Query("""
+ update LoanAccountLock lck set
+ lck.error = null, lck.lockOwner=org.apache.fineract.cob.domain.LockOwner.LOAN_COB_PARTITIONING
+ where lck.lockPlacedOnCobBusinessDate is not null and lck.error is not null and
+ lck.lockOwner in (org.apache.fineract.cob.domain.LockOwner.LOAN_COB_CHUNK_PROCESSING,org.apache.fineract.cob.domain.LockOwner.LOAN_INLINE_COB_PROCESSING)
+ """)
+ @Modifying(flushAutomatically = true)
+ void updateToSoftLockByOwner();
}
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 2f949a27c..6efb01bb2 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
@@ -41,7 +41,7 @@ import org.springframework.batch.repeat.RepeatStatus;
@Slf4j
@RequiredArgsConstructor
-public class ApplyLoanLockTasklet implements Tasklet {
+public class ApplyLoanLockTasklet implements Tasklet, LoanCatchUpSupport {
private final FineractProperties fineractProperties;
private final LoanLockingService loanLockingService;
@@ -57,8 +57,8 @@ public class ApplyLoanLockTasklet implements Tasklet {
|| (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
loanIds = Collections.emptyList();
} else {
- loanIds = new ArrayList<>(
- retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter));
+ loanIds = new ArrayList<>(retrieveLoanIdService
+ .retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, isCatchUp(contribution)));
}
List<List<Long>> loanIdPartitions = Lists.partition(loanIds, getInClauseParameterSizeLimit());
List<LoanAccountLock> accountLocks = new ArrayList<>();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCatchUpSupport.java
similarity index 61%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
copy to fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCatchUpSupport.java
index 12abcc961..ce382ca31 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCatchUpSupport.java
@@ -16,16 +16,18 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.cob.service;
+package org.apache.fineract.cob.loan;
-import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
-import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.StepExecution;
-public interface LoanCOBCatchUpService {
+public interface LoanCatchUpSupport {
- OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan();
+ default boolean isCatchUp(StepContribution contribution) {
+ return isCatchUp(contribution.getStepExecution());
+ }
- void executeLoanCOBCatchUp();
-
- IsCatchUpRunningDTO isCatchUpRunning();
+ default boolean isCatchUp(StepExecution execution) {
+ return "true".equalsIgnoreCase(execution.getExecutionContext().getString(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "false"));
+ }
}
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
index 78e73253e..75b6ed6f2 100644
--- 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
@@ -30,7 +30,7 @@ import org.springframework.batch.repeat.RepeatStatus;
@Slf4j
@RequiredArgsConstructor
-public class LoanIdParameterTasklet implements Tasklet {
+public class LoanIdParameterTasklet implements Tasklet, LoanCatchUpSupport {
private final RetrieveLoanIdService retrieveLoanIdService;
@@ -40,7 +40,7 @@ public class LoanIdParameterTasklet implements Tasklet {
.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
LoanCOBParameter minAndMaxLoanId = retrieveLoanIdService.retrieveMinAndMaxLoanIdsNDaysBehind(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND,
- businessDate);
+ businessDate, isCatchUp(contribution));
if (Objects.isNull(minAndMaxLoanId)
|| (Objects.isNull(minAndMaxLoanId.getMinLoanId()) && Objects.isNull(minAndMaxLoanId.getMaxLoanId()))) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
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 ad7c87374..def3ab969 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
@@ -29,7 +29,7 @@ import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.item.ExecutionContext;
-public class LoanItemReader extends AbstractLoanItemReader {
+public class LoanItemReader extends AbstractLoanItemReader implements LoanCatchUpSupport {
private final RetrieveLoanIdService retrieveLoanIdService;
@@ -49,7 +49,8 @@ public class LoanItemReader extends AbstractLoanItemReader {
|| (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
loanIds = Collections.emptyList();
} else {
- loanIds = retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter);
+ loanIds = retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter,
+ isCatchUp(stepExecution));
}
setRemainingData(new ArrayList<>(loanIds));
}
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 3a7eadc69..f049e1351 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
@@ -26,7 +26,7 @@ import org.apache.fineract.cob.domain.LockOwner;
public interface LoanLockingService {
- void applySoftLock(LocalDate lastClosedBusinessDate, LoanCOBParameter loanCOBParameter);
+ void applySoftLock(LocalDate lastClosedBusinessDate, LoanCOBParameter loanCOBParameter, boolean isCatchUp);
void upgradeLock(List<Long> accountsToLock, 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 bf4c76a76..3c44d2db6 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
@@ -36,22 +36,32 @@ import org.springframework.jdbc.core.JdbcTemplate;
@Slf4j
public class LoanLockingServiceImpl implements LoanLockingService {
+ private static final String NORMAL_LOAN_INSERT = """
+ 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)
+ """;
+ private static final String CATCH_UP_LOAN_INSERT = """
+ 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)
+ """;
+
private final JdbcTemplate jdbcTemplate;
private final FineractProperties fineractProperties;
private final LoanAccountLockRepository loanAccountLockRepository;
@Override
- public void applySoftLock(LocalDate lastClosedBusinessDate, LoanCOBParameter loanCOBParameter) {
+ public void applySoftLock(LocalDate lastClosedBusinessDate, LoanCOBParameter loanCOBParameter, boolean isCatchUp) {
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 -> {
+ jdbcTemplate.update(isCatchUp ? CATCH_UP_LOAN_INSERT : NORMAL_LOAN_INSERT, ps -> {
ps.setLong(1, 1);
ps.setString(2, LockOwner.LOAN_COB_PARTITIONING.name());
ps.setObject(3, DateUtils.getOffsetDateTimeOfTenant());
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
index f313998aa..e0768c488 100644
--- 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
@@ -31,7 +31,7 @@ import org.springframework.batch.repeat.RepeatStatus;
@Slf4j
@RequiredArgsConstructor
-public class LockLoanTasklet implements Tasklet {
+public class LockLoanTasklet implements Tasklet, LoanCatchUpSupport {
private final LoanLockingService loanLockingService;
@@ -47,7 +47,7 @@ public class LockLoanTasklet implements Tasklet {
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))) {
loanCOBParameter = new LoanCOBParameter(0L, 0L);
}
- loanLockingService.applySoftLock(lastClosedBusinessDate, loanCOBParameter);
+ loanLockingService.applySoftLock(lastClosedBusinessDate, loanCOBParameter, isCatchUp(contribution));
return RepeatStatus.FINISHED;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
index 258512ca3..ecc1f6ebd 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
@@ -34,8 +34,12 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
private final LoanRepository loanRepository;
@Override
- public LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) {
- return loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
+ public LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate, boolean isCatchUp) {
+ if (isCatchUp) {
+ return loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDateNotNull(businessDate.minusDays(numberOfDays));
+ } else {
+ return loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
+ }
}
@Override
@@ -49,10 +53,17 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
}
@Override
- public List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter) {
- return loanRepository.findAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
- loanCOBParameter.getMaxLoanId(),
- ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND));
+ public List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter,
+ boolean isCatchUp) {
+ if (isCatchUp) {
+ return loanRepository.findAllNonClosedLoansByLastClosedBusinessDateNotNullAndMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
+ loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)
+ .minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND));
+ } else {
+ return loanRepository.findAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
+ loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)
+ .minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND));
+ }
}
@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 0063909cc..681e0ab26 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
@@ -27,13 +27,13 @@ import org.springframework.data.repository.query.Param;
public interface RetrieveLoanIdService {
- LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
+ LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate, boolean isCatchUp);
List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsOldestCobProcessed(LocalDate businessDate);
- List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter);
+ List<Long> retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter, boolean isCatchUp);
List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
index 72d09a774..6f3fc7fba 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
@@ -37,7 +37,6 @@ import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
import org.apache.fineract.infrastructure.jobs.service.JobStarter;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.configuration.JobLocator;
@@ -52,7 +51,6 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class AsyncLoanCOBExecutorServiceImpl implements AsyncLoanCOBExecutorService {
- private final LoanRepository loanRepository;
private final JobLocator jobLocator;
private final ScheduledJobDetailRepository scheduledJobDetailRepository;
private final JobStarter jobStarter;
@@ -67,6 +65,7 @@ public class AsyncLoanCOBExecutorServiceImpl implements AsyncLoanCOBExecutorServ
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate = retrieveLoanIdService
.retrieveLoanIdsOldestCobProcessed(cobBusinessDate);
+
LocalDate oldestCOBProcessedDate = !loanIdAndLastClosedBusinessDate.isEmpty()
? loanIdAndLastClosedBusinessDate.get(0).getLastClosedBusinessDate()
: cobBusinessDate;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java
index 5fd6eaeee..98760cc80 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java
@@ -28,4 +28,6 @@ public interface LoanAccountLockService {
boolean isLoanHardLocked(Long loanId);
boolean isLoanSoftLocked(Long loanId);
+
+ void updateCobAndRemoveLocks();
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java
index 29423638b..673eef2d5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java
@@ -27,6 +27,8 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@@ -51,4 +53,12 @@ public class LoanAccountLockServiceImpl implements LoanAccountLockService {
public boolean isLoanSoftLocked(Long loanId) {
return loanAccountLockRepository.existsByLoanIdAndLockOwner(loanId, LockOwner.LOAN_COB_PARTITIONING);
}
+
+ @Override
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void updateCobAndRemoveLocks() {
+ loanAccountLockRepository.updateLoanFromAccountLocks();
+ loanAccountLockRepository.updateToSoftLockByOwner();
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
index 12abcc961..fac0c1bc7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java
@@ -23,6 +23,8 @@ import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
public interface LoanCOBCatchUpService {
+ void unlockHardLockedLoans();
+
OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan();
void executeLoanCOBCatchUp();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java
index e5c9231de..2cc77d04f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java
@@ -45,6 +45,13 @@ public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService {
private final JobExplorer jobExplorer;
private final RetrieveLoanIdService retrieveLoanIdService;
+ private final LoanAccountLockService accountLockService;
+
+ @Override
+ public void unlockHardLockedLoans() {
+ accountLockService.updateCobAndRemoveLocks();
+ }
+
@Override
public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() {
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDate = retrieveLoanIdService
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 de3de3bef..09f502a32 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
@@ -78,6 +78,9 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
String FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE = "select new org.apache.fineract.cob.data.LoanCOBParameter(min(loan.id), max(loan.id)) from Loan loan where loan.loanStatus in (100,200,300,303,304) "
+ "and (:businessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
+ String FIND_MIN_AND_MAX_NON_CLOSED_AND_NON_NULL_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE = "select new org.apache.fineract.cob.data.LoanCOBParameter(min(loan.id), max(loan.id)) from Loan loan where loan.loanStatus in (100,200,300,303,304) "
+ + "and :businessDate = loan.lastClosedBusinessDate";
+
String FIND_NON_CLOSED_LOAN_THAT_BELONGS_TO_CLIENT = "select loan from Loan loan where loan.id = :loanId and loan.loanStatus = 300 and loan.client.id = :clientId";
String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber";
@@ -93,6 +96,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
String FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE_AND_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 (:cobBusinessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
+ String FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE_NOT_NULL_AND_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 :cobBusinessDate = loan.lastClosedBusinessDate";
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_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";
@@ -198,6 +202,9 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Query(FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE)
LoanCOBParameter findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate);
+ @Query(FIND_MIN_AND_MAX_NON_CLOSED_AND_NON_NULL_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE)
+ LoanCOBParameter findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDateNotNull(@Param("businessDate") LocalDate businessDate);
+
@Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS)
List<LoanIdAndLastClosedBusinessDate> findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@Param("loanIds") List<Long> loanIds);
@@ -210,6 +217,10 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
List<Long> findAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(@Param("minLoanId") Long minLoanId,
@Param("maxLoanId") Long maxLoanId, @Param("cobBusinessDate") LocalDate cobBusinessDate);
+ @Query(FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE_NOT_NULL_AND_MIN_AND_MAX_LOAN_ID)
+ List<Long> findAllNonClosedLoansByLastClosedBusinessDateNotNullAndMinAndMaxLoanId(@Param("minLoanId") Long minLoanId,
+ @Param("maxLoanId") Long maxLoanId, @Param("cobBusinessDate") LocalDate cobBusinessDate);
+
@Query(FIND_OLDEST_COB_PROCESSED_LOAN)
List<LoanIdAndLastClosedBusinessDate> findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate);
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java
index 37851a9da..fe1060036 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
@@ -66,7 +66,8 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
ExecutionContext executionContext = new ExecutionContext();
LoanCOBParameter loanCOBParameter = new LoanCOBParameter(1L, 4L);
executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, loanCOBParameter);
- lenient().when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter))
+ lenient().when(
+ retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false))
.thenReturn(List.of(1L, 2L, 3L, 4L));
stepExecution.setExecutionContext(executionContext);
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 bbf77a9be..147cb883d 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
@@ -53,6 +53,8 @@ public class FetchAndLockLoanStepDefinitions implements En {
StepContribution contribution;
ArgumentCaptor<LocalDate> dateValueCaptor = ArgumentCaptor.forClass(LocalDate.class);
ArgumentCaptor<LoanCOBParameter> loanCOBParameterValueCaptor = ArgumentCaptor.forClass(LoanCOBParameter.class);
+
+ ArgumentCaptor<Boolean> isCatchUpParameterCaptor = ArgumentCaptor.forClass(Boolean.class);
private LockLoanTasklet lockLoanTasklet;
private String action;
private RepeatStatus result;
@@ -115,44 +117,52 @@ public class FetchAndLockLoanStepDefinitions implements En {
if ("empty steps".equals(action)) {
assertEquals(RepeatStatus.FINISHED, result);
} else if ("good".equals(action)) {
- verify(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture());
+ verify(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture(),
+ isCatchUpParameterCaptor.capture());
assertEquals(LocalDate.now(ZoneId.systemDefault()).minusDays(1), dateValueCaptor.getValue());
assertEquals(1L, loanCOBParameterValueCaptor.getValue().getMinLoanId());
assertEquals(3L, loanCOBParameterValueCaptor.getValue().getMaxLoanId());
assertEquals(RepeatStatus.FINISHED, result);
+ assertEquals(false, isCatchUpParameterCaptor.getValue());
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(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture());
+ verify(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture(),
+ isCatchUpParameterCaptor.capture());
assertEquals(LocalDate.now(ZoneId.systemDefault()).minusDays(1), dateValueCaptor.getValue());
assertEquals(1L, loanCOBParameterValueCaptor.getValue().getMinLoanId());
assertEquals(3L, loanCOBParameterValueCaptor.getValue().getMaxLoanId());
assertEquals(RepeatStatus.FINISHED, result);
+ assertEquals(false, isCatchUpParameterCaptor.getValue());
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(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture());
+ verify(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture(),
+ isCatchUpParameterCaptor.capture());
assertEquals(LocalDate.now(ZoneId.systemDefault()).minusDays(1), dateValueCaptor.getValue());
assertEquals(1L, loanCOBParameterValueCaptor.getValue().getMinLoanId());
assertEquals(3L, loanCOBParameterValueCaptor.getValue().getMaxLoanId());
assertEquals(RepeatStatus.FINISHED, result);
+ assertEquals(false, isCatchUpParameterCaptor.getValue());
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(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture());
+ verify(loanLockingService).applySoftLock(dateValueCaptor.capture(), loanCOBParameterValueCaptor.capture(),
+ isCatchUpParameterCaptor.capture());
assertEquals(LocalDate.now(ZoneId.systemDefault()).minusDays(1), dateValueCaptor.getValue());
assertEquals(1L, loanCOBParameterValueCaptor.getValue().getMinLoanId());
assertEquals(3L, loanCOBParameterValueCaptor.getValue().getMaxLoanId());
assertEquals(RepeatStatus.FINISHED, result);
+ assertEquals(false, isCatchUpParameterCaptor.getValue());
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution()
.getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER);
assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
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 7c0528a0f..2792ef9e9 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
@@ -74,7 +74,8 @@ public class LoanItemReaderStepDefinitions implements En {
stepExecutionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, loanCOBParameter);
stepExecution.setExecutionContext(stepExecutionContext);
- lenient().when(this.retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter))
+ lenient().when(
+ this.retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false))
.thenReturn(splitAccounts);
HashMap<BusinessDateType, LocalDate> businessDates = new HashMap<>();
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCatchUpIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCatchUpIntegrationTest.java
new file mode 100644
index 000000000..0bb766ece
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCatchUpIntegrationTest.java
@@ -0,0 +1,191 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.path.json.JsonPath;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BatchHelper;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanCOBCatchUpHelper;
+import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
+import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(LoanTestLifecycleExtension.class)
+public class LoanCatchUpIntegrationTest {
+
+ private static final String REPAYMENT_LOAN_PERMISSION = "REPAYMENT_LOAN";
+ private static final String READ_LOAN_PERMISSION = "READ_LOAN";
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private LoanCOBCatchUpHelper loanCOBCatchUpHelper;
+ private LoanTransactionHelper loanTransactionHelper;
+ private LoanAccountLockHelper loanAccountLockHelper;
+
+ @BeforeEach
+ public void setup() {
+ Utils.initializeRESTAssured();
+ loanCOBCatchUpHelper = new LoanCOBCatchUpHelper();
+ 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();
+ this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ }
+
+ @Test
+ public void testCatchUpInLockedInstance() {
+ 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 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", "Sample error");
+
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10));
+
+ loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
+ loanCOBCatchUpHelper.executeLoanCOBCatchUp();
+
+ Utils.conditionalSleepWithMaxWait(30, 1000, () -> loanCOBCatchUpHelper.isLoanCOBCatchUpRunning());
+
+ GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+ Assertions.assertEquals(LocalDate.of(2020, 3, 9), loan.getLastClosedBusinessDate());
+
+ requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
+
+ final BatchRequest br1 = BatchHelper.repayLoanRequestWithGivenLoanId(4730L, loanID, "10", LocalDate.of(2020, 3, 10));
+
+ final List<BatchRequest> batchRequests = new ArrayList<>();
+
+ batchRequests.add(br1);
+
+ final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests);
+
+ final List<BatchResponse> response = BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec,
+ this.responseSpec, jsonifiedRequest);
+ Assertions.assertEquals(HttpStatus.SC_OK, (long) response.get(0).getStatusCode(), "Verify Status Code 200 for Repayment");
+
+ loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+ Assertions.assertEquals(LocalDate.of(2020, 3, 9), 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")
+ .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+ .build(chargeId);
+ return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
+
+ private Integer applyForLoanApplication(final String clientID, final String loanProductID, final String savingsID, final String date) {
+
+ List<HashMap> collaterals = new ArrayList<>();
+ final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec);
+ Assertions.assertNotNull(collateralId);
+ final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, clientID,
+ collateralId);
+ Assertions.assertNotNull(clientCollateralId);
+ addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+ final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("15,000.00").withLoanTermFrequency("4")
+ .withLoanTermFrequencyAsMonths().withNumberOfRepayments("4").withRepaymentEveryAfter("1")
+ .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+ .withExpectedDisbursementDate(date).withSubmittedOnDate(date).withCollaterals(collaterals)
+ .build(clientID, loanProductID, savingsID);
+ return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+ }
+
+ private void addCollaterals(List<HashMap> collaterals, Integer collateralId, BigDecimal quantity) {
+ collaterals.add(collaterals(collateralId, quantity));
+ }
+
+ private HashMap<String, String> collaterals(Integer collateralId, BigDecimal quantity) {
+ HashMap<String, String> collateral = new HashMap<>(2);
+ collateral.put("clientCollateralId", collateralId.toString());
+ collateral.put("quantity", quantity.toString());
+ return collateral;
+ }
+
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
index 29bcf04e9..618ce73b5 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java
@@ -59,6 +59,7 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
+import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.HttpHostConnectException;
import org.slf4j.Logger;
@@ -144,6 +145,23 @@ public final class Utils {
}
}
+ /**
+ * Wait until the given condition is true or the maxRun is reached.
+ *
+ * @param maxRun
+ * max number of times to run the condition
+ * @param waitInMs
+ * wait time between evaluation in milliseconds
+ * @param waitCondition
+ * condition to evaluate
+ */
+ public static void conditionalSleepWithMaxWait(int maxRun, int waitInMs, Supplier<Boolean> waitCondition) {
+ do {
+ sleep(waitInMs);
+ maxRun--;
+ } while (maxRun > 0 && waitCondition.get());
+ }
+
private static void sleep(int seconds) {
try {
Thread.sleep(seconds * 1000);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java
index 983ae9ad7..5bfa9887f 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanAccountLockHelper.java
@@ -38,8 +38,13 @@ public class LoanAccountLockHelper extends IntegrationTest {
}
public String placeSoftLockOnLoanAccount(Integer loanId, String lockOwner) {
+ return placeSoftLockOnLoanAccount(loanId, lockOwner, null);
+ }
+
+ public String placeSoftLockOnLoanAccount(Integer loanId, String lockOwner, String error) {
return Utils.performServerPost(requestSpec, responseSpec,
INTERNAL_PLACE_LOCK_ON_LOAN_ACCOUNT_URL + loanId + "/place-lock/" + lockOwner + "?" + Utils.TENANT_IDENTIFIER,
- GSON.toJson(null));
+ error == null ? GSON.toJson(null) : error);
}
+
}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanCOBCatchUpHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanCOBCatchUpHelper.java
index 5a253ec04..606fef58a 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanCOBCatchUpHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanCOBCatchUpHelper.java
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.integrationtests.common.loans;
+import java.util.Objects;
import org.apache.fineract.client.models.GetOldestCOBProcessedLoanResponse;
import org.apache.fineract.client.models.IsCatchUpRunningResponse;
import org.apache.fineract.integrationtests.client.IntegrationTest;
@@ -27,6 +28,11 @@ public class LoanCOBCatchUpHelper extends IntegrationTest {
public LoanCOBCatchUpHelper() {}
+ public boolean isLoanCOBCatchUpRunning() {
+ Response<IsCatchUpRunningResponse> response = executeGetLoanCatchUpStatus();
+ return Boolean.TRUE.equals(Objects.requireNonNull(response.body()).getIsCatchUpRunning());
+ }
+
public Response<Void> executeLoanCOBCatchUp() {
return okR(fineract().loanCobCatchUpApi.executeLoanCOBCatchUp());
}
@@ -38,4 +44,5 @@ public class LoanCOBCatchUpHelper extends IntegrationTest {
public Response<IsCatchUpRunningResponse> executeGetLoanCatchUpStatus() {
return okR(fineract().loanCobCatchUpApi.isCatchUpRunning());
}
+
}