This is an automated email from the ASF dual-hosted git repository.

taskain pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 7a417b058 FINERACT-1971: Delinquency pause validation bug fixes
7a417b058 is described below

commit 7a417b058b51fae211f106ae8543e55f7bea2823
Author: taskain7 <[email protected]>
AuthorDate: Tue Dec 12 10:53:19 2023 +0100

    FINERACT-1971: Delinquency pause validation bug fixes
---
 .../DelinquencyActionParseAndValidator.java        | 15 +++++++-
 .../DelinquencyActionParseAndValidatorTest.java    | 44 +++++++++++++++++++++-
 .../DelinquencyActionIntegrationTests.java         | 25 ++++++++++++
 3 files changed, 82 insertions(+), 2 deletions(-)

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 41f876353..380785c90 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
@@ -61,6 +61,7 @@ public class DelinquencyActionParseAndValidator extends 
ParseAndValidator {
         } else if 
(DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) {
             validateResumeStartDate(parsedDelinquencyAction, businessDate);
             validateResumeNoEndDate(parsedDelinquencyAction);
+            validateResumeDoesNotExist(parsedDelinquencyAction, 
savedDelinquencyActions);
             validateResumeShouldBeOnActivePause(parsedDelinquencyAction, 
effectiveDelinquencyList);
         }
         return parsedDelinquencyAction;
@@ -87,6 +88,17 @@ public class DelinquencyActionParseAndValidator extends 
ParseAndValidator {
         }
     }
 
+    private void validateResumeDoesNotExist(LoanDelinquencyAction 
parsedDelinquencyAction,
+            List<LoanDelinquencyAction> savedDelinquencyActions) {
+        boolean match = savedDelinquencyActions.stream() //
+                .filter(action -> 
DelinquencyAction.RESUME.equals(action.getAction())) //
+                .anyMatch(action -> 
parsedDelinquencyAction.getStartDate().isEqual(action.getStartDate()));
+        if (match) {
+            
raiseValidationError("loan-delinquency-action-resume-should-be-unique",
+                    "There is an existing Resume Delinquency Action on this 
date");
+        }
+    }
+
     private void validateResumeNoEndDate(LoanDelinquencyAction 
parsedDelinquencyAction) {
         if (parsedDelinquencyAction.getEndDate() != null) {
             
raiseValidationError("loan-delinquency-action-resume-should-have-no-end-date",
@@ -163,7 +175,8 @@ public class DelinquencyActionParseAndValidator extends 
ParseAndValidator {
      */
     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()));
+                || (parsed.getStartDate().isAfter(existing.getStartDate()) && 
parsed.getStartDate().isBefore(existing.getEndDate()))
+                || (parsed.getStartDate().isEqual(existing.getStartDate()) && 
parsed.getEndDate().isEqual(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 ceb4475dd..2c1d3638f 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
@@ -36,6 +36,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
@@ -136,6 +137,21 @@ class DelinquencyActionParseAndValidatorTest {
                 () -> underTest.validateAndParseUpdate(command, loan, 
existing, localDate("09 September 2022")));
     }
 
+    @Test
+    public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() 
throws JsonProcessingException {
+        Loan loan = Mockito.mock(Loan.class);
+        Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
+
+        List<LoanDelinquencyAction> existing = 
List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
+        JsonCommand command = delinquencyAction("pause", "15 September 2022", 
"22 September 2022");
+        List<LoanDelinquencyActionData> effectiveList = 
List.of(loanDelinquencyActionData(existing.get(0)));
+        
Mockito.when(delinquencyEffectivePauseHelper.calculateEffectiveDelinquencyList(existing)).thenReturn(effectiveList);
+
+        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 testNewPauseIsNotOverlappingBecauseThereWasAResume() throws 
JsonProcessingException {
         Loan loan = Mockito.mock(Loan.class);
@@ -214,6 +230,32 @@ class DelinquencyActionParseAndValidatorTest {
                 () -> underTest.validateAndParseUpdate(command, loan, 
List.of(), localDate("10 September 2022")));
     }
 
+    @Test
+    public void testValidationErrorResumeOnExistingResumeDate() throws 
JsonProcessingException {
+        Loan loan = Mockito.mock(Loan.class);
+        Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
+
+        JsonCommand command = delinquencyAction("resume", "09 September 2022", 
null);
+        List<LoanDelinquencyAction> existing = 
List.of(loanDelinquencyAction(PAUSE, "05 September 2022", "15 September 2022"));
+        List<LoanDelinquencyActionData> effectiveList = 
List.of(loanDelinquencyActionData(existing.get(0)));
+        
Mockito.when(delinquencyEffectivePauseHelper.calculateEffectiveDelinquencyList(existing)).thenReturn(effectiveList);
+
+        LoanDelinquencyAction parsedDelinquencyAction = 
underTest.validateAndParseUpdate(command, loan, existing,
+                localDate("09 September 2022"));
+        Assertions.assertEquals(RESUME, parsedDelinquencyAction.getAction());
+        Assertions.assertEquals(localDate("09 September 2022"), 
parsedDelinquencyAction.getStartDate());
+        Assertions.assertNull(parsedDelinquencyAction.getEndDate());
+
+        List<LoanDelinquencyAction> existing2 = 
List.of(loanDelinquencyAction(PAUSE, "05 September 2022", "15 September 2022"),
+                loanDelinquencyAction(RESUME, "09 September 2022", null));
+
+        JsonCommand command2 = delinquencyAction("resume", "09 September 
2022", null);
+
+        assertPlatformValidationException("There is an existing Resume 
Delinquency Action on this date",
+                "loan-delinquency-action-resume-should-be-unique",
+                () -> underTest.validateAndParseUpdate(command2, loan, 
existing2, localDate("09 September 2022")));
+    }
+
     @Test
     public void testValidationErrorPausePeriodShouldBeAtLeastOneDay() throws 
JsonProcessingException {
         Loan loan = Mockito.mock(Loan.class);
@@ -348,7 +390,7 @@ class DelinquencyActionParseAndValidatorTest {
     }
 
     private LoanDelinquencyAction loanDelinquencyAction(DelinquencyAction 
action, String startTime, String endTime) {
-        return new LoanDelinquencyAction(null, action, localDate(startTime), 
localDate(endTime));
+        return new LoanDelinquencyAction(null, action, localDate(startTime), 
Objects.isNull(endTime) ? null : localDate(endTime));
     }
 
     private LoanDelinquencyActionData 
loanDelinquencyActionData(LoanDelinquencyAction loanDelinquencyAction) {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
index 2f95ef397..ac409adae 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
@@ -222,6 +222,31 @@ public class DelinquencyActionIntegrationTests extends 
BaseLoanIntegrationTest {
         });
     }
 
+    @Test
+    public void testValidationErrorIsThrownWhenCreatingActionThatOverlaps() {
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            // Create Loan Product
+            Long loanProductId = createLoanProductWith25PctDownPayment(true, 
true);
+
+            // Apply and Approve Loan
+            Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 
January 2023", 1500.0, 2);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 
2023");
+
+            // Create Delinquency Pause for the Loan
+            loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, 
"01 January 2023", "15 January 2023");
+
+            // Create overlapping Delinquency Pause for the Loan
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> 
loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, "01 January 
2023", "15 January 2023"));
+            assertTrue(exception.getMessage().contains("Delinquency pause 
period cannot overlap with another pause period"));
+        });
+    }
+
     private void validateLoanDelinquencyPausePeriods(Long loanId, 
GetLoansLoanIdDelinquencyPausePeriod... pausePeriods) {
         GetLoansLoanIdResponse loan = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
         Assertions.assertNotNull(loan.getDelinquent());

Reply via email to