From 7a417b058b51fae211f106ae8543e55f7bea2823 Mon Sep 17 00:00:00 2001 From: taskain7 Date: Tue, 12 Dec 2023 10:53:19 +0100 Subject: [PATCH] FINERACT-1971: Delinquency pause validation bug fixes --- .../DelinquencyActionParseAndValidator.java | 15 ++++++- ...elinquencyActionParseAndValidatorTest.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 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 existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022")); + JsonCommand command = delinquencyAction("pause", "15 September 2022", "22 September 2022"); + List 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 existing = List.of(loanDelinquencyAction(PAUSE, "05 September 2022", "15 September 2022")); + List 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 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());