FINERACT-2081: Loan account data additional fields for summary and delinquency

This commit is contained in:
Jose Alberto Hernandez
2024-07-11 17:41:57 +02:00
committed by Adam Saghy
parent 4ac102f4e0
commit 79f32d32a3
11 changed files with 371 additions and 34 deletions
@@ -64,6 +64,11 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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()) {
@@ -71,6 +76,10 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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);
@@ -85,6 +94,10 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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;
@@ -110,6 +123,11 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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) {
@@ -248,8 +266,17 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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());
@@ -272,6 +299,10 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
}
collectionData.setDelinquentDate(overdueSinceDate);
collectionData.setDelinquentAmount(outstandingAmount);
collectionData.setDelinquentPrincipal(delinquentPrincipal);
collectionData.setDelinquentInterest(delinquentInterest);
collectionData.setDelinquentFee(delinquentFee);
collectionData.setDelinquentPenalty(delinquentPenalty);
return collectionData;
}
@@ -283,6 +314,10 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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();
@@ -306,6 +341,10 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
}
collectionData.setDelinquentDate(overdueSinceDate);
collectionData.setDelinquentAmount(outstandingAmount);
collectionData.setDelinquentPrincipal(delinquentPrincipal);
collectionData.setDelinquentInterest(delinquentInterest);
collectionData.setDelinquentFee(delinquentFee);
collectionData.setDelinquentPenalty(delinquentPenalty);
return collectionData;
}
@@ -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);
}
}
@@ -24,8 +24,12 @@ import java.util.Collection;
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;
/**
@@ -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;
@@ -110,6 +118,9 @@ public class LoanSummaryData {
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)) {
@@ -131,6 +142,24 @@ public class LoanSummaryData {
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)
@@ -163,7 +192,9 @@ public class LoanSummaryData {
.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) {
@@ -191,4 +222,45 @@ public class LoanSummaryData {
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;
}
}
@@ -72,6 +72,7 @@ public final class LoanSchedulePeriodData {
private final BigDecimal totalActualCostOfLoanForPeriod;
private final BigDecimal totalInstallmentAmountForPeriod;
private final BigDecimal totalCredits;
private final BigDecimal totalAccruedInterest;
private final Boolean downPaymentPeriod;
public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed,
@@ -109,7 +110,8 @@ public final class LoanSchedulePeriodData {
final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod,
final BigDecimal totalPaidLateForPeriod, final BigDecimal totalWaived, final BigDecimal totalWrittenOff,
final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod,
final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits, final boolean isDownPayment) {
final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits, final boolean isDownPayment,
final BigDecimal totalAccruedInterest) {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue,
principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
@@ -117,7 +119,7 @@ public final class LoanSchedulePeriodData {
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid,
penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid,
totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding,
totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits, isDownPayment);
totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits, isDownPayment, totalAccruedInterest);
}
public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData, final boolean complete,
@@ -138,7 +140,8 @@ public final class LoanSchedulePeriodData {
loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod,
loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod,
loanSchedulePeriodData.totalActualCostOfLoanForPeriod, loanSchedulePeriodData.totalInstallmentAmountForPeriod,
loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod());
loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod(),
loanSchedulePeriodData.totalAccruedInterest);
}
/*
@@ -202,6 +205,7 @@ public final class LoanSchedulePeriodData {
this.totalInstallmentAmountForPeriod = null;
this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null;
this.totalCredits = BigDecimal.ZERO;
this.totalAccruedInterest = BigDecimal.ZERO;
this.downPaymentPeriod = false;
}
@@ -261,6 +265,7 @@ public final class LoanSchedulePeriodData {
this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod;
this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null;
this.totalCredits = BigDecimal.ZERO;
this.totalAccruedInterest = BigDecimal.ZERO;
this.downPaymentPeriod = false;
}
@@ -316,6 +321,7 @@ public final class LoanSchedulePeriodData {
this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null;
this.totalCredits = BigDecimal.ZERO;
this.downPaymentPeriod = true;
this.totalAccruedInterest = BigDecimal.ZERO;
}
/*
@@ -334,7 +340,7 @@ public final class LoanSchedulePeriodData {
final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod,
final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding,
final BigDecimal totalActualCostOfLoanForPeriod, final BigDecimal totalInstallmentAmountForPeriod,
final BigDecimal totalCredits, final boolean isDownPayment) {
final BigDecimal totalCredits, final boolean isDownPayment, final BigDecimal totalAccruedInterest) {
this.period = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
@@ -385,6 +391,7 @@ public final class LoanSchedulePeriodData {
this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null;
this.totalCredits = totalCredits;
this.downPaymentPeriod = isDownPayment;
this.totalAccruedInterest = totalAccruedInterest;
}
private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) {
@@ -482,4 +489,5 @@ public final class LoanSchedulePeriodData {
public BigDecimal totalOutstandingForPeriod() {
return defaultToZeroIfNull(this.totalOutstandingForPeriod);
}
}