FINERACT-1678-loan-cob-drop-softlocking-use-lastclosedbusinessdate

This commit is contained in:
Ruchi Dhamankar
2023-05-26 12:48:44 +02:00
committed by Adam Saghy
parent 005af94291
commit e9bc0fe8c2
18 changed files with 66 additions and 380 deletions
@@ -50,11 +50,9 @@ public interface LoanAccountLockRepository extends JpaRepository<LoanAccountLock
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
delete from LoanAccountLock lck 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();
void removeLockByOwner();
}
@@ -19,5 +19,5 @@
package org.apache.fineract.cob.domain;
public enum LockOwner {
LOAN_COB_PARTITIONING, LOAN_COB_CHUNK_PROCESSING, LOAN_INLINE_COB_PROCESSING;
LOAN_COB_CHUNK_PROCESSING, LOAN_INLINE_COB_PROCESSING;
}
@@ -22,10 +22,7 @@ import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
@@ -69,23 +66,13 @@ public class ApplyLoanLockTasklet implements Tasklet {
List<LoanAccountLock> accountLocks = new ArrayList<>();
loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(loanLockingService.findAllByLoanIdIn(loanIdPartition)));
Map<Long, LoanAccountLock> alreadySoftLockedAccountsMap = accountLocks.stream()
.filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner()))
.collect(Collectors.toMap(LoanAccountLock::getLoanId, Function.identity()));
List<Long> alreadyLockedByChunkProcessingAccountIds = accountLocks.stream()
.filter(e -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
List<Long> toBeProcessedLoanIds = new ArrayList<>(alreadySoftLockedAccountsMap.keySet());
loanLockingService.upgradeLock(toBeProcessedLoanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING);
toBeProcessedLoanIds.addAll(alreadyLockedByChunkProcessingAccountIds);
List<Long> alreadyLockedByInlineCOBOrProcessedLoanIds = new ArrayList<>(loanIds);
alreadyLockedByInlineCOBOrProcessedLoanIds.removeAll(toBeProcessedLoanIds);
loanIds.removeAll(alreadyLockedByInlineCOBOrProcessedLoanIds);
List<Long> toBeProcessedLoanIds = new ArrayList<>(loanIds);
toBeProcessedLoanIds.removeAll(alreadyLockedByChunkProcessingAccountIds);
loanLockingService.applyLock(toBeProcessedLoanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING);
return RepeatStatus.FINISHED;
}
@@ -73,8 +73,6 @@ public class LoanCOBManagerConfiguration {
private BusinessEventNotifierService businessEventNotifierService;
@Autowired
private CustomJobParameterResolver customJobParameterResolver;
@Autowired
private LoanLockingService loanLockingService;
@Bean
@JobScope
@@ -93,11 +91,6 @@ public class LoanCOBManagerConfiguration {
return localStepBuilderFactory.get("Set loan ID parameter - Step").tasklet(loanIdParameterTasklet()).build();
}
@Bean
public Step lockStep() {
return localStepBuilderFactory.get("Lock loan accounts - Step").tasklet(lockLoanTasklet()).build();
}
@Bean
public Step resolveCustomJobParametersStep() {
return localStepBuilderFactory.get("Resolve custom job parameters - Step").tasklet(resolveCustomJobParametersTasklet())
@@ -115,12 +108,6 @@ public class LoanCOBManagerConfiguration {
return new LoanIdParameterTasklet(retrieveLoanIdService, customJobParameterResolver);
}
@Bean
@JobScope
public LockLoanTasklet lockLoanTasklet() {
return new LockLoanTasklet(loanLockingService, customJobParameterResolver);
}
@Bean
@JobScope
public ResolveLoanCOBCustomJobParametersTasklet resolveCustomJobParametersTasklet() {
@@ -138,7 +125,7 @@ public class LoanCOBManagerConfiguration {
return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
.listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
.start(resolveCustomJobParametersStep()) //
.next(loanIdParameterStep()).next(lockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
.next(loanIdParameterStep()).next(loanCOBStep()).next(stayedLockedStep()) //
.incrementer(new RunIdIncrementer()) //
.build();
}
@@ -18,16 +18,12 @@
*/
package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LockOwner;
public interface LoanLockingService {
void applySoftLock(LocalDate lastClosedBusinessDate, LoanCOBParameter loanCOBParameter, boolean isCatchUp);
void upgradeLock(List<Long> accountsToLock, LockOwner lockOwner);
void deleteByLoanIdInAndLockOwner(List<Long> loanIds, LockOwner lockOwner);
@@ -35,4 +31,6 @@ public interface LoanLockingService {
List<LoanAccountLock> findAllByLoanIdIn(List<Long> loanIds);
LoanAccountLock findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner);
void applyLock(List<Long> loanIds, LockOwner lockOwner);
}
@@ -18,11 +18,11 @@
*/
package org.apache.fineract.cob.loan;
import java.sql.PreparedStatement;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
@@ -53,25 +53,14 @@ public class LoanLockingServiceImpl implements LoanLockingService {
AND (? = loan.last_closed_business_date)
""";
private static final String BATCH_LOAN_LOCK_INSERT = """
INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date) VALUES (?,?,?,?,?)
""";
private final JdbcTemplate jdbcTemplate;
private final FineractProperties fineractProperties;
private final LoanAccountLockRepository loanAccountLockRepository;
@Override
public void applySoftLock(LocalDate lastClosedBusinessDate, LoanCOBParameter loanCOBParameter, boolean isCatchUp) {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
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());
ps.setObject(4, cobBusinessDate);
ps.setObject(5, loanCOBParameter.getMinLoanId());
ps.setObject(6, loanCOBParameter.getMaxLoanId());
ps.setObject(7, lastClosedBusinessDate);
});
}
@Override
public void upgradeLock(List<Long> accountsToLock, LockOwner lockOwner) {
jdbcTemplate.batchUpdate("""
@@ -96,6 +85,18 @@ public class LoanLockingServiceImpl implements LoanLockingService {
});
}
@Override
public void applyLock(List<Long> loanIds, LockOwner lockOwner) {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
jdbcTemplate.batchUpdate(BATCH_LOAN_LOCK_INSERT, loanIds, loanIds.size(), (PreparedStatement ps, Long loanId) -> {
ps.setLong(1, loanId);
ps.setLong(2, 1);
ps.setString(3, lockOwner.name());
ps.setObject(4, DateUtils.getOffsetDateTimeOfTenant());
ps.setObject(5, cobBusinessDate);
});
}
@Override
public void deleteByLoanIdInAndLockOwner(List<Long> loanIds, LockOwner lockOwner) {
loanAccountLockRepository.deleteByLoanIdInAndLockOwner(loanIds, lockOwner);
@@ -1,59 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.jetbrains.annotations.NotNull;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
@Slf4j
@RequiredArgsConstructor
public class LockLoanTasklet implements Tasklet {
private final LoanLockingService loanLockingService;
private final CustomJobParameterResolver customJobParameterResolver;
@Override
public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
LocalDate lastClosedBusinessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter))
.minusDays(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND);
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution().getExecutionContext()
.get(LoanCOBConstant.LOAN_COB_PARAMETER);
if (Objects.isNull(loanCOBParameter)
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))) {
loanCOBParameter = new LoanCOBParameter(0L, 0L);
}
loanLockingService.applySoftLock(lastClosedBusinessDate, loanCOBParameter,
customJobParameterResolver
.getCustomJobParameterById(contribution.getStepExecution(), LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME)
.map(Boolean::parseBoolean).orElse(false));
return RepeatStatus.FINISHED;
}
}
@@ -226,7 +226,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
}
private boolean isLockOverrulable(LoanAccountLock loanAccountLock) {
if (LockOwner.LOAN_COB_PARTITIONING.equals(loanAccountLock.getLockOwner()) || isBypassUser()) {
if (isBypassUser()) {
return true;
} else {
return StringUtils.isNotBlank(loanAccountLock.getError());
@@ -27,7 +27,5 @@ public interface LoanAccountLockService {
boolean isLoanHardLocked(Long loanId);
boolean isLoanSoftLocked(Long loanId);
void updateCobAndRemoveLocks();
}
@@ -49,16 +49,11 @@ public class LoanAccountLockServiceImpl implements LoanAccountLockService {
|| loanAccountLockRepository.existsByLoanIdAndLockOwner(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING);
}
@Override
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();
loanAccountLockRepository.removeLockByOwner();
}
}
@@ -121,7 +121,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchReque
} else {
try {
List<Long> loanIds = calculateRelevantLoanIds(request.getPathInfo());
if (!loanIds.isEmpty() && (isLoanSoftLocked(loanIds) || isLoanBehind(loanIds))) {
if (!loanIds.isEmpty() && isLoanBehind(loanIds)) {
executeInlineCob(loanIds);
}
proceed(filterChain, request, response);
@@ -184,17 +184,8 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchReque
}
}
private boolean isLoanSoftLocked(List<Long> loanIds) {
return isLoanLocked(loanIds, false);
}
private boolean isLoanHardLocked(List<Long> loanIds) {
return isLoanLocked(loanIds, true);
}
private boolean isLoanLocked(List<Long> loanIds, boolean isHardLock) {
return isHardLock ? loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked)
: loanIds.stream().anyMatch(loanAccountLockService::isLoanSoftLocked);
return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
}
private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response)
@@ -256,7 +247,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchReque
boolean bypassUser = isBypassUser();
if (!bypassUser) {
List<Long> result = calculateRelevantLoanIds(relativeUrl);
if (!result.isEmpty() && (isLoanSoftLocked(result) || isLoanBehind(result))) {
if (!result.isEmpty() && isLoanBehind(result)) {
executeInlineCob(result);
}
}
@@ -127,4 +127,5 @@
<include file="parts/0105_add_indices_loan_table.xml" relativeToChangelogFile="true" />
<include file="parts/0106_new_repayment_strategy.xml" relativeToChangelogFile="true" />
<include file="parts/0107_add_configuration_charges_accrual_date.xml" relativeToChangelogFile="true" />
<include file="parts/0108_precondition_check_cob_loan_account_lock.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="fineract" id="1">
<preConditions onFail="HALT" onFailMessage="Loan COB check failed: There should not be any soft locks for loan account.">
<sqlCheck expectedResult="0">select count(*) from m_loan_account_locks where lock_owner = 'LOAN_COB_PARTITIONING'</sqlCheck>
</preConditions>
</changeSet>
</databaseChangeLog>
@@ -83,10 +83,9 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
lenient().when(loanLockingService.findAllByLoanIdIn(Mockito.anyList())).thenThrow(new RuntimeException("fail"));
} else {
LoanAccountLock lock1 = new LoanAccountLock(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault()));
LoanAccountLock lock2 = new LoanAccountLock(2L, LockOwner.LOAN_COB_PARTITIONING, LocalDate.now(ZoneId.systemDefault()));
LoanAccountLock lock3 = new LoanAccountLock(3L, LockOwner.LOAN_INLINE_COB_PROCESSING,
LocalDate.now(ZoneId.systemDefault()));
List<LoanAccountLock> accountLocks = List.of(lock1, lock2, lock3);
List<LoanAccountLock> accountLocks = List.of(lock1, lock3);
lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
lenient().when(loanLockingService.findAllByLoanIdIn(Mockito.anyList())).thenReturn(accountLocks);
@@ -101,7 +100,7 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
Then("ApplyLoanLockTasklet.execute result should match", () -> {
assertEquals(RepeatStatus.FINISHED, resultItem);
verify(loanLockingService, Mockito.times(1)).upgradeLock(valueCaptor.capture(), lockOwnerValueCaptor.capture());
verify(loanLockingService, Mockito.times(1)).applyLock(valueCaptor.capture(), lockOwnerValueCaptor.capture());
List<Long> values = valueCaptor.getValue();
assertEquals(2L, values.get(0));
assertEquals(LockOwner.LOAN_COB_CHUNK_PROCESSING, lockOwnerValueCaptor.getValue());
@@ -1,178 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.loan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import io.cucumber.java8.En;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.repeat.RepeatStatus;
public class FetchAndLockLoanStepDefinitions implements En {
private final LoanAccountLockRepository loanAccountLockRepository = mock(LoanAccountLockRepository.class);
private final FineractProperties fineractProperties = mock(FineractProperties.class);
private final FineractProperties.FineractQueryProperties fineractQueryProperties = mock(
FineractProperties.FineractQueryProperties.class);
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;
private final LoanLockingService loanLockingService = mock(LoanLockingService.class);
private final CustomJobParameterResolver customJobParameterResolver = mock(CustomJobParameterResolver.class);
public FetchAndLockLoanStepDefinitions() {
Given("/^The FetchAndLockLoanTasklet.execute method with action (.*)$/", (String action) -> {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
HashMap<BusinessDateType, LocalDate> businessDateMap = new HashMap<>();
businessDateMap.put(BusinessDateType.COB_DATE, LocalDate.now(ZoneId.systemDefault()));
ThreadLocalContextUtil.setBusinessDates(businessDateMap);
this.action = action;
JobExecution jobExecution = new JobExecution(1L);
StepExecution stepExecution = new StepExecution("step", jobExecution);
contribution = new StepContribution(stepExecution);
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
LocalDate.now(ZoneId.systemDefault()).toString());
lenient().when(customJobParameterResolver.getCustomJobParameterSet(Mockito.any())).thenReturn(Optional.empty());
if ("empty loanIds".equals(action)) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(0L, 0L));
} else if ("good".equals(action)) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(1L, 3L));
lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(Collections.emptyList());
} else if ("soft lock".equals(action)) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(1L, 3L));
lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(
List.of(new LoanAccountLock(1L, LockOwner.LOAN_COB_PARTITIONING, LocalDate.now(ZoneId.systemDefault()))));
} else if ("inline cob".equals(action)) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(1L, 3L));
lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(
List.of(new LoanAccountLock(2L, LockOwner.LOAN_INLINE_COB_PROCESSING, LocalDate.now(ZoneId.systemDefault()))));
} else if ("chunk processing".equals(action)) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(1L, 3L));
lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(
List.of(new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault()))));
}
lockLoanTasklet = new LockLoanTasklet(loanLockingService, customJobParameterResolver);
});
When("FetchAndLockLoanTasklet.execute method executed", () -> {
result = lockLoanTasklet.execute(contribution, null);
});
Then("FetchAndLockLoanTasklet.execute result should match", () -> {
if ("empty steps".equals(action)) {
assertEquals(RepeatStatus.FINISHED, result);
} else if ("good".equals(action)) {
verify(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(),
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(),
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(),
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());
}
});
}
}
@@ -193,7 +193,6 @@ class LoanCOBApiFilterTest {
given(request.getPathInfo()).willReturn("/v1/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false);
given(context.authenticatedUser()).willReturn(appUser);
given(fineractProperties.getQuery()).willReturn(fineractQueryProperties);
given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000);
@@ -221,7 +220,6 @@ class LoanCOBApiFilterTest {
given(request.getPathInfo()).willReturn("/v1/loans/external-id/" + uuid + "/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false);
given(context.authenticatedUser()).willReturn(appUser);
given(loanRepository.findIdByExternalId(any())).willReturn(2L);
given(fineractProperties.getQuery()).willReturn(fineractQueryProperties);
@@ -250,7 +248,6 @@ class LoanCOBApiFilterTest {
given(request.getPathInfo()).willReturn("/v1/rescheduleloans/" + resourceId + "/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false);
given(fineractProperties.getQuery()).willReturn(fineractQueryProperties);
given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000);
LoanRescheduleRequest rescheduleRequest = mock(LoanRescheduleRequest.class);
@@ -265,24 +262,6 @@ class LoanCOBApiFilterTest {
verify(filterChain, times(1)).doFilter(request, response);
}
@Test
void shouldRunInlineCOBAndProceedWhenLoanIsSoftLocked() throws ServletException, IOException {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
AppUser appUser = mock(AppUser.class);
given(request.getPathInfo()).willReturn("/v1/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(true);
given(context.authenticatedUser()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
verify(inlineLoanCOBExecutorService, times(1)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB");
verify(filterChain, times(1)).doFilter(request, response);
}
@Test
void shouldRunInlineCOBAndProceedWhenLoanIsBehind() throws ServletException, IOException {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
@@ -299,16 +278,15 @@ class LoanCOBApiFilterTest {
LoanIdAndLastClosedBusinessDate result = mock(LoanIdAndLastClosedBusinessDate.class);
given(result.getId()).willReturn(2L);
given(result.getLastClosedBusinessDate()).willReturn(businessDate.minusDays(2));
given(request.getPathInfo()).willReturn("/v1/loans/2?command=approve");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(true);
given(fineractProperties.getQuery()).willReturn(fineractQueryProperties);
given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000);
given(loanRepository.findAllNonClosedLoansBehindOrNullByLoanIds(
given(loanRepository.findAllNonClosedLoansBehindByLoanIds(
eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList()))
.willReturn(Collections.singletonList(result));
given(context.authenticatedUser()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
@@ -335,7 +313,6 @@ class LoanCOBApiFilterTest {
given(request.getPathInfo()).willReturn("/v1/loans/2?command=approve");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false);
given(fineractProperties.getQuery()).willReturn(fineractQueryProperties);
given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000);
given(loanRepository.findAllNonClosedLoansBehindOrNullByLoanIds(
@@ -1,34 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
Feature: COB Loan fetch and (soft) lock Step
@cob
Scenario Outline: FetchAndLockLoanTasklet - run test
Given The FetchAndLockLoanTasklet.execute method with action <action>
When FetchAndLockLoanTasklet.execute method executed
Then FetchAndLockLoanTasklet.execute result should match
Examples:
|action|
|empty loanIds|
|good|
|soft lock|
|inline cob|
|chunk processing|
@@ -284,7 +284,6 @@ public class InlineLoanCOBTest {
requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
loanAccountLockHelper.placeSoftLockOnLoanAccount(loanID, "LOAN_COB_PARTITIONING");
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
loanTransactionHelper.makeRepayment("10 March 2020", 10.0f, loanID);
@@ -399,8 +398,6 @@ public class InlineLoanCOBTest {
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10));
requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
loanAccountLockHelper.placeSoftLockOnLoanAccount(loanID, "LOAN_COB_PARTITIONING");
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
final BatchRequest br1 = BatchHelper.oldRepayLoanRequestWithGivenLoanId(4730L, loanID, "10", LocalDate.of(2020, 3, 10));
@@ -468,8 +465,6 @@ public class InlineLoanCOBTest {
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10));
requestSpec = UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
loanAccountLockHelper.placeSoftLockOnLoanAccount(loanID, "LOAN_COB_PARTITIONING");
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
final BatchRequest br1 = BatchHelper.repayLoanRequestWithGivenLoanId(4730L, loanID, "10", LocalDate.of(2020, 3, 10));