Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
Expand Down Expand Up @@ -230,8 +231,12 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand
savedDelinquencyList, businessDate);

parsedDelinquencyAction.setLoan(loan);

LoanDelinquencyAction saved = loanDelinquencyActionRepository.saveAndFlush(parsedDelinquencyAction);
// if backdated pause recalculate delinquency data
if (DateUtils.isBefore(parsedDelinquencyAction.getStartDate(), businessDate)
&& DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
recalculateLoanDelinquencyData(loan);
}
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
.withEntityId(saved.getId()) //
Expand All @@ -242,6 +247,21 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand
.build();
}

private void recalculateLoanDelinquencyData(Loan loan) {
List<LoanDelinquencyAction> savedDelinquencyList = delinquencyReadPlatformService.retrieveLoanDelinquencyActions(loan.getId());
List<LoanDelinquencyActionData> effectiveDelinquencyList = delinquencyEffectivePauseHelper
.calculateEffectiveDelinquencyList(savedDelinquencyList);

CollectionData loanDelinquencyData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(loan.getId(),
loanDelinquencyData.getDelinquentDate(), loanDelinquencyData.getDelinquentDays(), loan);
if (loanScheduleDelinquencyData.getOverdueDays() > 0) {
applyDelinquencyTagToLoan(loanScheduleDelinquencyData, effectiveDelinquencyList);
} else {
removeDelinquencyTagToLoan(loan);
}
}

@Override
public void removeDelinquencyTagToLoan(final Loan loan) {
if (loan.isEnableInstallmentLevelDelinquency()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public LoanDelinquencyAction validateAndParseUpdate(@NotNull final JsonCommand c
validateLoanIsActive(loan);
if (DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
validateBothStartAndEndDatesAreProvided(parsedDelinquencyAction);
validatePauseStartAndEndDate(parsedDelinquencyAction, businessDate);
validatePauseStartAndEndDate(parsedDelinquencyAction);
validatePauseStartDateNotBeforeDisbursementDate(parsedDelinquencyAction, loan.getDisbursementDate());
validatePauseShallNotOverlap(parsedDelinquencyAction, effectiveDelinquencyList);
} else if (DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) {
validateResumeStartDate(parsedDelinquencyAction, businessDate);
Expand Down Expand Up @@ -117,15 +118,18 @@ private void validateResumeStartDate(LoanDelinquencyAction parsedDelinquencyActi
}
}

private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction, LocalDate businessDate) {
private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction) {
if (parsedDelinquencyAction.getStartDate().equals(parsedDelinquencyAction.getEndDate())) {
raiseValidationError("loan-delinquency-action-invalid-start-date-and-end-date",
"Delinquency pause period must be at least one day");
}
}

if (businessDate.isAfter(parsedDelinquencyAction.getStartDate())) {
raiseValidationError("loan-delinquency-action-invalid-start-date", "Start date of pause period must be in the future",
START_DATE);
private void validatePauseStartDateNotBeforeDisbursementDate(LoanDelinquencyAction parsedDelinquencyAction,
LocalDate firstDisbursalDate) {
if (firstDisbursalDate.isAfter(parsedDelinquencyAction.getStartDate())) {
raiseValidationError("loan-delinquency-action-invalid-start-date",
"Start date of pause period must be after first disbursal date", START_DATE);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class DelinquencyActionParseAndValidatorTest {
public void testParseAndValidationIsOKForPause() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022"));

JsonCommand command = delinquencyAction("pause", "09 September 2022", "19 September 2022");

Expand Down Expand Up @@ -96,6 +97,7 @@ public void testParseAndValidationIsOKForResume() throws JsonProcessingException
public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022"));

List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022"));
JsonCommand command = delinquencyAction("pause", "09 September 2022", "15 September 2022");
Expand All @@ -111,6 +113,7 @@ public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws
public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022"));
JsonCommand command = delinquencyAction("pause", "15 September 2022", "23 September 2022");
Expand All @@ -126,7 +129,7 @@ public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessing
public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);

Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
JsonCommand command = delinquencyAction("pause", "13 September 2022", "20 September 2022");
List<LoanDelinquencyActionData> effectiveList = List.of(loanDelinquencyActionData(existing.get(0)));
Expand All @@ -141,6 +144,7 @@ public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessin
public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
JsonCommand command = delinquencyAction("pause", "15 September 2022", "22 September 2022");
Expand All @@ -156,6 +160,7 @@ public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws
public void testNewPauseIsNotOverlappingBecauseThereWasAResume() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");

Expand Down Expand Up @@ -269,13 +274,15 @@ public void testValidationErrorPausePeriodShouldBeAtLeastOneDay() throws JsonPro
}

@Test
public void testValidationErrorPausePeriodMustBeInFuture() throws JsonProcessingException {
public void testValidationErrorPausePeriodMustNotBeBeforeDisbursement() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

JsonCommand command = delinquencyAction("pause", "08 September 2022", "09 September 2022");

assertPlatformValidationException("Start date of pause period must be in the future", "loan-delinquency-action-invalid-start-date",
assertPlatformValidationException("Start date of pause period must be after first disbursal date",
"loan-delinquency-action-invalid-start-date",
() -> underTest.validateAndParseUpdate(command, loan, List.of(), localDate("09 September 2022")));
}

Expand Down Expand Up @@ -308,6 +315,7 @@ public void testStartDateIsMissingForResume() {
public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");

Expand All @@ -324,6 +332,7 @@ public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingE
public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");

Expand All @@ -340,6 +349,7 @@ public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingExcepti
public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));

JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");

Expand All @@ -355,6 +365,21 @@ public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonPr
Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate());
}

@Test
public void testParseAndValidationIsOKForBackdatedPause() throws JsonProcessingException {
Loan loan = Mockito.mock(Loan.class);
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022"));

JsonCommand command = delinquencyAction("pause", "08 September 2022", "19 September 2022");

LoanDelinquencyAction parsedDelinquencyAction = underTest.validateAndParseUpdate(command, loan, List.of(),
localDate("09 September 2022"));
Assertions.assertEquals(PAUSE, parsedDelinquencyAction.getAction());
Assertions.assertEquals(localDate("08 September 2022"), parsedDelinquencyAction.getStartDate());
Assertions.assertEquals(localDate("19 September 2022"), parsedDelinquencyAction.getEndDate());
}

@NotNull
private JsonCommand delinquencyAction(@Nullable String action, @Nullable String startDate, @Nullable String endDate)
throws JsonProcessingException {
Expand Down
Loading