Skip to content

Commit

Permalink
FINERACT-2081: Loan account data additional fields for summary and de…
Browse files Browse the repository at this point in the history
…linquency
  • Loading branch information
Jose Alberto Hernandez authored and adamsaghy committed Jul 11, 2024
1 parent 4ac102f commit 79f32d3
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,22 @@ public CollectionData getOverdueCollectionData(final Loan loan, List<LoanDelinqu
return CollectionData.template();
}

BigDecimal delinquentPrincipal = BigDecimal.ZERO;
BigDecimal delinquentInterest = BigDecimal.ZERO;
BigDecimal delinquentFee = BigDecimal.ZERO;
BigDecimal delinquentPenalty = BigDecimal.ZERO;

// Get the oldest overdue installment if exists one
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
if (!installment.isObligationsMet()) {
if (DateUtils.isBefore(installment.getDueDate(), businessDate)) {
log.debug("Loan Id: {} with installment {} due date {}", loan.getId(), installment.getInstallmentNumber(),
installment.getDueDate());
outstandingAmount = outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount());
delinquentPrincipal = delinquentPrincipal.add(installment.getPrincipalOutstanding(loanCurrency).getAmount());
delinquentInterest = delinquentInterest.add(installment.getInterestOutstanding(loanCurrency).getAmount());
delinquentFee = delinquentFee.add(installment.getFeeChargesOutstanding(loanCurrency).getAmount());
delinquentPenalty = delinquentPenalty.add(installment.getPenaltyChargesOutstanding(loanCurrency).getAmount());
if (!oldestOverdueInstallment) {
log.debug("Oldest installment {} {}", installment.getInstallmentNumber(), installment.getDueDate());
CollectionData overDueInstallmentDelinquentData = calculateDelinquencyDataForOverdueInstallment(loan, installment);
Expand All @@ -85,6 +94,10 @@ public CollectionData getOverdueCollectionData(final Loan loan, List<LoanDelinqu
CollectionData nonOverDueInstallmentDelinquentData = calculateDelinquencyDataForNonOverdueInstallment(loan,
installment);
outstandingAmount = outstandingAmount.add(nonOverDueInstallmentDelinquentData.getDelinquentAmount());
delinquentPrincipal = delinquentPrincipal.add(nonOverDueInstallmentDelinquentData.getDelinquentPrincipal());
delinquentInterest = delinquentInterest.add(nonOverDueInstallmentDelinquentData.getDelinquentInterest());
delinquentFee = delinquentFee.add(nonOverDueInstallmentDelinquentData.getDelinquentFee());
delinquentPenalty = delinquentPenalty.add(nonOverDueInstallmentDelinquentData.getDelinquentPenalty());
if (!overdueSinceDateWasSet) {
overdueSinceDate = nonOverDueInstallmentDelinquentData.getDelinquentDate();
overdueSinceDateWasSet = true;
Expand All @@ -110,6 +123,11 @@ public CollectionData getOverdueCollectionData(final Loan loan, List<LoanDelinqu
collectionData.setDelinquentDate(overdueSinceDate);
}
collectionData.setDelinquentAmount(outstandingAmount);
collectionData.setDelinquentPrincipal(delinquentPrincipal);
collectionData.setDelinquentInterest(delinquentInterest);
collectionData.setDelinquentFee(delinquentFee);
collectionData.setDelinquentPenalty(delinquentPenalty);

collectionData.setDelinquentDays(0L);
Long delinquentDays = overdueDays - graceDays;
if (delinquentDays > 0) {
Expand Down Expand Up @@ -248,8 +266,17 @@ private CollectionData calculateDelinquencyDataForOverdueInstallment(final Loan
LocalDate overdueSinceDate = null;
CollectionData collectionData = CollectionData.template();
BigDecimal outstandingAmount = BigDecimal.ZERO;
BigDecimal delinquentPrincipal = BigDecimal.ZERO;
BigDecimal delinquentInterest = BigDecimal.ZERO;
BigDecimal delinquentFee = BigDecimal.ZERO;
BigDecimal delinquentPenalty = BigDecimal.ZERO;

outstandingAmount = outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount());
delinquentPrincipal = delinquentPrincipal.add(installment.getPrincipalOutstanding(loanCurrency).getAmount());
delinquentInterest = delinquentInterest.add(installment.getInterestOutstanding(loanCurrency).getAmount());
delinquentFee = delinquentFee.add(installment.getFeeChargesOutstanding(loanCurrency).getAmount());
delinquentPenalty = delinquentPenalty.add(installment.getPenaltyChargesOutstanding(loanCurrency).getAmount());

overdueSinceDate = installment.getDueDate();
BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount();
boolean isLatestInstallment = Objects.equals(installment.getId(), latestInstallment.getId());
Expand All @@ -272,6 +299,10 @@ private CollectionData calculateDelinquencyDataForOverdueInstallment(final Loan
}
collectionData.setDelinquentDate(overdueSinceDate);
collectionData.setDelinquentAmount(outstandingAmount);
collectionData.setDelinquentPrincipal(delinquentPrincipal);
collectionData.setDelinquentInterest(delinquentInterest);
collectionData.setDelinquentFee(delinquentFee);
collectionData.setDelinquentPenalty(delinquentPenalty);
return collectionData;
}

Expand All @@ -283,6 +314,10 @@ private CollectionData calculateDelinquencyDataForNonOverdueInstallment(final Lo
LocalDate overdueSinceDate = null;
CollectionData collectionData = CollectionData.template();
BigDecimal outstandingAmount = BigDecimal.ZERO;
BigDecimal delinquentPrincipal = BigDecimal.ZERO;
BigDecimal delinquentInterest = BigDecimal.ZERO;
BigDecimal delinquentFee = BigDecimal.ZERO;
BigDecimal delinquentPenalty = BigDecimal.ZERO;

List<LoanTransaction> chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback);
BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount();
Expand All @@ -306,6 +341,10 @@ private CollectionData calculateDelinquencyDataForNonOverdueInstallment(final Lo
}
collectionData.setDelinquentDate(overdueSinceDate);
collectionData.setDelinquentAmount(outstandingAmount);
collectionData.setDelinquentPrincipal(delinquentPrincipal);
collectionData.setDelinquentInterest(delinquentInterest);
collectionData.setDelinquentFee(delinquentFee);
collectionData.setDelinquentPenalty(delinquentPenalty);
return collectionData;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,14 @@ public final class CollectionData {
public Collection<DelinquencyPausePeriod> delinquencyPausePeriods;
public Collection<InstallmentLevelDelinquency> installmentLevelDelinquency;

private BigDecimal delinquentPrincipal;
private BigDecimal delinquentInterest;
private BigDecimal delinquentFee;
private BigDecimal delinquentPenalty;

public static CollectionData template() {
final BigDecimal zero = BigDecimal.ZERO;
return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero, null, null);
return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero, null, null, zero, zero, zero, zero);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.springframework.util.CollectionUtils;

/**
Expand Down Expand Up @@ -91,11 +95,15 @@ public class LoanSummaryData {
private BigDecimal totalCreditBalanceRefundReversed;
private BigDecimal totalRepaymentTransaction;
private BigDecimal totalRepaymentTransactionReversed;
private BigDecimal totalInterestPaymentWaiver;
private final Long chargeOffReasonId;
private final String chargeOffReason;

private BigDecimal totalUnpaidAccruedDueInterest;
private BigDecimal totalUnpaidAccruedNotDueInterest;

public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
final Collection<LoanTransactionData> loanTransactions) {
final Collection<LoanTransactionData> loanTransactions, final LoanScheduleData repaymentSchedule) {

BigDecimal totalMerchantRefund = BigDecimal.ZERO;
BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
Expand All @@ -110,6 +118,9 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat
BigDecimal totalCreditBalanceRefundReversed = BigDecimal.ZERO;
BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
BigDecimal totalUnpaidAccruedDueInterest = BigDecimal.ZERO;
BigDecimal totalUnpaidAccruedNotDueInterest = BigDecimal.ZERO;

if (!CollectionUtils.isEmpty(loanTransactions)) {

Expand All @@ -131,6 +142,24 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat
loanTransactions);
totalRepaymentTransaction = computeTotalRepaymentTransactionAmount(loanTransactions);
totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions);
totalInterestPaymentWaiver = computeTotalAmountForNonReversedTransactions(LoanTransactionType.INTEREST_PAYMENT_WAIVER,
loanTransactions);
}

if (repaymentSchedule != null) {
// Accrued Due Interest on Past due installments
totalUnpaidAccruedDueInterest = computeTotalAccruedDueInterestAmount(repaymentSchedule.getPeriods());
if (MathUtil.isGreaterThanZero(totalUnpaidAccruedDueInterest)) {
totalUnpaidAccruedDueInterest = totalUnpaidAccruedDueInterest
.subtract(computeTotalInterestPaidDueAmount(repaymentSchedule.getPeriods()));
}

// Accrued Due Interest on Actual Installment
totalUnpaidAccruedNotDueInterest = computeTotalAccruedNotDueInterestAmountOnActualPeriod(repaymentSchedule.getPeriods());
if (MathUtil.isGreaterThanZero(totalUnpaidAccruedNotDueInterest)) {
totalUnpaidAccruedNotDueInterest = totalUnpaidAccruedNotDueInterest
.subtract(computeTotalInterestPaidNotDueAmountOnActualPeriod(repaymentSchedule.getPeriods()));
}
}

return LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed)
Expand Down Expand Up @@ -163,7 +192,9 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat
.totalChargeAdjustment(totalChargeAdjustment).totalChargeAdjustmentReversed(totalChargeAdjustmentReversed)
.totalChargeback(totalChargeback).totalCreditBalanceRefund(totalCreditBalanceRefund)
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).build();
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
.totalUnpaidAccruedDueInterest(totalUnpaidAccruedDueInterest)
.totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).build();
}

public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
Expand Down Expand Up @@ -191,4 +222,45 @@ private static BigDecimal computeTotalRepaymentTransactionAmount(Collection<Loan
loanTransactions);
return totalRepaymentTransaction.add(totalDownPaymentTransaction);
}

private static BigDecimal computeTotalAccruedDueInterestAmount(Collection<LoanSchedulePeriodData> periods) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.isAfter(period.getDueDate()))
.map(period -> period.getTotalAccruedInterest()).reduce(BigDecimal.ZERO, BigDecimal::add);
}

private static BigDecimal computeTotalInterestPaidDueAmount(Collection<LoanSchedulePeriodData> periods) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.isAfter(period.getDueDate()))
.map(period -> period.getInterestPaid()).reduce(BigDecimal.ZERO, BigDecimal::add);
}

private static BigDecimal computeTotalAccruedNotDueInterestAmountOnActualPeriod(Collection<LoanSchedulePeriodData> periods) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
return periods.stream()
.filter(period -> !period.getDownPaymentPeriod() && isActualPeriod(period) && businessDate.isBefore(period.getDueDate()))
.map(period -> period.getTotalAccruedInterest()).reduce(BigDecimal.ZERO, BigDecimal::add);
}

private static BigDecimal computeTotalInterestPaidNotDueAmountOnActualPeriod(Collection<LoanSchedulePeriodData> periods) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
return periods.stream()
.filter(period -> !period.getDownPaymentPeriod() && isActualPeriod(period) && businessDate.isBefore(period.getDueDate()))
.map(period -> period.getInterestPaid()).reduce(BigDecimal.ZERO, BigDecimal::add);
}

private static boolean isActualPeriod(LoanSchedulePeriodData period) {
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
boolean actualPeriod = false;
if (period.getPeriod() != null) {
if (period.getPeriod() == 1) {
actualPeriod = ((businessDate.isEqual(period.getFromDate()) || businessDate.isAfter(period.getFromDate()))
&& businessDate.isBefore(period.getDueDate()));
} else {
actualPeriod = (businessDate.isAfter(period.getFromDate()) && businessDate.isBefore(period.getDueDate()));
}
}

return actualPeriod;
}
}
Loading

0 comments on commit 79f32d3

Please sign in to comment.