FINERACT-1992: Backdated delinquency pause
This commit is contained in:
committed by
Adam Saghy
parent
da2cdd8bfd
commit
6526e101a1
+21
-1
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
|
|||||||
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
|
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
|
||||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
||||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
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.DelinquencyBucket;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings;
|
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings;
|
||||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
|
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
|
||||||
@@ -230,8 +231,12 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
savedDelinquencyList, businessDate);
|
savedDelinquencyList, businessDate);
|
||||||
|
|
||||||
parsedDelinquencyAction.setLoan(loan);
|
parsedDelinquencyAction.setLoan(loan);
|
||||||
|
|
||||||
LoanDelinquencyAction saved = loanDelinquencyActionRepository.saveAndFlush(parsedDelinquencyAction);
|
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));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan));
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||||
.withEntityId(saved.getId()) //
|
.withEntityId(saved.getId()) //
|
||||||
@@ -242,6 +247,21 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
.build();
|
.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
|
@Override
|
||||||
public void removeDelinquencyTagToLoan(final Loan loan) {
|
public void removeDelinquencyTagToLoan(final Loan loan) {
|
||||||
if (loan.isEnableInstallmentLevelDelinquency()) {
|
if (loan.isEnableInstallmentLevelDelinquency()) {
|
||||||
|
|||||||
+9
-5
@@ -56,7 +56,8 @@ public class DelinquencyActionParseAndValidator extends ParseAndValidator {
|
|||||||
validateLoanIsActive(loan);
|
validateLoanIsActive(loan);
|
||||||
if (DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
|
if (DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
|
||||||
validateBothStartAndEndDatesAreProvided(parsedDelinquencyAction);
|
validateBothStartAndEndDatesAreProvided(parsedDelinquencyAction);
|
||||||
validatePauseStartAndEndDate(parsedDelinquencyAction, businessDate);
|
validatePauseStartAndEndDate(parsedDelinquencyAction);
|
||||||
|
validatePauseStartDateNotBeforeDisbursementDate(parsedDelinquencyAction, loan.getDisbursementDate());
|
||||||
validatePauseShallNotOverlap(parsedDelinquencyAction, effectiveDelinquencyList);
|
validatePauseShallNotOverlap(parsedDelinquencyAction, effectiveDelinquencyList);
|
||||||
} else if (DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) {
|
} else if (DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) {
|
||||||
validateResumeStartDate(parsedDelinquencyAction, businessDate);
|
validateResumeStartDate(parsedDelinquencyAction, businessDate);
|
||||||
@@ -117,15 +118,18 @@ public class DelinquencyActionParseAndValidator extends ParseAndValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction, LocalDate businessDate) {
|
private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction) {
|
||||||
if (parsedDelinquencyAction.getStartDate().equals(parsedDelinquencyAction.getEndDate())) {
|
if (parsedDelinquencyAction.getStartDate().equals(parsedDelinquencyAction.getEndDate())) {
|
||||||
raiseValidationError("loan-delinquency-action-invalid-start-date-and-end-date",
|
raiseValidationError("loan-delinquency-action-invalid-start-date-and-end-date",
|
||||||
"Delinquency pause period must be at least one day");
|
"Delinquency pause period must be at least one day");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (businessDate.isAfter(parsedDelinquencyAction.getStartDate())) {
|
private void validatePauseStartDateNotBeforeDisbursementDate(LoanDelinquencyAction parsedDelinquencyAction,
|
||||||
raiseValidationError("loan-delinquency-action-invalid-start-date", "Start date of pause period must be in the future",
|
LocalDate firstDisbursalDate) {
|
||||||
START_DATE);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-3
@@ -65,6 +65,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testParseAndValidationIsOKForPause() throws JsonProcessingException {
|
public void testParseAndValidationIsOKForPause() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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");
|
JsonCommand command = delinquencyAction("pause", "09 September 2022", "19 September 2022");
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws JsonProcessingException {
|
public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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"));
|
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022"));
|
||||||
JsonCommand command = delinquencyAction("pause", "09 September 2022", "15 September 2022");
|
JsonCommand command = delinquencyAction("pause", "09 September 2022", "15 September 2022");
|
||||||
@@ -111,6 +113,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessingException {
|
public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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"));
|
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022"));
|
||||||
JsonCommand command = delinquencyAction("pause", "15 September 2022", "23 September 2022");
|
JsonCommand command = delinquencyAction("pause", "15 September 2022", "23 September 2022");
|
||||||
@@ -126,7 +129,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessingException {
|
public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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"));
|
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
|
||||||
JsonCommand command = delinquencyAction("pause", "13 September 2022", "20 September 2022");
|
JsonCommand command = delinquencyAction("pause", "13 September 2022", "20 September 2022");
|
||||||
List<LoanDelinquencyActionData> effectiveList = List.of(loanDelinquencyActionData(existing.get(0)));
|
List<LoanDelinquencyActionData> effectiveList = List.of(loanDelinquencyActionData(existing.get(0)));
|
||||||
@@ -141,6 +144,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws JsonProcessingException {
|
public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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"));
|
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
|
||||||
JsonCommand command = delinquencyAction("pause", "15 September 2022", "22 September 2022");
|
JsonCommand command = delinquencyAction("pause", "15 September 2022", "22 September 2022");
|
||||||
@@ -156,6 +160,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testNewPauseIsNotOverlappingBecauseThereWasAResume() throws JsonProcessingException {
|
public void testNewPauseIsNotOverlappingBecauseThereWasAResume() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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");
|
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
|
||||||
|
|
||||||
@@ -269,13 +274,15 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidationErrorPausePeriodMustBeInFuture() throws JsonProcessingException {
|
public void testValidationErrorPausePeriodMustNotBeBeforeDisbursement() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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");
|
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")));
|
() -> underTest.validateAndParseUpdate(command, loan, List.of(), localDate("09 September 2022")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +315,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingException {
|
public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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");
|
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
|
||||||
|
|
||||||
@@ -324,6 +332,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingException {
|
public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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");
|
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
|
||||||
|
|
||||||
@@ -340,6 +349,7 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonProcessingException {
|
public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonProcessingException {
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
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");
|
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
|
||||||
|
|
||||||
@@ -355,6 +365,21 @@ class DelinquencyActionParseAndValidatorTest {
|
|||||||
Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate());
|
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
|
@NotNull
|
||||||
private JsonCommand delinquencyAction(@Nullable String action, @Nullable String startDate, @Nullable String endDate)
|
private JsonCommand delinquencyAction(@Nullable String action, @Nullable String startDate, @Nullable String endDate)
|
||||||
throws JsonProcessingException {
|
throws JsonProcessingException {
|
||||||
|
|||||||
+137
-3
@@ -22,6 +22,7 @@ import static java.lang.Boolean.TRUE;
|
|||||||
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||||
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE;
|
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE;
|
||||||
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
|
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -32,11 +33,14 @@ import java.time.LocalDate;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.apache.fineract.client.models.BusinessDateRequest;
|
import org.apache.fineract.client.models.BusinessDateRequest;
|
||||||
import org.apache.fineract.client.models.GetDelinquencyActionsResponse;
|
import org.apache.fineract.client.models.GetDelinquencyActionsResponse;
|
||||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdDelinquencyPausePeriod;
|
import org.apache.fineract.client.models.GetLoansLoanIdDelinquencyPausePeriod;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdLoanInstallmentLevelDelinquency;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
@@ -44,6 +48,7 @@ import org.apache.fineract.client.models.PostLoansDelinquencyActionResponse;
|
|||||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||||
|
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -201,7 +206,7 @@ public class DelinquencyActionIntegrationTests extends BaseLoanIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidationErrorIsThrownWhenCreatingActionInThePast() {
|
public void testValidationErrorIsThrownWhenCreatingPauseActionWithBackdatedStartDateBeforeDisbursement() {
|
||||||
runAt("01 January 2023", () -> {
|
runAt("01 January 2023", () -> {
|
||||||
// Create Client
|
// Create Client
|
||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
@@ -215,10 +220,80 @@ public class DelinquencyActionIntegrationTests extends BaseLoanIntegrationTest {
|
|||||||
// Disburse Loan
|
// Disburse Loan
|
||||||
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
|
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
|
||||||
|
|
||||||
// Create Delinquency Pause for the Loan in the past
|
// Create Delinquency Pause for the Loan before disbursement date
|
||||||
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
||||||
() -> loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, "05 December 2022", "15 January 2023"));
|
() -> loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, "05 December 2022", "15 January 2023"));
|
||||||
assertTrue(exception.getMessage().contains("Start date of pause period must be in the future"));
|
assertTrue(exception.getMessage().contains("Start date of pause period must be after first disbursal date"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateAndVerifyBackdatedPauseDelinquencyAction() {
|
||||||
|
runAt("30 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, "25 December 2022", 1500.0, 3,
|
||||||
|
req -> req.submittedOnDate("25 December 2022"));
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "25 December 2022");
|
||||||
|
|
||||||
|
// Create Delinquency Pause for the Loan in the past
|
||||||
|
PostLoansDelinquencyActionResponse response = loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE,
|
||||||
|
"28 January 2023", "15 February 2023");
|
||||||
|
|
||||||
|
List<GetDelinquencyActionsResponse> loanDelinquencyActions = loanTransactionHelper.getLoanDelinquencyActions(loanId);
|
||||||
|
Assertions.assertNotNull(loanDelinquencyActions);
|
||||||
|
Assertions.assertEquals(1, loanDelinquencyActions.size());
|
||||||
|
Assertions.assertEquals("PAUSE", loanDelinquencyActions.get(0).getAction());
|
||||||
|
Assertions.assertEquals(LocalDate.parse("28 January 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getStartDate());
|
||||||
|
Assertions.assertEquals(LocalDate.parse("15 February 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getEndDate());
|
||||||
|
|
||||||
|
// Validate Active Delinquency Pause Period on Loan
|
||||||
|
validateLoanDelinquencyPausePeriods(loanId, pausePeriods("28 January 2023", "15 February 2023", true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerifyLoanDelinquencyRecalculationForBackdatedPauseDelinquencyAction() {
|
||||||
|
runAt("30 January 2023", () -> {
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
Long loanProductId = createLoanProductWith25PctDownPaymentAndDelinquencyBucket(true, true, true);
|
||||||
|
|
||||||
|
// Apply and Approve Loan
|
||||||
|
Long loanId = applyAndApproveLoan(clientId, loanProductId, "25 December 2022", 1500.0, 3,
|
||||||
|
req -> req.submittedOnDate("25 December 2022"));
|
||||||
|
|
||||||
|
// Disburse Loan
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "25 December 2022");
|
||||||
|
|
||||||
|
// Loan delinquency data before backdated pause
|
||||||
|
verifyLoanDelinquencyData(loanId, 6, new InstallmentDelinquencyData(4, 10, BigDecimal.valueOf(250.0)));
|
||||||
|
|
||||||
|
// Create Delinquency Pause for the Loan in the past
|
||||||
|
PostLoansDelinquencyActionResponse response = loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE,
|
||||||
|
"27 January 2023", "15 February 2023");
|
||||||
|
|
||||||
|
List<GetDelinquencyActionsResponse> loanDelinquencyActions = loanTransactionHelper.getLoanDelinquencyActions(loanId);
|
||||||
|
Assertions.assertNotNull(loanDelinquencyActions);
|
||||||
|
Assertions.assertEquals(1, loanDelinquencyActions.size());
|
||||||
|
Assertions.assertEquals("PAUSE", loanDelinquencyActions.get(0).getAction());
|
||||||
|
Assertions.assertEquals(LocalDate.parse("27 January 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getStartDate());
|
||||||
|
Assertions.assertEquals(LocalDate.parse("15 February 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getEndDate());
|
||||||
|
|
||||||
|
// Loan delinquency data calculation after backdated pause
|
||||||
|
verifyLoanDelinquencyData(loanId, 3, new InstallmentDelinquencyData(1, 3, BigDecimal.valueOf(250.0)));
|
||||||
|
|
||||||
|
// Validate Active Delinquency Pause Period on Loan
|
||||||
|
validateLoanDelinquencyPausePeriods(loanId, pausePeriods("27 January 2023", "15 February 2023", true));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +340,24 @@ public class DelinquencyActionIntegrationTests extends BaseLoanIntegrationTest {
|
|||||||
return pausePeriod;
|
return pausePeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyLoanDelinquencyData(Long loanId, Integer loanLevelDelinquentDays,
|
||||||
|
InstallmentDelinquencyData... expectedInstallmentLevelInstallmentDelinquencyData) {
|
||||||
|
GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(loan.getDelinquent());
|
||||||
|
List<GetLoansLoanIdLoanInstallmentLevelDelinquency> installmentLevelDelinquency = loan.getDelinquent()
|
||||||
|
.getInstallmentLevelDelinquency();
|
||||||
|
|
||||||
|
assertThat(loan.getDelinquent().getDelinquentDays()).isEqualTo(loanLevelDelinquentDays);
|
||||||
|
|
||||||
|
assertThat(installmentLevelDelinquency.get(0).getMaximumAgeDays())
|
||||||
|
.isEqualTo(expectedInstallmentLevelInstallmentDelinquencyData[0].maxAgeDays);
|
||||||
|
assertThat(installmentLevelDelinquency.get(0).getMinimumAgeDays())
|
||||||
|
.isEqualTo(expectedInstallmentLevelInstallmentDelinquencyData[0].minAgeDays);
|
||||||
|
assertThat(installmentLevelDelinquency.get(0).getDelinquentAmount())
|
||||||
|
.isEqualByComparingTo(expectedInstallmentLevelInstallmentDelinquencyData[0].delinquentAmount);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled) {
|
private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled) {
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
|
||||||
product.setMultiDisburseLoan(multiDisburseEnabled);
|
product.setMultiDisburseLoan(multiDisburseEnabled);
|
||||||
@@ -294,4 +387,45 @@ public class DelinquencyActionIntegrationTests extends BaseLoanIntegrationTest {
|
|||||||
return loanProductId;
|
return loanProductId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long createLoanProductWith25PctDownPaymentAndDelinquencyBucket(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled,
|
||||||
|
boolean installmentLevelDelinquencyEnabled) {
|
||||||
|
// Create DelinquencyBuckets
|
||||||
|
Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, List.of(//
|
||||||
|
Pair.of(1, 3), //
|
||||||
|
Pair.of(4, 10), //
|
||||||
|
Pair.of(11, 60), //
|
||||||
|
Pair.of(61, null)//
|
||||||
|
));
|
||||||
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
|
||||||
|
product.setDelinquencyBucketId(delinquencyBucketId.longValue());
|
||||||
|
product.setMultiDisburseLoan(multiDisburseEnabled);
|
||||||
|
product.setEnableDownPayment(true);
|
||||||
|
|
||||||
|
product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE);
|
||||||
|
product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled);
|
||||||
|
product.setEnableInstallmentLevelDelinquency(installmentLevelDelinquencyEnabled);
|
||||||
|
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||||
|
GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper
|
||||||
|
.retrieveLoanProductById(loanProductResponse.getResourceId());
|
||||||
|
|
||||||
|
Long loanProductId = loanProductResponse.getResourceId();
|
||||||
|
|
||||||
|
assertEquals(TRUE, getLoanProductsProductIdResponse.getEnableDownPayment());
|
||||||
|
assertNotNull(getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment());
|
||||||
|
assertEquals(0, getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE));
|
||||||
|
assertEquals(autoDownPaymentEnabled, getLoanProductsProductIdResponse.getEnableAutoRepaymentForDownPayment());
|
||||||
|
assertEquals(multiDisburseEnabled, getLoanProductsProductIdResponse.getMultiDisburseLoan());
|
||||||
|
return loanProductId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class InstallmentDelinquencyData {
|
||||||
|
|
||||||
|
Integer minAgeDays;
|
||||||
|
Integer maxAgeDays;
|
||||||
|
BigDecimal delinquentAmount;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user