This is an automated email from the ASF dual-hosted git repository.
adamsaghy 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 48300cd19 FINERACT-1992: Multi delinquency implication on events
48300cd19 is described below
commit 48300cd19dc0c9cdf78e8b8fc0125f815859a930
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Tue Nov 21 14:00:02 2023 +0530
FINERACT-1992: Multi delinquency implication on events
---
.../loan/v1/LoanAccountDelinquencyRangeDataV1.avsc | 7 +
...=> LoanInstallmentDelinquencyBucketDataV1.avsc} | 36 +----
...inquencyRangeChangeBusinessEventSerializer.java | 86 +++++++++++-
.../data/LoanInstallmentDelinquencyTagData.java | 2 +
.../LoanInstallmentDelinquencyTagRepository.java | 2 +-
.../DelinquencyWritePlatformServiceImpl.java | 13 +-
...AccountDelinquencyRangeEventSerializerTest.java | 149 ++++++++++++++++++++-
7 files changed, 255 insertions(+), 40 deletions(-)
diff --git
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
index 3da294421..95d504c54 100644
---
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
+++
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
@@ -50,6 +50,13 @@
"null",
"string"
]
+ },
+ {
+ "name": "installmentDelinquencyBuckets",
+ "type": {
+ "type": "array",
+ "items":
"org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1"
+ }
}
]
}
diff --git
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInstallmentDelinquencyBucketDataV1.avsc
similarity index 61%
copy from
fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
copy to
fineract-avro-schemas/src/main/avro/loan/v1/LoanInstallmentDelinquencyBucketDataV1.avsc
index 3da294421..22c306a23 100644
---
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
+++
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInstallmentDelinquencyBucketDataV1.avsc
@@ -1,24 +1,8 @@
{
- "name": "LoanAccountDelinquencyRangeDataV1",
+ "name": "LoanInstallmentDelinquencyBucketDataV1",
"namespace": "org.apache.fineract.avro.loan.v1",
"type": "record",
"fields": [
- {
- "name": "loanId",
- "type": "long"
- },
- {
- "name": "loanAccountNo",
- "type": "string"
- },
- {
- "default": null,
- "name": "loanExternalId",
- "type": [
- "null",
- "string"
- ]
- },
{
"default": null,
"name": "delinquencyRange",
@@ -27,6 +11,11 @@
"org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1"
]
},
+ {
+ "name": "amount",
+ "doc": "Contains installments total, fee, interest, principal and
penalty amount summaries",
+ "type": "org.apache.fineract.avro.loan.v1.LoanAmountDataV1"
+ },
{
"name": "charges",
"type": {
@@ -37,19 +26,6 @@
{
"name": "currency",
"type": "org.apache.fineract.avro.generic.v1.CurrencyDataV1"
- },
- {
- "name": "amount",
- "doc": "Contains installments total, fee, interest, principal and
penalty amount summaries",
- "type": "org.apache.fineract.avro.loan.v1.LoanAmountDataV1"
- },
- {
- "default": null,
- "name": "delinquentDate",
- "type": [
- "null",
- "string"
- ]
}
]
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
index 7fb24d9eb..fcf9d4045 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
@@ -19,8 +19,12 @@
package
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.function.BiFunction;
+import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer;
import org.apache.fineract.avro.generator.ByteBufferSerializable;
@@ -28,6 +32,8 @@ import
org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1;
import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
import org.apache.fineract.avro.loan.v1.LoanAmountDataV1;
import org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1;
+import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import
org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
import
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.generic.CurrencyDataMapper;
@@ -36,10 +42,12 @@ import
org.apache.fineract.infrastructure.event.external.service.serialization.m
import
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroDateTimeMapper;
import
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
import
org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -94,6 +102,10 @@ public class
LoanDelinquencyRangeChangeBusinessEventSerializer implements Busine
.build();
DelinquencyRangeDataV1 delinquencyRange =
mapper.map(data.getDelinquencyRange());
+
+ List<LoanInstallmentDelinquencyBucketDataV1>
installmentsDelinquencyData =
calculateInstallmentLevelDelinquencyData(event.get(),
+ data);
+
LoanAccountDelinquencyRangeDataV1.Builder builder =
LoanAccountDelinquencyRangeDataV1.newBuilder();
return builder//
.setLoanId(id)//
@@ -104,7 +116,79 @@ public class
LoanDelinquencyRangeChangeBusinessEventSerializer implements Busine
.setAmount(amount)//
.setCurrency(currencyMapper.map(data.getCurrency()))//
.setDelinquentDate(delinquentDate)//
- .build();
+
.setInstallmentDelinquencyBuckets(installmentsDelinquencyData).build();
+ }
+
+ private List<LoanInstallmentDelinquencyBucketDataV1>
calculateInstallmentLevelDelinquencyData(Loan loan, LoanAccountData data) {
+ List<LoanInstallmentDelinquencyBucketDataV1>
loanInstallmentDelinquencyData = new ArrayList<>();
+ if (loan.isEnableInstallmentLevelDelinquency()) {
+ Collection<LoanInstallmentDelinquencyTagData>
installmentDelinquencyTags = delinquencyReadPlatformService
+
.retrieveLoanInstallmentsCurrentDelinquencyTag(loan.getId());
+ if (installmentDelinquencyTags != null &&
installmentDelinquencyTags.size() > 0) {
+ // group installments that are in same range
+ Map<Long, List<LoanInstallmentDelinquencyTagData>>
installmentsInSameRange = installmentDelinquencyTags.stream().collect(
+ Collectors.groupingBy(installmentDelnquencyTags ->
installmentDelnquencyTags.getDelinquencyRange().getId()));
+ // for installments in each range, get details from loan
repayment schedule installment, add amounts,
+ // list charges
+ for (Map.Entry<Long, List<LoanInstallmentDelinquencyTagData>>
installmentDelinquencyTagData : installmentsInSameRange
+ .entrySet()) {
+ // get installments details
+ List<LoanRepaymentScheduleInstallment>
delinquentInstallmentsInSameRange = loan.getRepaymentScheduleInstallments()
+ .stream().filter(installment ->
installmentDelinquencyTagData.getValue().stream()
+ .anyMatch(installmentTag ->
installmentTag.getId().equals(installment.getId())))
+ .toList();
+ // add amounts
+ LoanAmountDataV1 amount = LoanAmountDataV1.newBuilder()//
+
.setPrincipalAmount(delinquentInstallmentsInSameRange.stream()
+ .map(instlment ->
instlment.getPrincipalOutstanding(loan.getCurrency()).getAmount())
+ .reduce(BigDecimal.ZERO,
BigDecimal::add))//
+
.setFeeAmount(delinquentInstallmentsInSameRange.stream()
+ .map(instlment ->
instlment.getFeeChargesOutstanding(loan.getCurrency()).getAmount())
+ .reduce(BigDecimal.ZERO,
BigDecimal::add))//
+
.setInterestAmount(delinquentInstallmentsInSameRange.stream()
+ .map(instlment ->
instlment.getInterestOutstanding(loan.getCurrency()).getAmount())
+ .reduce(BigDecimal.ZERO,
BigDecimal::add))//
+
.setPenaltyAmount(delinquentInstallmentsInSameRange.stream()
+ .map(instlment ->
instlment.getPenaltyChargesOutstanding(loan.getCurrency()).getAmount())
+ .reduce(BigDecimal.ZERO,
BigDecimal::add))//
+
.setTotalAmount(delinquentInstallmentsInSameRange.stream()
+ .map(instlment ->
instlment.getTotalOutstanding(loan.getCurrency()).getAmount())
+ .reduce(BigDecimal.ZERO,
BigDecimal::add))//
+ .build();
+
+ // get list of charges for installments in same range
+ List<LoanCharge> chargesForInstallmentsInSameRange =
loan.getLoanCharges().stream()
+ .filter(loanCharge -> !loanCharge.isPaid() &&
delinquentInstallmentsInSameRange.stream().anyMatch(
+ installmentForCharge ->
(DateUtils.isAfter(loanCharge.getDueDate(), installmentForCharge.getFromDate())
+ ||
DateUtils.isEqual(loanCharge.getDueDate(), installmentForCharge.getFromDate()))
+ &&
(DateUtils.isBefore(loanCharge.getDueDate(), installmentForCharge.getDueDate())
+ ||
DateUtils.isEqual(loanCharge.getDueDate(), installmentForCharge.getDueDate()))))
+ .toList();
+
+ List<LoanChargeDataRangeViewV1> charges = new
ArrayList<>();
+ for (LoanCharge charge :
chargesForInstallmentsInSameRange) {
+ LoanChargeDataRangeViewV1 chargeData =
LoanChargeDataRangeViewV1.newBuilder().setId(charge.getId())
+
.setName(charge.name()).setAmount(charge.amountOutstanding())
+
.setCurrency(currencyMapper.map(data.getCurrency())).build();
+ charges.add(chargeData);
+ }
+
+
LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange delinquencyRange
= installmentDelinquencyTagData
+ .getValue().get(0).getDelinquencyRange();
+
+ DelinquencyRangeDataV1 delinquencyRangeDataV1 =
DelinquencyRangeDataV1.newBuilder().setId(delinquencyRange.getId())
+
.setClassification(delinquencyRange.getClassification()).setMinimumAgeDays(delinquencyRange.getMinimumAgeDays())
+
.setMaximumAgeDays(delinquencyRange.getMaximumAgeDays()).build();
+
+ LoanInstallmentDelinquencyBucketDataV1
installmentDelinquencyBucketDataV1 = LoanInstallmentDelinquencyBucketDataV1
+
.newBuilder().setDelinquencyRange(delinquencyRangeDataV1).setAmount(amount).setCharges(charges)
+
.setCurrency(currencyMapper.map(data.getCurrency())).build();
+
+
loanInstallmentDelinquencyData.add(installmentDelinquencyBucketDataV1);
+ }
+ }
+ }
+ return loanInstallmentDelinquencyData;
}
private BigDecimal calculateDataSummary(Loan loan, BiFunction<Loan,
LoanRepaymentScheduleInstallment, BigDecimal> mapper) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
index 860156951..819db7384 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/LoanInstallmentDelinquencyTagData.java
@@ -22,6 +22,8 @@ import java.math.BigDecimal;
public interface LoanInstallmentDelinquencyTagData {
+ Long getId();
+
InstallmentDelinquencyRange getDelinquencyRange();
BigDecimal getOutstandingAmount();
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
index 5729fdec6..24dc1721b 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanInstallmentDelinquencyTagRepository.java
@@ -38,7 +38,7 @@ public interface LoanInstallmentDelinquencyTagRepository
List<LoanInstallmentDelinquencyTag> findByLoanId(@Param("loanId") Long
loanId);
// Fetching Installment Delinquency range and outstanding amount
- @Query("select i.delinquencyRange, i.outstandingAmount from
LoanInstallmentDelinquencyTag i where i.loan.id = :loanId")
+ @Query("select i.installment.id, i.delinquencyRange, i.outstandingAmount
from LoanInstallmentDelinquencyTag i where i.loan.id = :loanId")
List<LoanInstallmentDelinquencyTagData>
findInstallmentDelinquencyTags(@Param("loanId") Long loanId);
@Modifying(flushAutomatically = true)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
index 75bcf3f7d..2e88c76ad 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
@@ -175,12 +175,13 @@ public class DelinquencyWritePlatformServiceImpl
implements DelinquencyWritePlat
final CollectionData collectionData =
loanDelinquencyData.getLoanCollectionData();
// loan installments delinquent data
final Map<Long, CollectionData> installmentsCollectionData =
loanDelinquencyData.getLoanInstallmentsCollectionData();
- // delinquency for loan
- changes = lookUpDelinquencyRange(loan, delinquencyBucket,
collectionData.getDelinquentDays());
// delinquency for installments
if (installmentsCollectionData.size() > 0) {
applyDelinquencyDetailsForLoanInstallments(loan,
delinquencyBucket, installmentsCollectionData);
}
+ // delinquency for loan
+ changes = lookUpDelinquencyRange(loan, delinquencyBucket,
collectionData.getDelinquentDays());
+
}
return new
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId())
.withEntityExternalId(loan.getExternalId()).with(changes).build();
@@ -196,13 +197,13 @@ public class DelinquencyWritePlatformServiceImpl
implements DelinquencyWritePlat
final CollectionData collectionData =
loanDelinquentData.getLoanCollectionData();
// loan installments delinquent data
final Map<Long, CollectionData> installmentsCollectionData =
loanDelinquentData.getLoanInstallmentsCollectionData();
- log.debug("Delinquency {}", collectionData);
- // delinquency for loan
- lookUpDelinquencyRange(loan, delinquencyBucket,
collectionData.getDelinquentDays());
// delinquency for installments
if (installmentsCollectionData.size() > 0) {
applyDelinquencyDetailsForLoanInstallments(loan,
delinquencyBucket, installmentsCollectionData);
}
+ log.debug("Delinquency {}", collectionData);
+ // delinquency for loan
+ lookUpDelinquencyRange(loan, delinquencyBucket,
collectionData.getDelinquentDays());
}
}
@@ -231,10 +232,10 @@ public class DelinquencyWritePlatformServiceImpl
implements DelinquencyWritePlat
@Override
public void removeDelinquencyTagToLoan(final Loan loan) {
- setLoanDelinquencyTag(loan, null);
if (loan.isEnableInstallmentLevelDelinquency()) {
cleanLoanInstallmentsDelinquencyTags(loan);
}
+ setLoanDelinquencyTag(loan, null);
}
@Override
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
index 807c7cbbf..0002caec3 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
@@ -38,6 +38,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
+import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -59,6 +60,7 @@ import
org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import
org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
import
org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
@@ -75,9 +77,12 @@ import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
import org.springframework.test.util.ReflectionTestUtils;
@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
public class LoanAccountDelinquencyRangeEventSerializerTest {
@Mock
@@ -101,7 +106,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest
{
}
@Test
- public void testLoanRepaymentEventPayloadSerialization() throws
IOException {
+ public void testLoanDelinquencyRangeEventPayloadSerialization() throws
IOException {
// given
LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new
LoanDelinquencyRangeChangeBusinessEventSerializer(
loanReadPlatformService, new
LoanDelinquencyRangeDataMapperImpl(), loanChargeReadPlatformService,
@@ -123,6 +128,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest
{
when(loanAccountData.getCurrency()).thenAnswer(a -> new
CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(),
loanCurrency.getCurrencyInMultiplesOf()));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
+
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(false);
when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
when(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
when(delinquencyReadPlatformService.calculateLoanCollectionData(any(Long.class))).thenReturn(delinquentData);
@@ -163,6 +169,141 @@ public class
LoanAccountDelinquencyRangeEventSerializerTest {
moneyHelper.close();
}
+ @Test
+ public void
testLoanDelinquencyRangeEventPayloadSerializationWithInstallmentDelinquencyData()
throws IOException {
+ // given
+ LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new
LoanDelinquencyRangeChangeBusinessEventSerializer(
+ loanReadPlatformService, new
LoanDelinquencyRangeDataMapperImpl(), loanChargeReadPlatformService,
+ delinquencyReadPlatformService, new
LoanChargeDataMapperImpl(null, null, null), new CurrencyDataMapperImpl(),
mapper);
+
+ Loan loanForProcessing = Mockito.mock(Loan.class);
+ LoanAccountData loanAccountData = mock(LoanAccountData.class);
+ CollectionData delinquentData = mock(CollectionData.class);
+ MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1);
+ MockedStatic<MoneyHelper> moneyHelper =
Mockito.mockStatic(MoneyHelper.class);
+ String delinquentDateAsStr = "2022-12-01";
+ LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr);
+ when(loanForProcessing.getId()).thenReturn(1L);
+ when(loanAccountData.getId()).thenReturn(1L);
+ when(loanAccountData.getAccountNo()).thenReturn("0001");
+
when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
+ when(loanAccountData.getDelinquencyRange()).thenReturn(new
DelinquencyRangeData(1L, "classification", 1, 10));
+ when(loanAccountData.getCurrency()).thenAnswer(a -> new
CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(),
+ loanCurrency.getCurrencyInMultiplesOf()));
+ when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
+
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(true);
+ when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
+
when(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
+
when(delinquencyReadPlatformService.calculateLoanCollectionData(any(Long.class))).thenReturn(delinquentData);
+
when(mapper.mapLocalDate(delinquentDate)).thenReturn(delinquentDateAsStr);
+
+ LoanDelinquencyRangeChangeBusinessEvent event = new
LoanDelinquencyRangeChangeBusinessEvent(loanForProcessing);
+
+ List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments =
new ArrayList<>();
+ LoanRepaymentScheduleInstallment repaymentScheduleInstallment_1 =
buildInstallment(loanForProcessing, loanCurrency,
+ BigDecimal.valueOf(100), BigDecimal.valueOf(10),
BigDecimal.valueOf(20), BigDecimal.valueOf(20), BigDecimal.valueOf(150),
+ new BigDecimal("10"), new BigDecimal("20"));
+ when(repaymentScheduleInstallment_1.getId()).thenReturn(1L);
+
when(repaymentScheduleInstallment_1.getFromDate()).thenReturn(LocalDate.of(2022,
6, 20));
+
when(repaymentScheduleInstallment_1.getDueDate()).thenReturn(LocalDate.of(2022,
6, 30));
+ repaymentScheduleInstallments.add(repaymentScheduleInstallment_1);
+
+ LoanRepaymentScheduleInstallment repaymentScheduleInstallment_2 =
buildInstallment(loanForProcessing, loanCurrency,
+ BigDecimal.valueOf(100), BigDecimal.valueOf(0),
BigDecimal.valueOf(20), BigDecimal.valueOf(0), BigDecimal.valueOf(120));
+ when(repaymentScheduleInstallment_2.getId()).thenReturn(2L);
+
when(repaymentScheduleInstallment_2.getFromDate()).thenReturn(LocalDate.of(2022,
7, 1));
+
when(repaymentScheduleInstallment_2.getDueDate()).thenReturn(LocalDate.of(2022,
7, 10));
+ repaymentScheduleInstallments.add(repaymentScheduleInstallment_2);
+
+ LoanRepaymentScheduleInstallment repaymentScheduleInstallment_3 =
buildInstallment(loanForProcessing, loanCurrency,
+ BigDecimal.valueOf(100), BigDecimal.valueOf(0),
BigDecimal.valueOf(20), BigDecimal.valueOf(0), BigDecimal.valueOf(120));
+ when(repaymentScheduleInstallment_3.getId()).thenReturn(3L);
+
when(repaymentScheduleInstallment_3.getFromDate()).thenReturn(LocalDate.of(2022,
7, 11));
+
when(repaymentScheduleInstallment_3.getDueDate()).thenReturn(LocalDate.of(2022,
7, 20));
+ repaymentScheduleInstallments.add(repaymentScheduleInstallment_3);
+
+
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+
when(loanChargeReadPlatformService.retrieveLoanCharges(anyLong())).thenAnswer(a
-> repaymentScheduleInstallments.get(0)
+ .getInstallmentCharges().stream().map(c ->
c.getLoanCharge().toData()).collect(Collectors.toList()));
+
+ List<LoanInstallmentDelinquencyTagData> installmentDelinquencyTags =
new ArrayList<>();
+ installmentDelinquencyTags.add(buildInstallmentDelinquencyTag(1L, 1L));
+ installmentDelinquencyTags.add(buildInstallmentDelinquencyTag(2L, 1L));
+ installmentDelinquencyTags.add(buildInstallmentDelinquencyTag(3L, 2L));
+
+
when(delinquencyReadPlatformService.retrieveLoanInstallmentsCurrentDelinquencyTag(anyLong()))
+ .thenReturn(installmentDelinquencyTags);
+
+ when(loanForProcessing.getLoanCharges()).thenAnswer(a ->
repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
+ .map(c -> c.getLoanCharge()).collect(Collectors.toList()));
+
+ moneyHelper.when(() ->
MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP);
+
+ // when
+ LoanAccountDelinquencyRangeDataV1 data =
(LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
+
+ // then
+ assertEquals(1L, data.getLoanId());
+ assertEquals("0001", data.getLoanAccountNo());
+ assertEquals("externalId", data.getLoanExternalId());
+ assertEquals(1L, data.getDelinquencyRange().getId());
+ assertEquals("classification",
data.getDelinquencyRange().getClassification());
+ assertEquals(1, data.getDelinquencyRange().getMinimumAgeDays());
+ assertEquals(10, data.getDelinquencyRange().getMaximumAgeDays());
+ assertEquals(2, data.getCharges().size());
+ assertTrue(data.getCharges().stream().anyMatch(a ->
a.getAmount().compareTo(new BigDecimal("10")) == 0));
+ assertTrue(data.getCharges().stream().anyMatch(a ->
a.getAmount().compareTo(new BigDecimal("20")) == 0));
+ assertEquals(0, data.getAmount().getTotalAmount().compareTo(new
BigDecimal("390.0")));
+ assertEquals(0, data.getAmount().getPrincipalAmount().compareTo(new
BigDecimal("300.0")));
+ assertEquals(0, data.getAmount().getInterestAmount().compareTo(new
BigDecimal("60.0")));
+ assertEquals(0, data.getAmount().getFeeAmount().compareTo(new
BigDecimal("10.0")));
+ assertEquals(0, data.getAmount().getPenaltyAmount().compareTo(new
BigDecimal("20.0")));
+ assertEquals(delinquentDateAsStr, data.getDelinquentDate());
+
+ // check installment delinquency data
+ assertEquals(2, data.getInstallmentDelinquencyBuckets().size());
+
+ // check calculations
+
+ LoanInstallmentDelinquencyBucketDataV1
installmentDelinquencyBucketDataV1_1 =
data.getInstallmentDelinquencyBuckets().get(0);
+ assertEquals(1L,
installmentDelinquencyBucketDataV1_1.getDelinquencyRange().getId());
+ assertEquals("range_1",
installmentDelinquencyBucketDataV1_1.getDelinquencyRange().getClassification());
+ assertEquals(0,
installmentDelinquencyBucketDataV1_1.getAmount().getTotalAmount().compareTo(new
BigDecimal("270.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_1.getAmount().getPrincipalAmount().compareTo(new
BigDecimal("200.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_1.getAmount().getInterestAmount().compareTo(new
BigDecimal("40.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_1.getAmount().getFeeAmount().compareTo(new
BigDecimal("10.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_1.getAmount().getPenaltyAmount().compareTo(new
BigDecimal("20.0")));
+ assertEquals(2,
installmentDelinquencyBucketDataV1_1.getCharges().size());
+ assertTrue(installmentDelinquencyBucketDataV1_1.getCharges().stream()
+ .anyMatch(a -> a.getAmount().compareTo(new BigDecimal("10"))
== 0));
+ assertTrue(installmentDelinquencyBucketDataV1_1.getCharges().stream()
+ .anyMatch(a -> a.getAmount().compareTo(new BigDecimal("20"))
== 0));
+
+ LoanInstallmentDelinquencyBucketDataV1
installmentDelinquencyBucketDataV1_2 =
data.getInstallmentDelinquencyBuckets().get(1);
+ assertEquals(2L,
installmentDelinquencyBucketDataV1_2.getDelinquencyRange().getId());
+ assertEquals("range_2",
installmentDelinquencyBucketDataV1_2.getDelinquencyRange().getClassification());
+ assertEquals(0,
installmentDelinquencyBucketDataV1_2.getAmount().getTotalAmount().compareTo(new
BigDecimal("120.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_2.getAmount().getPrincipalAmount().compareTo(new
BigDecimal("100.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_2.getAmount().getInterestAmount().compareTo(new
BigDecimal("20.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_2.getAmount().getFeeAmount().compareTo(new
BigDecimal("0.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_2.getAmount().getPenaltyAmount().compareTo(new
BigDecimal("0.0")));
+ assertEquals(0,
installmentDelinquencyBucketDataV1_2.getCharges().size());
+ moneyHelper.close();
+ }
+
+ private LoanInstallmentDelinquencyTagData
buildInstallmentDelinquencyTag(long installmentId, long rangeId) {
+ LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange
delinquencyRange = mock(
+
LoanInstallmentDelinquencyTagData.InstallmentDelinquencyRange.class);
+ when(delinquencyRange.getId()).thenReturn(rangeId);
+ when(delinquencyRange.getClassification()).thenReturn("range_" +
rangeId);
+ when(delinquencyRange.getMaximumAgeDays()).thenReturn(1);
+ when(delinquencyRange.getMinimumAgeDays()).thenReturn(2);
+ LoanInstallmentDelinquencyTagData installmentDelinquencyTagData =
mock(LoanInstallmentDelinquencyTagData.class);
+ when(installmentDelinquencyTagData.getId()).thenReturn(installmentId);
+
when(installmentDelinquencyTagData.getDelinquencyRange()).thenReturn(delinquencyRange);
+ return installmentDelinquencyTagData;
+ }
+
private LoanRepaymentScheduleInstallment buildInstallment(Loan loan,
MonetaryCurrency currency, BigDecimal principalAmount,
BigDecimal freeAmount, BigDecimal interestAmount, BigDecimal
penaltyAmount, BigDecimal totalAmount, BigDecimal... charges) {
@@ -173,6 +314,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest
{
when(installment.getFeeChargesOutstanding(any())).thenAnswer(a ->
Money.of(currency, freeAmount));
when(installment.getTotalOutstanding(any())).thenAnswer(a ->
Money.of(currency, totalAmount));
Charge charge = mock(Charge.class);
+ when(charge.getName()).thenReturn("charge");
when(charge.toData()).thenAnswer(a -> {
ChargeData chargeData = mock(ChargeData.class);
when(chargeData.getCurrency()).thenAnswer(b -> new
CurrencyData(currency.getCode()));
@@ -193,7 +335,10 @@ public class
LoanAccountDelinquencyRangeEventSerializerTest {
}
private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge
charge) {
- return new LoanCharge(loan, charge, amount, amount,
ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT,
+ LoanCharge loanCharge = new LoanCharge(loan, charge, amount, amount,
ChargeTimeType.SPECIFIED_DUE_DATE, ChargeCalculationType.FLAT,
LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new
BigDecimal(100), ExternalId.generate());
+ ReflectionTestUtils.setField(loanCharge, "id", 1L);
+ return loanCharge;
}
+
}