Skip to content

Commit

Permalink
FINERACT-1992: Multi delinquency implication on events
Browse files Browse the repository at this point in the history
  • Loading branch information
ruchiD committed Nov 22, 2023
1 parent 677a499 commit 3477eb3
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
"null",
"string"
]
},
{
"name": "installmentDelinquencyBuckets",
"type": {
"type": "array",
"items": "org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@
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;
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;
Expand All @@ -36,10 +42,12 @@
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;
Expand Down Expand Up @@ -94,6 +102,10 @@ public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
.build();

DelinquencyRangeDataV1 delinquencyRange = mapper.map(data.getDelinquencyRange());

List<LoanInstallmentDelinquencyBucketDataV1> installmentsDelinquencyData = calculateInstallmentLevelDelinquencyData(event.get(),
data);

LoanAccountDelinquencyRangeDataV1.Builder builder = LoanAccountDelinquencyRangeDataV1.newBuilder();
return builder//
.setLoanId(id)//
Expand All @@ -104,7 +116,79 @@ public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
.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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

public interface LoanInstallmentDelinquencyTagData {

Long getId();

InstallmentDelinquencyRange getDelinquencyRange();

BigDecimal getOutstandingAmount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,13 @@ public CommandProcessingResult applyDelinquencyTagToLoan(Long loanId, JsonComman
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();
Expand All @@ -196,13 +197,13 @@ public void applyDelinquencyTagToLoan(LoanScheduleDelinquencyData loanDelinquenc
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());
}
}

Expand Down Expand Up @@ -231,10 +232,10 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand

@Override
public void removeDelinquencyTagToLoan(final Loan loan) {
setLoanDelinquencyTag(loan, null);
if (loan.isEnableInstallmentLevelDelinquency()) {
cleanLoanInstallmentsDelinquencyTags(loan);
}
setLoanDelinquencyTag(loan, null);
}

@Override
Expand Down
Loading

0 comments on commit 3477eb3

Please sign in to comment.