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 9dd401fec..571fb3a25 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 @@ -31,18 +31,22 @@ import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.UriInfo; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; import org.apache.fineract.infrastructure.core.filters.BatchFilter; import org.apache.fineract.infrastructure.core.filters.BatchFilterChain; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException; import org.apache.http.HttpStatus; import org.springframework.http.HttpMethod; @@ -57,6 +61,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte private final LoanAccountLockService loanAccountLockService; private final PlatformSecurityContext context; private final InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService; + private final LoanRepository loanRepository; private static final List HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); @@ -115,9 +120,9 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte proceed(filterChain, request, response); } else { try { - List result = calculateRelevantLoanIds(request.getPathInfo()); - if (isLoanSoftLocked(result)) { - executeInlineCob(result); + List loanIds = calculateRelevantLoanIds(request.getPathInfo()); + if (isLoanSoftLocked(loanIds) || isLoanBehind(loanIds)) { + executeInlineCob(loanIds); } proceed(filterChain, request, response); } catch (LoanIdsHardLockedException e) { @@ -130,6 +135,11 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte } } + private boolean isLoanBehind(List loanIds) { + return CollectionUtils.isNotEmpty(loanRepository + .findAllNonClosedLoansBehindByLoanIds(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), loanIds)); + } + private List calculateRelevantLoanIds(String pathInfo) { boolean isGlim = isGlim(pathInfo); 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 f37895f34..0ccf932f3 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 @@ -18,6 +18,8 @@ */ package org.apache.fineract.infrastructure.jobs.filter; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -27,15 +29,22 @@ import com.sun.research.ws.wadl.HTTPMethods; import java.io.IOException; import java.io.PrintWriter; import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.Collections; +import java.util.HashMap; import javax.servlet.FilterChain; import javax.servlet.ServletException; import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Assertions; @@ -63,6 +72,8 @@ class LoanCOBApiFilterTest { private PlatformSecurityContext context; @Mock private InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService; + @Mock + private LoanRepository loanRepository; @Test void shouldLoanAndExternalMatchToo() { @@ -117,34 +128,52 @@ class LoanCOBApiFilterTest { } @Test - void shouldProceedWhenLoanIsNotLocked() throws ServletException, IOException { + void shouldProceedWhenLoanIsNotLockedAndNoLoanIsBehind() 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); given(request.getPathInfo()).willReturn("/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(loanRepository.findAllNonClosedLoansBehindByLoanIds( + eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())) + .willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); verify(filterChain, times(1)).doFilter(request, response); } @Test - void shouldProceedWhenExternalLoanIsNotLocked() throws ServletException, IOException { + void shouldProceedWhenExternalLoanIsNotLockedAndNotBehind() 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); given(request.getPathInfo()).willReturn("/loans/external-id/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(loanRepository.findAllNonClosedLoansBehindByLoanIds( + eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())) + .willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); verify(filterChain, times(1)).doFilter(request, response); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java index 71cd3cc39..984cc6959 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java @@ -230,7 +230,7 @@ public class InlineLoanCOBTest { } @Test - public void testInlineCOBOnRepayment() { + public void testInlineCOBOnRepaymentWithSoftLockedLoan() { 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"); @@ -280,6 +280,55 @@ public class InlineLoanCOBTest { Assertions.assertEquals(LocalDate.of(2020, 3, 9), loan.getLastClosedBusinessDate()); } + @Test + public void testInlineCOBCatchUpOnRepaymentWithNotLockedLoan() { + 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)); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanID.longValue())); + GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID); + Assertions.assertEquals(LocalDate.of(2020, 3, 2), loan.getLastClosedBusinessDate()); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 10)); + + createNewSimpleUserWithoutBypassPermission(); + loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + + loanTransactionHelper.makeRepayment("10 March 2020", 10.0f, loanID); + + loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID); + Assertions.assertEquals(LocalDate.of(2020, 3, 9), loan.getLastClosedBusinessDate()); + } + @Test public void testInlineCOBOnBatchAPI() { GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);