FINERACT-1992-FINERACT-Installment-level-delinquency-calculation

This commit is contained in:
Ruchi Dhamankar
2023-10-27 14:31:59 +02:00
committed by Adam Saghy
parent 585d200c67
commit 8cea837a5b
14 changed files with 828 additions and 58 deletions
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.deliquency;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyIterable;
import static org.mockito.Mockito.times;
@@ -30,6 +31,7 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -46,13 +48,17 @@ import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketReposit
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTag;
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService;
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
@@ -88,6 +94,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private LoanDelinquencyDomainService loanDelinquencyDomainService;
@Mock
private LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository;
@InjectMocks
private DelinquencyWritePlatformServiceImpl underTest;
@@ -120,11 +129,15 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null,
null, null);
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
LoanDelinquencyData loanDelinquencyData = new LoanDelinquencyData(collectionData, installmentsCollection);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
when(loanDelinquencyDomainService.getOverdueCollectionData(loanForProcessing)).thenReturn(collectionData);
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
// when
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
@@ -155,11 +168,17 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 2L,
loanForProcessing);
CollectionData collectionData = CollectionData.template();
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
LoanDelinquencyData loanDelinquencyData = new LoanDelinquencyData(collectionData, installmentsCollection);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
when(loanDelinquencyDomainService.getOverdueCollectionData(loanForProcessing)).thenReturn(CollectionData.template());
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
// when
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
@@ -189,4 +208,142 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
}
@Test
public void givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinquencyRangeIsSetForInstallmentTest() {
ArgumentCaptor<List<LoanInstallmentDelinquencyTag>> loanInstallmentDelinquencyTagsArgumentCaptor = ArgumentCaptor
.forClass(List.class);
// given
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
delinquencyBucket.setRanges(listDelinquencyRanges);
final Long daysDiff = 2L;
final LocalDate fromDate = DateUtils.getBusinessLocalDate().minusMonths(1).minusDays(daysDiff);
final LocalDate dueDate = DateUtils.getBusinessLocalDate().minusDays(daysDiff);
final BigDecimal installmentPrincipalAmount = BigDecimal.valueOf(100);
final BigDecimal zeroAmount = BigDecimal.ZERO;
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1, fromDate, dueDate,
installmentPrincipalAmount, zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount);
installment.setId(1L);
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = Arrays.asList(installment);
LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2);
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
loanForProcessing);
CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null,
null, null);
CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate,
installmentPrincipalAmount, null, null, null, null);
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
installmentsCollection.put(1L, installmentCollectionData);
LoanDelinquencyData loanDelinquencyData = new LoanDelinquencyData(collectionData, installmentsCollection);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
when(loanInstallmentDelinquencyTagRepository.findByLoanAndInstallment(loanForProcessing, repaymentScheduleInstallments.get(0)))
.thenReturn(Optional.empty());
// when
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
// then
verify(loanDelinquencyTagRepository, times(1)).saveAllAndFlush(anyIterable());
verify(loanInstallmentDelinquencyTagRepository, times(1)).saveAllAndFlush(loanInstallmentDelinquencyTagsArgumentCaptor.capture());
List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags = loanInstallmentDelinquencyTagsArgumentCaptor.getValue();
assertEquals(1, installmentDelinquencyTags.size());
assertEquals(1, installmentDelinquencyTags.get(0).getInstallment().getInstallmentNumber());
assertEquals(1, installmentDelinquencyTags.get(0).getDelinquencyRange().getId());
assertEquals(installmentPrincipalAmount, installmentDelinquencyTags.get(0).getOutstandingAmount());
}
@Test
public void givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinquencyRangeChangesForInstallmentTest() {
ArgumentCaptor<List<LoanInstallmentDelinquencyTag>> loanInstallmentDelinquencyTagsArgumentCaptor = ArgumentCaptor
.forClass(List.class);
ArgumentCaptor<LoanInstallmentDelinquencyTag> loanInstallmentDelinquencyTagArgumentCaptorForDelete = ArgumentCaptor
.forClass(LoanInstallmentDelinquencyTag.class);
// given
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
delinquencyBucket.setRanges(listDelinquencyRanges);
final Long daysDiff = 2L;
final LocalDate fromDate = DateUtils.getBusinessLocalDate().minusMonths(1).minusDays(daysDiff);
final LocalDate dueDate = DateUtils.getBusinessLocalDate().minusDays(daysDiff);
final BigDecimal installmentPrincipalAmount = BigDecimal.valueOf(100);
final BigDecimal zeroAmount = BigDecimal.ZERO;
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1, fromDate, dueDate,
installmentPrincipalAmount, zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount);
installment.setId(1L);
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = Arrays.asList(installment);
LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29);
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
loanForProcessing);
CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, BigDecimal.ZERO, null, null,
null, null);
CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate,
installmentPrincipalAmount, null, null, null, null);
Map<Long, CollectionData> installmentsCollection = new HashMap<>();
installmentsCollection.put(1L, installmentCollectionData);
LoanDelinquencyData loanDelinquencyData = new LoanDelinquencyData(collectionData, installmentsCollection);
LoanInstallmentDelinquencyTag previousInstallmentTag = new LoanInstallmentDelinquencyTag();
previousInstallmentTag.setDelinquencyRange(range1);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
when(loanDelinquencyDomainService.getLoanDelinquencyData(loanForProcessing)).thenReturn(loanDelinquencyData);
when(loanInstallmentDelinquencyTagRepository.findByLoanAndInstallment(loanForProcessing, repaymentScheduleInstallments.get(0)))
.thenReturn(Optional.of(previousInstallmentTag));
// when
underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
// then
verify(loanDelinquencyTagRepository, times(1)).saveAllAndFlush(anyIterable());
verify(loanInstallmentDelinquencyTagRepository, times(1)).saveAllAndFlush(loanInstallmentDelinquencyTagsArgumentCaptor.capture());
verify(loanInstallmentDelinquencyTagRepository, times(1)).delete(loanInstallmentDelinquencyTagArgumentCaptorForDelete.capture());
List<LoanInstallmentDelinquencyTag> installmentDelinquencyTags = loanInstallmentDelinquencyTagsArgumentCaptor.getValue();
assertEquals(1, installmentDelinquencyTags.size());
assertEquals(1, installmentDelinquencyTags.get(0).getInstallment().getInstallmentNumber());
assertEquals(2, installmentDelinquencyTags.get(0).getDelinquencyRange().getId());
assertEquals(installmentPrincipalAmount, installmentDelinquencyTags.get(0).getOutstandingAmount());
LoanInstallmentDelinquencyTag deletedInstallmentDelinquencyTag = loanInstallmentDelinquencyTagArgumentCaptorForDelete.getValue();
assertNotNull(deletedInstallmentDelinquencyTag);
assertEquals(previousInstallmentTag, deletedInstallmentDelinquencyTag);
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.deliquency;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
@@ -43,6 +44,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainServiceImpl;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -185,4 +187,95 @@ public class LoanDelinquencyDomainServiceTest {
}
@Test
public void givenLoanInstallmentWithOverdueEnableInstallmentDelinquencyThenCalculateDelinquentData() {
// given
final Long daysDiff = 2L;
final LocalDate fromDate = businessDate.minusMonths(1).minusDays(daysDiff);
final LocalDate dueDate = businessDate.minusDays(daysDiff);
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, 1, fromDate, dueDate, principal,
zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount);
installment.setId(1L);
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = Arrays.asList(installment);
// when
when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0);
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Collections.emptyList());
when(loan.getLastLoanRepaymentScheduleInstallment()).thenReturn(repaymentScheduleInstallments.get(0));
when(loan.getCurrency()).thenReturn(currency);
when(loan.isEnableInstallmentLevelDelinquency()).thenReturn(true);
LoanDelinquencyData collectionData = underTest.getLoanDelinquencyData(loan);
// then
assertNotNull(collectionData);
assertNotNull(collectionData.getLoanInstallmentsCollectionData());
assertEquals(1L, collectionData.getLoanInstallmentsCollectionData().size());
CollectionData loanCollectionData = collectionData.getLoanCollectionData();
CollectionData installmentCollectionData = collectionData.getLoanInstallmentsCollectionData().get(1L);
assertEquals(daysDiff, loanCollectionData.getDelinquentDays());
assertEquals(dueDate, loanCollectionData.getDelinquentDate());
assertEquals(loanCollectionData.getDelinquentDays(), loanCollectionData.getPastDueDays());
assertEquals(daysDiff, installmentCollectionData.getDelinquentDays());
assertEquals(dueDate, installmentCollectionData.getDelinquentDate());
assertEquals(installmentCollectionData.getDelinquentDays(), installmentCollectionData.getPastDueDays());
}
@Test
public void givenLoanInstallmentWithoutOverdueWithChargebackAndEnableInstallmentDelinquencyThenCalculateDelinquentData() {
// given
PaymentDetail paymentDetail = Mockito.mock(PaymentDetail.class);
Long daysDiff = 2L;
final LocalDate fromDate = businessDate.minusMonths(1).plusDays(daysDiff);
final LocalDate dueDate = businessDate.plusDays(daysDiff);
final LocalDate transactionDate = businessDate.minusDays(daysDiff);
final Money zeroMoney = Money.zero(currency);
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, 1, fromDate, dueDate, principal,
zeroAmount, zeroAmount, zeroAmount, false, new HashSet<>(), zeroAmount);
installment.setId(1L);
LoanTransaction loanTransaction = LoanTransaction.chargeback(loan, Money.of(currency, principal), paymentDetail, transactionDate,
null);
installment.getLoanTransactionToRepaymentScheduleMappings().add(LoanTransactionToRepaymentScheduleMapping
.createFrom(loanTransaction, installment, zeroMoney, zeroMoney, zeroMoney, zeroMoney));
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = Arrays.asList(installment);
// when
when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0);
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
when(loan.isEnableInstallmentLevelDelinquency()).thenReturn(true);
when(loan.getCurrency()).thenReturn(currency);
when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Arrays.asList(loanTransaction));
LoanDelinquencyData collectionData = underTest.getLoanDelinquencyData(loan);
// then
assertNotNull(collectionData);
assertNotNull(collectionData.getLoanInstallmentsCollectionData());
assertEquals(1L, collectionData.getLoanInstallmentsCollectionData().size());
CollectionData loanCollectionData = collectionData.getLoanCollectionData();
CollectionData installmentCollectionData = collectionData.getLoanInstallmentsCollectionData().get(1L);
assertEquals(daysDiff, loanCollectionData.getDelinquentDays());
assertEquals(transactionDate, loanCollectionData.getDelinquentDate());
assertEquals(loanCollectionData.getDelinquentDays(), loanCollectionData.getPastDueDays());
// then
assertEquals(daysDiff, installmentCollectionData.getDelinquentDays());
assertEquals(transactionDate, installmentCollectionData.getDelinquentDate());
assertEquals(installmentCollectionData.getDelinquentDays(), installmentCollectionData.getPastDueDays());
assertEquals(0, principal.compareTo(installmentCollectionData.getDelinquentAmount()));
}
}