From 766a0034c34d8754d7e9eb8804e876d12235365e Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Wed, 3 Jul 2024 00:40:12 -0600 Subject: [PATCH] FINERACT-2081: Loan account data additional fields for summary and delinquency --- .../LoanDelinquencyDomainServiceImpl.java | 39 ++++++++++++ .../loanaccount/data/CollectionData.java | 7 ++- .../loanaccount/data/LoanSummaryData.java | 62 ++++++++++++++++++- .../loanschedule/data/LoanScheduleData.java | 5 +- .../data/LoanSchedulePeriodData.java | 33 ++++++++-- .../domain/LoanScheduleModel.java | 3 +- .../domain/LoanRescheduleModel.java | 3 +- .../impl/ChargeOrTransaction.java | 2 +- .../loan/LoanBusinessEventSerializer.java | 3 +- .../loanaccount/api/LoansApiResource.java | 4 +- .../api/LoansApiResourceSwagger.java | 12 ++++ .../service/LoanReadPlatformServiceImpl.java | 14 +++-- ...tePlatformServiceRangeChangeEventTest.java | 42 +++++++------ 13 files changed, 190 insertions(+), 39 deletions(-) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java index d6ad0e5fc8f..c09a71479e2 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java @@ -64,6 +64,11 @@ public CollectionData getOverdueCollectionData(final Loan loan, List 0) { @@ -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()); @@ -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; } @@ -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 chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback); BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); @@ -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; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java index f872d6d27d2..fa4e4f6c8ae 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java @@ -47,9 +47,14 @@ public final class CollectionData { public Collection delinquencyPausePeriods; public Collection 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); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java index 42ebbd6bec4..6adffb592d9 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java @@ -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; /** @@ -91,11 +95,16 @@ public class LoanSummaryData { private BigDecimal totalCreditBalanceRefundReversed; private BigDecimal totalRepaymentTransaction; private BigDecimal totalRepaymentTransactionReversed; + private BigDecimal totalInterestRefund; + 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 loanTransactions) { + final Collection loanTransactions, final LoanScheduleData repaymentSchedule) { BigDecimal totalMerchantRefund = BigDecimal.ZERO; BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO; @@ -110,6 +119,10 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat BigDecimal totalCreditBalanceRefundReversed = BigDecimal.ZERO; BigDecimal totalRepaymentTransaction = BigDecimal.ZERO; BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO; + BigDecimal totalInterestRefund = BigDecimal.ZERO; + BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO; + BigDecimal totalUnpaidAccruedDueInterest = BigDecimal.ZERO; + BigDecimal totalUnpaidAccruedNotDueInterest = BigDecimal.ZERO; if (!CollectionUtils.isEmpty(loanTransactions)) { @@ -131,6 +144,22 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat loanTransactions); totalRepaymentTransaction = computeTotalRepaymentTransactionAmount(loanTransactions); totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions); + totalInterestPaymentWaiver = computeTotalAmountForNonReversedTransactions(LoanTransactionType.INTEREST_PAYMENT_WAIVER, + loanTransactions); + } + + // Past due installments + totalUnpaidAccruedDueInterest = computeTotalAccruedDueAmount(repaymentSchedule.getPeriods()); + if (MathUtil.isGreaterThanZero(totalUnpaidAccruedDueInterest)) { + totalUnpaidAccruedDueInterest = totalUnpaidAccruedDueInterest + .subtract(computeTotalInterestPaidDueAmount(repaymentSchedule.getPeriods())); + } + + // Actual Installment + totalUnpaidAccruedNotDueInterest = fetchTotalAccruedNotDueAmount(repaymentSchedule.getPeriods()); + if (MathUtil.isGreaterThanZero(totalUnpaidAccruedNotDueInterest)) { + totalUnpaidAccruedNotDueInterest = totalUnpaidAccruedNotDueInterest + .subtract(fetchTotalInterestNotDueAmount(repaymentSchedule.getPeriods())); } return LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed) @@ -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).totalInterestRefund(totalInterestRefund) + .totalInterestPaymentWaiver(totalInterestPaymentWaiver).totalUnpaidAccruedDueInterest(totalUnpaidAccruedDueInterest) + .totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).build(); } public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) { @@ -191,4 +222,31 @@ private static BigDecimal computeTotalRepaymentTransactionAmount(Collection 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 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 fetchTotalAccruedNotDueAmount(Collection periods) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + return periods.stream() + .filter(period -> !period.getDownPaymentPeriod() && period.getActualPeriod() && businessDate.isBefore(period.getDueDate())) + .map(period -> period.getTotalAccruedInterest()).reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private static BigDecimal fetchTotalInterestNotDueAmount(Collection periods) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + return periods.stream() + .filter(period -> !period.getDownPaymentPeriod() && period.getActualPeriod() && businessDate.isBefore(period.getDueDate())) + .map(period -> period.getInterestPaid()).reduce(BigDecimal.ZERO, BigDecimal::add); + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java index 4f7a4d15388..962fe75f3cf 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleData.java @@ -54,6 +54,7 @@ public class LoanScheduleData { private final BigDecimal totalPaidLate; private final BigDecimal totalOutstanding; private final BigDecimal totalCredits; + private final BigDecimal totalAccruedInterest; /** * periods is collection of data objects containing specific information to each period of the loan @@ -68,7 +69,7 @@ public LoanScheduleData(final CurrencyData currency, final Collection periods, final Integer loanTermInDays, @@ -108,6 +110,7 @@ public LoanScheduleData(final CurrencyData currency, final Collection futurePeriods) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java index fb50ee5f87c..6fb7838291b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java @@ -72,7 +72,9 @@ public final class LoanSchedulePeriodData { private final BigDecimal totalActualCostOfLoanForPeriod; private final BigDecimal totalInstallmentAmountForPeriod; private final BigDecimal totalCredits; + private final BigDecimal totalAccruedInterest; private final Boolean downPaymentPeriod; + private final Boolean actualPeriod; public static LoanSchedulePeriodData disbursementOnlyPeriod(final LocalDate disbursementDate, final BigDecimal principalDisbursed, final BigDecimal feeChargesDueAtTimeOfDisbursement, final boolean isDisbursed) { @@ -109,7 +111,8 @@ public static LoanSchedulePeriodData periodWithPayments(@SuppressWarnings("unuse 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 +120,7 @@ public static LoanSchedulePeriodData periodWithPayments(@SuppressWarnings("unuse 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 +141,8 @@ public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod, loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod, loanSchedulePeriodData.totalActualCostOfLoanForPeriod, loanSchedulePeriodData.totalInstallmentAmountForPeriod, - loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod()); + loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod(), + loanSchedulePeriodData.totalAccruedInterest); } /* @@ -202,7 +206,9 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalInstallmentAmountForPeriod = null; this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.totalAccruedInterest = BigDecimal.ZERO; this.downPaymentPeriod = false; + this.actualPeriod = setActualPeriod(periodNumber, fromDate, dueDate); } /* @@ -261,7 +267,9 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod; this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.totalAccruedInterest = BigDecimal.ZERO; this.downPaymentPeriod = false; + this.actualPeriod = setActualPeriod(periodNumber, fromDate, dueDate); } // TODO refactor the class to builder pattern @@ -316,6 +324,8 @@ private LoanSchedulePeriodData(Integer periodNumber, LocalDate fromDate, LocalDa this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; this.downPaymentPeriod = true; + this.totalAccruedInterest = BigDecimal.ZERO; + this.actualPeriod = setActualPeriod(periodNumber, fromDate, dueDate); } /* @@ -334,7 +344,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD 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 +395,8 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = totalCredits; this.downPaymentPeriod = isDownPayment; + this.totalAccruedInterest = totalAccruedInterest; + this.actualPeriod = setActualPeriod(periodNumber, fromDate, dueDate); } private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) { @@ -482,4 +494,17 @@ public BigDecimal getTotalOverdue() { public BigDecimal totalOutstandingForPeriod() { return defaultToZeroIfNull(this.totalOutstandingForPeriod); } + + private boolean setActualPeriod(final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + boolean actualPeriod = false; + if (periodNumber == 1) { + actualPeriod = ((businessDate.isEqual(fromDate) || businessDate.isAfter(fromDate)) && businessDate.isBefore(dueDate)); + } else { + actualPeriod = (businessDate.isAfter(fromDate) && businessDate.isBefore(dueDate)); + } + + return actualPeriod; + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index 9b36bce9524..6ed80e3d090 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -96,6 +96,7 @@ public LoanScheduleData toData() { final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf); final BigDecimal totalCredits = BigDecimal.ZERO; + final BigDecimal totalAccruedInterest = BigDecimal.ZERO; final List periodsData = new ArrayList<>(); for (final LoanScheduleModelPeriod modelPeriod : this.periods) { @@ -111,7 +112,7 @@ public LoanScheduleData toData() { return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(), this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged, this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment, - totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits); + totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits, totalAccruedInterest); } public List getPeriods() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java index f9ec64f971f..44c03b2d3ed 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java @@ -101,11 +101,12 @@ public LoanScheduleData toData() { final BigDecimal totalRepayment = null; final BigDecimal totalPaidInAdvance = null; final BigDecimal totalPaidLate = null; + final BigDecimal totalAccruedInterest = null; return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(), this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged, this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment, - totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits); + totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits, totalAccruedInterest); } public Collection getPeriods() { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java index 4e69c90c3f8..3812d2efd6e 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChargeOrTransaction.java @@ -58,7 +58,7 @@ private LocalDate getEffectiveDate() { } private boolean isBackdatedCharge() { - return loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate()); + return (loanCharge.get().getDueDate() != null && loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate())); } private LocalDate getSubmittedOnDate() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java index 6844fe235a1..e74cbf3c853 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java @@ -73,7 +73,8 @@ public ByteBufferSerializable toAvroDTO(BusinessEvent rawEvent) { if (data.getSummary() != null) { final Collection currentLoanTransactions = service.retrieveLoanTransactions(loanId); - data.setSummary(LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), currentLoanTransactions)); + data.setSummary( + LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), currentLoanTransactions, data.getRepaymentSchedule())); } else { data.setSummary(LoanSummaryData.withOnlyCurrencyData(data.getCurrency())); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 8fd7316cb22..468086871a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -1114,8 +1114,8 @@ private String retrieveLoan(final Long loanId, final String loanExternalIdStr, b // updating summary with transaction amounts summary if (loanBasicDetails.getSummary() != null) { - loanBasicDetails - .setSummary(LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), currentLoanRepayments)); + loanBasicDetails.setSummary( + LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), currentLoanRepayments, repaymentSchedule)); } final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index 7808e07589b..04464dbf3c3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -636,6 +636,10 @@ private GetLoansLoanIdFeeFrequency() {} @Schema(example = "0.000000") public Double totalRepaymentTransaction; @Schema(example = "0.000000") + public Double totalInterestRefund; + @Schema(example = "0.000000") + public Double totalInterestPaymentWaiver; + @Schema(example = "0.000000") public Double totalRepaymentTransactionReversed; public Set overdueCharges; @Schema(example = "1") @@ -1004,6 +1008,14 @@ private GetLoansLoanIdDelinquencySummary() {} public LocalDate delinquentDate; @Schema(example = "100.000000") public Double delinquentAmount; + @Schema(example = "80.000000") + public Double delinquentPrincipal; + @Schema(example = "10.000000") + public Double delinquentInterest; + @Schema(example = "6.000000") + public Double delinquentFee; + @Schema(example = "4.000000") + public Double delinquentPenalty; @Schema(example = "[2022, 07, 01]") public LocalDate lastPaymentDate; @Schema(example = "100.000000") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 1f13036e43c..05b9711ee93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -1160,8 +1160,8 @@ public String schema() { + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, " + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, " + " ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, " - + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as principalCredits, ls.credited_fee as feeCredits, ls.credited_penalty as penaltyCredits, ls.is_down_payment isDownPayment " - + " from m_loan_repayment_schedule ls "; + + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as principalCredits, ls.credited_fee as feeCredits, ls.credited_penalty as penaltyCredits, ls.is_down_payment isDownPayment, " + + " ls.accrual_interest_derived as totalAccrualInterest " + " from m_loan_repayment_schedule ls "; } @Override @@ -1207,6 +1207,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep Money totalPaidLate = Money.zero(monCurrency); Money totalOutstanding = Money.zero(monCurrency); Money totalCredits = Money.zero(monCurrency); + Money totalAccruedInterest = Money.zero(monCurrency); // update totals with details of fees charged during disbursement totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.getFeeChargesDue().subtract(waivedChargeAmount)); @@ -1260,6 +1261,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff"); final BigDecimal totalInstallmentAmount = totalPrincipalPaid.zero().plus(principalDue).plus(interestExpectedDue) .getAmount(); + final BigDecimal totalAccrualInterest = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalAccrualInterest"); final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff); final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid); @@ -1309,6 +1311,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep totalPaidLate = totalPaidLate.plus(totalPaidLateForPeriod); totalOutstanding = totalOutstanding.plus(totalOutstandingForPeriod); totalCredits = totalCredits.add(credits); + totalAccruedInterest = totalAccruedInterest.add(totalAccrualInterest); if (fromDate == null) { fromDate = this.lastDueDate; @@ -1331,7 +1334,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaidForPeriod, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod, totalInstallmentAmount, - credits, isDownPayment); + credits, isDownPayment, totalAccrualInterest); periods.add(periodData); } @@ -1340,7 +1343,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep totalPrincipalPaid.getAmount(), totalInterestCharged.getAmount(), totalFeeChargesCharged.getAmount(), totalPenaltyChargesCharged.getAmount(), totalWaived.getAmount(), totalWrittenOff.getAmount(), totalRepaymentExpected.getAmount(), totalRepayment.getAmount(), totalPaidInAdvance.getAmount(), - totalPaidLate.getAmount(), totalOutstanding.getAmount(), totalCredits.getAmount()); + totalPaidLate.getAmount(), totalOutstanding.getAmount(), totalCredits.getAmount(), totalAccruedInterest.getAmount()); } private BigDecimal processDisbursementData(LoanScheduleType loanScheduleType, Collection disbursementData, @@ -2313,6 +2316,7 @@ public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") i final BigDecimal totalPaid = null; final BigDecimal totalInstallmentAmount = null; final BigDecimal totalCredits = null; + final BigDecimal totalAccruedInterest = null; return LoanSchedulePeriodData.periodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, @@ -2320,7 +2324,7 @@ public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") i feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, - totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits, false); + totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits, false, totalAccruedInterest); } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java index 0a994567557..7524a9248be 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java @@ -167,8 +167,9 @@ public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaise LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + final BigDecimal zero = BigDecimal.ZERO; + CollectionData collectionData = new CollectionData(zero, 2L, null, 2L, overDueSinceDate, zero, null, null, null, null, null, null, + zero, zero, zero, zero); Map installmentsCollection = new HashMap<>(); @@ -221,11 +222,11 @@ public void test_ApplyDelinquencyTagToLoan_ExecutesDelinquencyApplication_InTheR LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + CollectionData collectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, zeroAmount, null, null, null, null, + null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -349,11 +350,12 @@ public void givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinq LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); - CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData collectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, zeroAmount, null, null, null, null, + null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + + CollectionData installmentCollectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -426,11 +428,11 @@ public void givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinq LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, zeroAmount, null, null, null, + null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, 29L, null, 29L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -514,14 +516,14 @@ public void givenLoanAccountWithOverdueInstallmentsAndEnableInstallmentThenDelin LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + CollectionData collectionData = new CollectionData(zeroAmount, 29L, null, 29L, overDueSinceDate, zeroAmount, null, null, null, null, + null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData_1 = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData installmentCollectionData_1 = new CollectionData(zeroAmount, 29L, null, 29L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData_2 = new CollectionData(BigDecimal.ZERO, 0L, null, 0L, null, installmentPrincipalAmount, - null, null, null, null, null, null); + CollectionData installmentCollectionData_2 = new CollectionData(zeroAmount, 0L, null, 0L, null, installmentPrincipalAmount, null, + null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData_1);