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, JpaSpecificationExecutor { @@ -32,4 +34,27 @@ public interface LoanAccountLockRepository extends JpaRepository findAllByLoanIdIn(List 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> loanIdPartitions = Lists.partition(loanIds, getInClauseParameterSizeLimit()); List accountLocks = new ArrayList<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCatchUpSupport.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCatchUpSupport.java new file mode 100644 index 000000000..ce382ca31 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCatchUpSupport.java @@ -0,0 +1,33 @@ +/** + * 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 org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.StepExecution; + +public interface LoanCatchUpSupport { + + default boolean isCatchUp(StepContribution contribution) { + return isCatchUp(contribution.getStepExecution()); + } + + 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 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 retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter) { - return loanRepository.findAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter.getMinLoanId(), - loanCOBParameter.getMaxLoanId(), - ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND)); + public List 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 retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List loanIds); List retrieveLoanIdsOldestCobProcessed(LocalDate businessDate); - List retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter); + List retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(LoanCOBParameter loanCOBParameter, boolean isCatchUp); List 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 = 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 = 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, 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, 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, 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 findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate, @Param("loanIds") List loanIds); @@ -210,6 +217,10 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat List 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 findAllNonClosedLoansByLastClosedBusinessDateNotNullAndMinAndMaxLoanId(@Param("minLoanId") Long minLoanId, + @Param("maxLoanId") Long maxLoanId, @Param("cobBusinessDate") LocalDate cobBusinessDate); + @Query(FIND_OLDEST_COB_PROCESSED_LOAN) List 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 dateValueCaptor = ArgumentCaptor.forClass(LocalDate.class); ArgumentCaptor loanCOBParameterValueCaptor = ArgumentCaptor.forClass(LoanCOBParameter.class); + + ArgumentCaptor 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 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 batchRequests = new ArrayList<>(); + + batchRequests.add(br1); + + final String jsonifiedRequest = BatchHelper.toJsonString(batchRequests); + + final List 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 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 collaterals, Integer collateralId, BigDecimal quantity) { + collaterals.add(collaterals(collateralId, quantity)); + } + + private HashMap collaterals(Integer collateralId, BigDecimal quantity) { + HashMap 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 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 response = executeGetLoanCatchUpStatus(); + return Boolean.TRUE.equals(Objects.requireNonNull(response.body()).getIsCatchUpRunning()); + } + public Response executeLoanCOBCatchUp() { return okR(fineract().loanCobCatchUpApi.executeLoanCOBCatchUp()); } @@ -38,4 +44,5 @@ public class LoanCOBCatchUpHelper extends IntegrationTest { public Response executeGetLoanCatchUpStatus() { return okR(fineract().loanCobCatchUpApi.isCatchUpRunning()); } + }