From 36f907906d6ad4dceb3cf246622aaf7d0bc49fa1 Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Wed, 8 Mar 2023 18:52:11 +0100 Subject: [PATCH] FINERACT-1678: Inline COB filter fix --- .../InlineLoanCOBExecutorServiceImpl.java | 2 +- .../jobs/filter/LoanCOBApiFilter.java | 4 +- .../loanaccount/domain/LoanRepository.java | 10 +- .../InlineLoanCOBExecutorServiceImplTest.java | 2 +- .../jobs/filter/LoanCOBApiFilterTest.java | 97 +++++++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java index 4ff06aece..12e31614e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java @@ -155,7 +155,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService getLoansToBeProcessed(List loanIds, LocalDate cobBusinessDate) { - return loanRepository.findAllNonClosedLoansBehindByLoanIds(cobBusinessDate, loanIds); + return loanRepository.findAllNonClosedLoansBehindOrNullByLoanIds(cobBusinessDate, loanIds); } private List getLoanAccountLocks(List loanIds) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java index 9812f6f55..09ddddcff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java @@ -66,6 +66,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte private static final List HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); + public static final Pattern IGNORE_LOAN_PATH_PATTERN = Pattern.compile("\\/loans\\/catch-up"); public static final Pattern LOAN_PATH_PATTERN = Pattern.compile("\\/?loans\\/(?:external-id\\/)?([^\\/\\?]+).*"); public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = Pattern.compile("\\/?loans\\/glimAccount\\/(\\d+).*"); @@ -224,7 +225,8 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte if (StringUtils.isBlank(pathInfo)) { return false; } - return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && URL_FUNCTION.test(pathInfo); + return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find() + && URL_FUNCTION.test(pathInfo); } private boolean isGlim(String pathInfo) { 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 691550356..08e3cd0ef 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 @@ -84,11 +84,13 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat // should follow the logic of `FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE` query String FIND_OLDEST_COB_PROCESSED_LOAN = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.loanStatus in (100,200,300,303,304) and loan.lastClosedBusinessDate = (select min(l.lastClosedBusinessDate) from Loan l where l" - + ".loanStatus in (100,200,300,303,304) and l.lastClosedBusinessDate <> :cobBusinessDate)"; + + ".loanStatus in (100,200,300,303,304) and l.lastClosedBusinessDate < :cobBusinessDate)"; - 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 or " + String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_LOAN_IDS = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate < :cobBusinessDate or " + "loan.lastClosedBusinessDate is null)"; + String FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and loan.loanStatus in (100,200,300,303,304) and loan.lastClosedBusinessDate < :cobBusinessDate"; + String FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate"; @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) @@ -194,6 +196,10 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat List findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate, @Param("loanIds") List loanIds); + @Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_LOAN_IDS) + List findAllNonClosedLoansBehindOrNullByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate, + @Param("loanIds") List loanIds); + @Query(FIND_OLDEST_COB_PROCESSED_LOAN) List findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java index d940fd6fb..407dcfe3d 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java @@ -72,7 +72,7 @@ class InlineLoanCOBExecutorServiceImplTest { ThreadLocalContextUtil.setBusinessDates(businessDates); when(transactionTemplate.execute(any())).thenThrow(new LoanAccountLockCannotBeOverruledException("")); - when(loanRepository.findAllNonClosedLoansBehindByLoanIds(any(), anyList())).thenReturn(List.of(loan)); + when(loanRepository.findAllNonClosedLoansBehindOrNullByLoanIds(any(), anyList())).thenReturn(List.of(loan)); assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB")); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java index e31e89647..6043d748d 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.UUID; import javax.servlet.FilterChain; import javax.servlet.ServletException; +import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate; import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; import org.apache.fineract.cob.service.LoanAccountLockService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; @@ -227,6 +228,102 @@ class LoanCOBApiFilterTest { verify(filterChain, times(1)).doFilter(request, response); } + @Test + void shouldRunInlineCOBAndProceedWhenLoanIsBehind() throws ServletException, IOException { + MockHttpServletRequest request = mock(MockHttpServletRequest.class); + MockHttpServletResponse response = mock(MockHttpServletResponse.class); + FilterChain filterChain = mock(FilterChain.class); + AppUser appUser = mock(AppUser.class); + + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + HashMap businessDates = new HashMap<>(); + LocalDate businessDate = LocalDate.now(ZoneId.systemDefault()); + businessDates.put(BusinessDateType.BUSINESS_DATE, businessDate); + businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1)); + ThreadLocalContextUtil.setBusinessDates(businessDates); + + LoanIdAndLastClosedBusinessDate result = mock(LoanIdAndLastClosedBusinessDate.class); + given(result.getId()).willReturn(2L); + given(request.getPathInfo()).willReturn("/loans/2?command=approve"); + given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); + given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false); + given(loanRepository.findAllNonClosedLoansBehindByLoanIds( + eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())) + .willReturn(Collections.singletonList(result)); + + 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 shouldNotRunInlineCOBAndProceedWhenLoanIsNotBehind() throws ServletException, IOException { + MockHttpServletRequest request = mock(MockHttpServletRequest.class); + MockHttpServletResponse response = mock(MockHttpServletResponse.class); + FilterChain filterChain = mock(FilterChain.class); + AppUser appUser = mock(AppUser.class); + + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + HashMap businessDates = new HashMap<>(); + LocalDate businessDate = LocalDate.now(ZoneId.systemDefault()); + businessDates.put(BusinessDateType.BUSINESS_DATE, businessDate); + businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1)); + ThreadLocalContextUtil.setBusinessDates(businessDates); + + LoanIdAndLastClosedBusinessDate result = mock(LoanIdAndLastClosedBusinessDate.class); + given(result.getId()).willReturn(2L); + given(request.getPathInfo()).willReturn("/loans/2?command=approve"); + given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); + given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false); + given(loanRepository.findAllNonClosedLoansBehindByLoanIds( + eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())) + .willReturn(Collections.emptyList()); + + given(context.authenticatedUser()).willReturn(appUser); + + testObj.doFilterInternal(request, response, filterChain); + verify(inlineLoanCOBExecutorService, times(0)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); + verify(filterChain, times(1)).doFilter(request, response); + } + + @Test + void shouldNotRunInlineCOBAndProceedWhenLoanIsBehindForLoanCreation() 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("/loans"); + given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + + given(context.authenticatedUser()).willReturn(appUser); + + testObj.doFilterInternal(request, response, filterChain); + verify(inlineLoanCOBExecutorService, times(0)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); + verify(filterChain, times(1)).doFilter(request, response); + } + + @Test + void shouldNotRunInlineCOBForCatchUp() 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("/loans/catch-up"); + given(request.getMethod()).willReturn(HTTPMethods.POST.value()); + + given(context.authenticatedUser()).willReturn(appUser); + + testObj.doFilterInternal(request, response, filterChain); + verify(inlineLoanCOBExecutorService, times(0)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB"); + verify(filterChain, times(1)).doFilter(request, response); + } + @Test void shouldRejectWhenLoanIsHardLocked() throws ServletException, IOException { MockHttpServletRequest request = mock(MockHttpServletRequest.class);