FINERACT-1992: Multi delinquency implication on events
This commit is contained in:
committed by
Adam Saghy
parent
f089f01116
commit
48300cd19d
@@ -50,6 +50,13 @@
|
|||||||
"null",
|
"null",
|
||||||
"string"
|
"string"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "installmentDelinquencyBuckets",
|
||||||
|
"type": {
|
||||||
|
"type": "array",
|
||||||
|
"items": "org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "LoanInstallmentDelinquencyBucketDataV1",
|
||||||
|
"namespace": "org.apache.fineract.avro.loan.v1",
|
||||||
|
"type": "record",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": null,
|
||||||
|
"name": "delinquencyRange",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"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": {
|
||||||
|
"type": "array",
|
||||||
|
"items": "org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "currency",
|
||||||
|
"type": "org.apache.fineract.avro.generic.v1.CurrencyDataV1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+85
-1
@@ -19,8 +19,12 @@
|
|||||||
package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
|
package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.avro.generic.GenericContainer;
|
import org.apache.avro.generic.GenericContainer;
|
||||||
import org.apache.fineract.avro.generator.ByteBufferSerializable;
|
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.LoanAccountDelinquencyRangeDataV1;
|
||||||
import org.apache.fineract.avro.loan.v1.LoanAmountDataV1;
|
import org.apache.fineract.avro.loan.v1.LoanAmountDataV1;
|
||||||
import org.apache.fineract.avro.loan.v1.LoanChargeDataRangeViewV1;
|
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.BusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.generic.CurrencyDataMapper;
|
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.mapper.support.AvroDateTimeMapper;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
|
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
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.delinquency.service.DelinquencyReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
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.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
@@ -94,6 +102,10 @@ public class LoanDelinquencyRangeChangeBusinessEventSerializer implements Busine
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
DelinquencyRangeDataV1 delinquencyRange = mapper.map(data.getDelinquencyRange());
|
DelinquencyRangeDataV1 delinquencyRange = mapper.map(data.getDelinquencyRange());
|
||||||
|
|
||||||
|
List<LoanInstallmentDelinquencyBucketDataV1> installmentsDelinquencyData = calculateInstallmentLevelDelinquencyData(event.get(),
|
||||||
|
data);
|
||||||
|
|
||||||
LoanAccountDelinquencyRangeDataV1.Builder builder = LoanAccountDelinquencyRangeDataV1.newBuilder();
|
LoanAccountDelinquencyRangeDataV1.Builder builder = LoanAccountDelinquencyRangeDataV1.newBuilder();
|
||||||
return builder//
|
return builder//
|
||||||
.setLoanId(id)//
|
.setLoanId(id)//
|
||||||
@@ -104,7 +116,79 @@ public class LoanDelinquencyRangeChangeBusinessEventSerializer implements Busine
|
|||||||
.setAmount(amount)//
|
.setAmount(amount)//
|
||||||
.setCurrency(currencyMapper.map(data.getCurrency()))//
|
.setCurrency(currencyMapper.map(data.getCurrency()))//
|
||||||
.setDelinquentDate(delinquentDate)//
|
.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) {
|
private BigDecimal calculateDataSummary(Loan loan, BiFunction<Loan, LoanRepaymentScheduleInstallment, BigDecimal> mapper) {
|
||||||
|
|||||||
+2
@@ -22,6 +22,8 @@ import java.math.BigDecimal;
|
|||||||
|
|
||||||
public interface LoanInstallmentDelinquencyTagData {
|
public interface LoanInstallmentDelinquencyTagData {
|
||||||
|
|
||||||
|
Long getId();
|
||||||
|
|
||||||
InstallmentDelinquencyRange getDelinquencyRange();
|
InstallmentDelinquencyRange getDelinquencyRange();
|
||||||
|
|
||||||
BigDecimal getOutstandingAmount();
|
BigDecimal getOutstandingAmount();
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ public interface LoanInstallmentDelinquencyTagRepository
|
|||||||
List<LoanInstallmentDelinquencyTag> findByLoanId(@Param("loanId") Long loanId);
|
List<LoanInstallmentDelinquencyTag> findByLoanId(@Param("loanId") Long loanId);
|
||||||
|
|
||||||
// Fetching Installment Delinquency range and outstanding amount
|
// 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);
|
List<LoanInstallmentDelinquencyTagData> findInstallmentDelinquencyTags(@Param("loanId") Long loanId);
|
||||||
|
|
||||||
@Modifying(flushAutomatically = true)
|
@Modifying(flushAutomatically = true)
|
||||||
|
|||||||
+7
-6
@@ -175,12 +175,13 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
final CollectionData collectionData = loanDelinquencyData.getLoanCollectionData();
|
final CollectionData collectionData = loanDelinquencyData.getLoanCollectionData();
|
||||||
// loan installments delinquent data
|
// loan installments delinquent data
|
||||||
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquencyData.getLoanInstallmentsCollectionData();
|
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquencyData.getLoanInstallmentsCollectionData();
|
||||||
// delinquency for loan
|
|
||||||
changes = lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays());
|
|
||||||
// delinquency for installments
|
// delinquency for installments
|
||||||
if (installmentsCollectionData.size() > 0) {
|
if (installmentsCollectionData.size() > 0) {
|
||||||
applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData);
|
applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData);
|
||||||
}
|
}
|
||||||
|
// delinquency for loan
|
||||||
|
changes = lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays());
|
||||||
|
|
||||||
}
|
}
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId())
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId())
|
||||||
.withEntityExternalId(loan.getExternalId()).with(changes).build();
|
.withEntityExternalId(loan.getExternalId()).with(changes).build();
|
||||||
@@ -196,13 +197,13 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
|||||||
final CollectionData collectionData = loanDelinquentData.getLoanCollectionData();
|
final CollectionData collectionData = loanDelinquentData.getLoanCollectionData();
|
||||||
// loan installments delinquent data
|
// loan installments delinquent data
|
||||||
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquentData.getLoanInstallmentsCollectionData();
|
final Map<Long, CollectionData> installmentsCollectionData = loanDelinquentData.getLoanInstallmentsCollectionData();
|
||||||
log.debug("Delinquency {}", collectionData);
|
|
||||||
// delinquency for loan
|
|
||||||
lookUpDelinquencyRange(loan, delinquencyBucket, collectionData.getDelinquentDays());
|
|
||||||
// delinquency for installments
|
// delinquency for installments
|
||||||
if (installmentsCollectionData.size() > 0) {
|
if (installmentsCollectionData.size() > 0) {
|
||||||
applyDelinquencyDetailsForLoanInstallments(loan, delinquencyBucket, installmentsCollectionData);
|
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
|
@Override
|
||||||
public void removeDelinquencyTagToLoan(final Loan loan) {
|
public void removeDelinquencyTagToLoan(final Loan loan) {
|
||||||
setLoanDelinquencyTag(loan, null);
|
|
||||||
if (loan.isEnableInstallmentLevelDelinquency()) {
|
if (loan.isEnableInstallmentLevelDelinquency()) {
|
||||||
cleanLoanInstallmentsDelinquencyTags(loan);
|
cleanLoanInstallmentsDelinquencyTags(loan);
|
||||||
}
|
}
|
||||||
|
setLoanDelinquencyTag(loan, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+147
-2
@@ -38,6 +38,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
|
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.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
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.ChargePaymentMode;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
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.delinquency.service.DelinquencyReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||||
@@ -75,9 +77,12 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
|
import org.mockito.quality.Strictness;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
public class LoanAccountDelinquencyRangeEventSerializerTest {
|
public class LoanAccountDelinquencyRangeEventSerializerTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@@ -101,7 +106,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoanRepaymentEventPayloadSerialization() throws IOException {
|
public void testLoanDelinquencyRangeEventPayloadSerialization() throws IOException {
|
||||||
// given
|
// given
|
||||||
LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new LoanDelinquencyRangeChangeBusinessEventSerializer(
|
LoanDelinquencyRangeChangeBusinessEventSerializer serializer = new LoanDelinquencyRangeChangeBusinessEventSerializer(
|
||||||
loanReadPlatformService, new LoanDelinquencyRangeDataMapperImpl(), loanChargeReadPlatformService,
|
loanReadPlatformService, new LoanDelinquencyRangeDataMapperImpl(), loanChargeReadPlatformService,
|
||||||
@@ -123,6 +128,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
when(loanAccountData.getCurrency()).thenAnswer(a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(),
|
when(loanAccountData.getCurrency()).thenAnswer(a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(),
|
||||||
loanCurrency.getCurrencyInMultiplesOf()));
|
loanCurrency.getCurrencyInMultiplesOf()));
|
||||||
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
|
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
|
||||||
|
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(false);
|
||||||
when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
|
when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
|
||||||
when(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
|
when(loanReadPlatformService.retrieveOne(any(Long.class))).thenReturn(loanAccountData);
|
||||||
when(delinquencyReadPlatformService.calculateLoanCollectionData(any(Long.class))).thenReturn(delinquentData);
|
when(delinquencyReadPlatformService.calculateLoanCollectionData(any(Long.class))).thenReturn(delinquentData);
|
||||||
@@ -163,6 +169,141 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
moneyHelper.close();
|
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,
|
private LoanRepaymentScheduleInstallment buildInstallment(Loan loan, MonetaryCurrency currency, BigDecimal principalAmount,
|
||||||
BigDecimal freeAmount, BigDecimal interestAmount, BigDecimal penaltyAmount, BigDecimal totalAmount, BigDecimal... charges) {
|
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.getFeeChargesOutstanding(any())).thenAnswer(a -> Money.of(currency, freeAmount));
|
||||||
when(installment.getTotalOutstanding(any())).thenAnswer(a -> Money.of(currency, totalAmount));
|
when(installment.getTotalOutstanding(any())).thenAnswer(a -> Money.of(currency, totalAmount));
|
||||||
Charge charge = mock(Charge.class);
|
Charge charge = mock(Charge.class);
|
||||||
|
when(charge.getName()).thenReturn("charge");
|
||||||
when(charge.toData()).thenAnswer(a -> {
|
when(charge.toData()).thenAnswer(a -> {
|
||||||
ChargeData chargeData = mock(ChargeData.class);
|
ChargeData chargeData = mock(ChargeData.class);
|
||||||
when(chargeData.getCurrency()).thenAnswer(b -> new CurrencyData(currency.getCode()));
|
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) {
|
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());
|
LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100), ExternalId.generate());
|
||||||
|
ReflectionTestUtils.setField(loanCharge, "id", 1L);
|
||||||
|
return loanCharge;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user