From 5edd18aec251df75157b272964744fdc7e64e597 Mon Sep 17 00:00:00 2001 From: Ruchi Dhamankar Date: Fri, 15 Dec 2023 10:47:42 +0530 Subject: [PATCH] FINERACT-1992: Backdated delinquency pause events --- ...ncyRangeChangeBusinessEventSerializer.java | 13 +- .../DelinquencyWritePlatformServiceImpl.java | 4 + ...tePlatformServiceRangeChangeEventTest.java | 117 ++++++++++++++++++ 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java index fcf9d4045b5..22a606c0a4f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java @@ -157,12 +157,13 @@ private List calculateInstallmentLevelDe .build(); // get list of charges for installments in same range - List chargesForInstallmentsInSameRange = loan.getLoanCharges().stream() - .filter(loanCharge -> !loanCharge.isPaid() && delinquentInstallmentsInSameRange.stream().anyMatch( - installmentForCharge -> (DateUtils.isAfter(loanCharge.getDueDate(), installmentForCharge.getFromDate()) - || DateUtils.isEqual(loanCharge.getDueDate(), installmentForCharge.getFromDate())) - && (DateUtils.isBefore(loanCharge.getDueDate(), installmentForCharge.getDueDate()) - || DateUtils.isEqual(loanCharge.getDueDate(), installmentForCharge.getDueDate())))) + List chargesForInstallmentsInSameRange = loan.getLoanCharges().stream().filter(loanCharge -> !loanCharge + .isPaid() + && delinquentInstallmentsInSameRange.stream().anyMatch(installmentForCharge -> (DateUtils + .isAfter(loanCharge.getEffectiveDueDate(), installmentForCharge.getFromDate()) + || DateUtils.isEqual(loanCharge.getEffectiveDueDate(), installmentForCharge.getFromDate())) + && (DateUtils.isBefore(loanCharge.getEffectiveDueDate(), installmentForCharge.getDueDate()) + || DateUtils.isEqual(loanCharge.getEffectiveDueDate(), installmentForCharge.getDueDate())))) .toList(); List charges = new ArrayList<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java index 23a0f0cda83..20402997778 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java @@ -236,6 +236,10 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand if (DateUtils.isBefore(parsedDelinquencyAction.getStartDate(), businessDate) && DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) { recalculateLoanDelinquencyData(loan); + // if pause end date is after current business date, loan delinquency pause flag is changed, emit event + if (DateUtils.isAfter(parsedDelinquencyAction.getEndDate(), businessDate)) { + businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan)); + } } businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan)); return new CommandProcessingResultBuilder().withCommandId(command.commandId()) // diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java index 63663d77fd7..67ad3e7f77e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyIterable; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -30,6 +31,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.ZoneId; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -38,23 +40,31 @@ import java.util.Map; import java.util.Optional; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.ActionContext; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountDelinquencyPauseChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; +import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction; +import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTag; import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; +import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper; +import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl; import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService; +import org.apache.fineract.portfolio.delinquency.validator.DelinquencyActionParseAndValidator; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator; import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData; @@ -100,6 +110,14 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { private LoanDelinquencyDomainService loanDelinquencyDomainService; @Mock private LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository; + @Mock + private DelinquencyReadPlatformService delinquencyReadPlatformService; + @Mock + private DelinquencyActionParseAndValidator delinquencyActionParseAndValidator; + @Mock + private LoanDelinquencyActionRepository loanDelinquencyActionRepository; + @Mock + private DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper; @InjectMocks private DelinquencyWritePlatformServiceImpl underTest; @@ -498,4 +516,103 @@ public void givenLoanAccountEnableInstallmentLevelDelinquencyWhenLoanIsOutOfDeli assertEquals(loanForProcessing, loanPayloadForEvent); } + + @Test + public void givenLoanAccountWhenBackdatedPauseActionThenLoanDelinquencyPauseChangeBusinessEventIsRaisedTest() { + ArgumentCaptor loanDelinquencyPauseChangeEvent = ArgumentCaptor + .forClass(LoanAccountDelinquencyPauseChangedBusinessEvent.class); + // given + Loan loanForProcessing = Mockito.mock(Loan.class); + loanForProcessing.setId(1L); + + JsonCommand command = Mockito.mock(JsonCommand.class); + + // Pause period + LocalDate startDate = DateUtils.getBusinessLocalDate().minusDays(8); + LocalDate endDate = DateUtils.getBusinessLocalDate().minusDays(1); + + List delinquencyActions = new ArrayList<>(); + List effectiveDelinquency = new ArrayList<>(); + CollectionData loanCollectionData = CollectionData.template(); + + when(loanRepository.findOneWithNotFoundDetection(anyLong())).thenReturn(loanForProcessing); + + when(delinquencyReadPlatformService.retrieveLoanDelinquencyActions(anyLong())).thenReturn(delinquencyActions); + LoanDelinquencyAction backdatedPauseAction = Mockito.mock(LoanDelinquencyAction.class); + backdatedPauseAction.setId(1L); + + when(delinquencyActionParseAndValidator.validateAndParseUpdate(command, loanForProcessing, delinquencyActions, + DateUtils.getBusinessLocalDate())).thenReturn(backdatedPauseAction); + when(backdatedPauseAction.getStartDate()).thenReturn(startDate); + when(backdatedPauseAction.getEndDate()).thenReturn(endDate); + when(backdatedPauseAction.getAction()).thenReturn(DelinquencyAction.PAUSE); + + when(loanDelinquencyActionRepository.saveAndFlush(backdatedPauseAction)).thenReturn(backdatedPauseAction); + + when(delinquencyEffectivePauseHelper.calculateEffectiveDelinquencyList(delinquencyActions)).thenReturn(effectiveDelinquency); + when(loanDelinquencyDomainService.getOverdueCollectionData(any(), anyList())).thenReturn(loanCollectionData); + when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty()); + + // when + underTest.createDelinquencyAction(loanForProcessing.getId(), command); + + // then + // verify event is raised + verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanDelinquencyPauseChangeEvent.capture()); + + Loan loanPayloadForEvent = loanDelinquencyPauseChangeEvent.getValue().get(); + assertEquals(loanForProcessing, loanPayloadForEvent); + + // verify no range change event for pause flag change as both start and end date are backdated + verify(businessEventNotifierService, times(0)).notifyPostBusinessEvent(any(LoanDelinquencyRangeChangeBusinessEvent.class)); + + } + + @Test + public void givenLoanAccountWhenBackdatedPauseActionThenLoanDelinquencyRangeChangeBusinessEventIsRaisedIfPauseFlagChangeTest() { + ArgumentCaptor loanDelinquencyRangeChangeEvent = ArgumentCaptor + .forClass(LoanDelinquencyRangeChangeBusinessEvent.class); + // given + Loan loanForProcessing = Mockito.mock(Loan.class); + loanForProcessing.setId(1L); + + JsonCommand command = Mockito.mock(JsonCommand.class); + + // Pause period + LocalDate startDate = DateUtils.getBusinessLocalDate().minusDays(2); + LocalDate endDate = DateUtils.getBusinessLocalDate().plusDays(10); + + List delinquencyActions = new ArrayList<>(); + List effectiveDelinquency = new ArrayList<>(); + CollectionData loanCollectionData = CollectionData.template(); + + when(loanRepository.findOneWithNotFoundDetection(anyLong())).thenReturn(loanForProcessing); + + when(delinquencyReadPlatformService.retrieveLoanDelinquencyActions(anyLong())).thenReturn(delinquencyActions); + LoanDelinquencyAction backdatedPauseAction = Mockito.mock(LoanDelinquencyAction.class); + backdatedPauseAction.setId(1L); + + when(delinquencyActionParseAndValidator.validateAndParseUpdate(command, loanForProcessing, delinquencyActions, + DateUtils.getBusinessLocalDate())).thenReturn(backdatedPauseAction); + when(backdatedPauseAction.getStartDate()).thenReturn(startDate); + when(backdatedPauseAction.getEndDate()).thenReturn(endDate); + when(backdatedPauseAction.getAction()).thenReturn(DelinquencyAction.PAUSE); + + when(loanDelinquencyActionRepository.saveAndFlush(backdatedPauseAction)).thenReturn(backdatedPauseAction); + + when(delinquencyEffectivePauseHelper.calculateEffectiveDelinquencyList(delinquencyActions)).thenReturn(effectiveDelinquency); + when(loanDelinquencyDomainService.getOverdueCollectionData(any(), anyList())).thenReturn(loanCollectionData); + when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty()); + + // when + underTest.createDelinquencyAction(loanForProcessing.getId(), command); + + // then + // verify event is raised + verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanDelinquencyRangeChangeEvent.capture()); + + Loan loanPayloadForEvent = loanDelinquencyRangeChangeEvent.getValue().get(); + assertEquals(loanForProcessing, loanPayloadForEvent); + } + }