FINERACT-1992: Backdated delinquency pause events
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 fcf9d40..22a606c 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 @@
                             .build();
 
                     // get list of charges for installments in same range
-                    List<LoanCharge> 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<LoanCharge> 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<LoanChargeDataRangeViewV1> 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 23a0f0c..2040299 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 @@
         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 63663d7..67ad3e7 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 @@
     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 @@
         assertEquals(loanForProcessing, loanPayloadForEvent);
 
     }
+
+    @Test
+    public void givenLoanAccountWhenBackdatedPauseActionThenLoanDelinquencyPauseChangeBusinessEventIsRaisedTest() {
+        ArgumentCaptor<LoanAccountDelinquencyPauseChangedBusinessEvent> 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<LoanDelinquencyAction> delinquencyActions = new ArrayList<>();
+        List<LoanDelinquencyActionData> 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<LoanDelinquencyRangeChangeBusinessEvent> 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<LoanDelinquencyAction> delinquencyActions = new ArrayList<>();
+        List<LoanDelinquencyActionData> 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);
+    }
+
 }