diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java index 163ee2730..56274956c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java @@ -162,11 +162,38 @@ public class DelinquencyActionParseAndValidator extends ParseAndValidator { } } - private boolean isOverlapping(LoanDelinquencyAction parsedDelinquencyAction, LoanDelinquencyActionData ldad) { - return ((!parsedDelinquencyAction.getStartDate().isAfter(ldad.getStartDate()) - && !ldad.getStartDate().isAfter(parsedDelinquencyAction.getEndDate())) - || (!parsedDelinquencyAction.getStartDate().isAfter(ldad.getEndDate()) - && !ldad.getEndDate().isAfter(parsedDelinquencyAction.getEndDate()))); + /** + *
+     *  we have an overlap when
+     *  (parsed.endDate > existing.startDate AND parsed.endDate < existing.endDate)
+     *
+     *  existing       |------------|
+     *  parsed               |----------------|
+     *
+     *  we also  have an overlap when
+     *  (parsed.startDate > existing.startDate AND parsed.startDate < existing.endDate)
+     *
+     *  existing            |------------|
+     *  parsed    |----------------|
+     *
+     *  There is no overlap like when they are right after each other:
+     *
+     *  existing  |------------|
+     *  parsed                 |----------------|
+     *
+     *  or
+     *
+     *  existing               |------------|
+     *  parsed   |-------------|
+     * 
+ * + * @param parsed + * @param existing + * @return + */ + private boolean isOverlapping(LoanDelinquencyAction parsed, LoanDelinquencyActionData existing) { + return (parsed.getEndDate().isAfter(existing.getStartDate()) && parsed.getEndDate().isBefore(existing.getEndDate())) + || (parsed.getStartDate().isAfter(existing.getStartDate()) && parsed.getStartDate().isBefore(existing.getEndDate())); } @org.jetbrains.annotations.NotNull diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java index cc8ee2117..34011234a 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java @@ -111,32 +111,6 @@ class DelinquencyActionParseAndValidatorTest { () -> underTest.validateAndParseUpdate(command, loan, existing, localDate("09 September 2022"))); } - @Test - public void testNewPauseEndIsTheSameAsExistingPauseStart() throws JsonProcessingException { - Loan loan = Mockito.mock(Loan.class); - Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); - - List existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022")); - JsonCommand command = delinquencyAction("pause", "09 September 2022", "15 September 2022"); - - assertPlatformValidationException("Delinquency pause period cannot overlap with another pause period", - "loan-delinquency-action-overlapping", - () -> underTest.validateAndParseUpdate(command, loan, existing, localDate("09 September 2022"))); - } - - @Test - public void testNewPauseEndIsTheSameAsExistingPauseEnd() throws JsonProcessingException { - Loan loan = Mockito.mock(Loan.class); - Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); - - List existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022")); - JsonCommand command = delinquencyAction("pause", "22 September 2022", "25 September 2022"); - - assertPlatformValidationException("Delinquency pause period cannot overlap with another pause period", - "loan-delinquency-action-overlapping", - () -> underTest.validateAndParseUpdate(command, loan, existing, localDate("09 September 2022"))); - } - @Test public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); @@ -276,6 +250,57 @@ class DelinquencyActionParseAndValidatorTest { .validateAndParseUpdate(delinquencyAction("resume", null, null), loan, List.of(), localDate("09 September 2022"))); } + @Test + public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingException { + Loan loan = Mockito.mock(Loan.class); + Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + + JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); + + List existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "18 September 2022")); + + LoanDelinquencyAction parsedDelinquencyAction = underTest.validateAndParseUpdate(command, loan, existing, + localDate("18 September 2022")); + Assertions.assertEquals(PAUSE, parsedDelinquencyAction.getAction()); + Assertions.assertEquals(localDate("18 September 2022"), parsedDelinquencyAction.getStartDate()); + Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate()); + } + + @Test + public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingException { + Loan loan = Mockito.mock(Loan.class); + Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + + JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); + + List existing = List.of(loanDelinquencyAction(PAUSE, "20 September 2022", "25 September 2022")); + + LoanDelinquencyAction parsedDelinquencyAction = underTest.validateAndParseUpdate(command, loan, existing, + localDate("18 September 2022")); + Assertions.assertEquals(PAUSE, parsedDelinquencyAction.getAction()); + Assertions.assertEquals(localDate("18 September 2022"), parsedDelinquencyAction.getStartDate()); + Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate()); + } + + @Test + public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonProcessingException { + Loan loan = Mockito.mock(Loan.class); + Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + + JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); + + List existing = List.of(// + loanDelinquencyAction(PAUSE, "15 September 2022", "20 September 2022"), // + loanDelinquencyAction(RESUME, "18 September 2022") // + ); + + LoanDelinquencyAction parsedDelinquencyAction = underTest.validateAndParseUpdate(command, loan, existing, + localDate("18 September 2022")); + Assertions.assertEquals(PAUSE, parsedDelinquencyAction.getAction()); + Assertions.assertEquals(localDate("18 September 2022"), parsedDelinquencyAction.getStartDate()); + Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate()); + } + @NotNull private JsonCommand delinquencyAction(@Nullable String action, @Nullable String startDate, @Nullable String endDate) throws JsonProcessingException {