This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 79f32d32a FINERACT-2081: Loan account data additional fields for
summary and delinquency
79f32d32a is described below
commit 79f32d32a359d34ede8f00cb337d571da0dd984b
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Wed Jul 3 00:40:12 2024 -0600
FINERACT-2081: Loan account data additional fields for summary and
delinquency
---
.../service/LoanDelinquencyDomainServiceImpl.java | 39 +++++++
.../portfolio/loanaccount/data/CollectionData.java | 7 +-
.../loanaccount/data/LoanSummaryData.java | 76 +++++++++++++-
.../loanschedule/data/LoanSchedulePeriodData.java | 16 ++-
.../loan/LoanBusinessEventSerializer.java | 3 +-
.../loanaccount/api/LoansApiResource.java | 4 +-
.../loanaccount/api/LoansApiResourceSwagger.java | 14 +++
.../service/LoanReadPlatformServiceImpl.java | 10 +-
...cyWritePlatformServiceRangeChangeEventTest.java | 42 ++++----
...PaymentAllocationLoanRepaymentScheduleTest.java | 80 +++++++++++++++
.../DelinquencyBucketsIntegrationTest.java | 114 +++++++++++++++++++++
11 files changed, 371 insertions(+), 34 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 d6ad0e5fc..c09a71479 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 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;
}
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 f872d6d27..fa4e4f6c8 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<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);
}
}
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 42ebbd6be..1d3cf5a63 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 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;
+ }
}
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 fb50ee5f8..fcf050ee7 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,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);
}
+
}
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 6844fe235..e74cbf3c8 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 class LoanBusinessEventSerializer implements
BusinessEventSerializer {
if (data.getSummary() != null) {
final Collection<LoanTransactionData> 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 8fd7316cb..468086871 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 @@ public class LoansApiResource {
// 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 7808e0758..943303a86 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,7 +636,13 @@ final class LoansApiResourceSwagger {
@Schema(example = "0.000000")
public Double totalRepaymentTransaction;
@Schema(example = "0.000000")
+ public Double totalInterestPaymentWaiver;
+ @Schema(example = "0.000000")
public Double totalRepaymentTransactionReversed;
+ @Schema(example = "0.000000")
+ public Double totalUnpaidAccruedDueInterest;
+ @Schema(example = "0.000000")
+ public Double totalUnpaidAccruedNotDueInterest;
public Set<GetLoansLoanIdOverdueCharges> overdueCharges;
@Schema(example = "1")
public Long chargeOffReasonId;
@@ -1004,6 +1010,14 @@ final class LoansApiResourceSwagger {
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 1f13036e4..2195253dd 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 class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
+ " 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 accrualInterest " + "
from m_loan_repayment_schedule ls ";
}
@Override
@@ -1260,6 +1260,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
final BigDecimal interestWrittenOff =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
final BigDecimal totalInstallmentAmount =
totalPrincipalPaid.zero().plus(principalDue).plus(interestExpectedDue)
.getAmount();
+ final BigDecimal accrualInterest =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accrualInterest");
final BigDecimal interestActualDue =
interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff);
final BigDecimal interestOutstanding =
interestActualDue.subtract(interestPaid);
@@ -1331,7 +1332,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
penaltyChargesPaid, penaltyChargesWaived,
penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod,
totalPaidForPeriod, totalPaidInAdvanceForPeriod,
totalPaidLateForPeriod, totalWaivedForPeriod,
totalWrittenOffForPeriod, totalOutstandingForPeriod,
totalActualCostOfLoanForPeriod, totalInstallmentAmount,
- credits, isDownPayment);
+ credits, isDownPayment, accrualInterest);
periods.add(periodData);
}
@@ -2313,6 +2314,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
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 +2322,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
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 0a9945675..7524a9248 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 class
DelinquencyWritePlatformServiceRangeChangeEventTest {
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<Long, CollectionData> installmentsCollection = new HashMap<>();
@@ -221,11 +222,11 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
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<Long, CollectionData> installmentsCollection = new HashMap<>();
installmentsCollection.put(1L, installmentCollectionData);
@@ -349,11 +350,12 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
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<Long, CollectionData> installmentsCollection = new HashMap<>();
installmentsCollection.put(1L, installmentCollectionData);
@@ -426,11 +428,11 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
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<Long, CollectionData> installmentsCollection = new HashMap<>();
installmentsCollection.put(1L, installmentCollectionData);
@@ -514,14 +516,14 @@ public class
DelinquencyWritePlatformServiceRangeChangeEventTest {
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<Long, CollectionData> installmentsCollection = new HashMap<>();
installmentsCollection.put(1L, installmentCollectionData_1);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index 5d08ad104..aa23b5072 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -70,6 +70,7 @@ import
org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import
org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
@@ -4601,6 +4602,85 @@ public class
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
+ // UC144: Advanced payment allocation with Interest,
+ // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+ // 1. Create a Loan product with Adv. Pment. Alloc. and with 5% Interest,
360/30, 1 repayment per month
+ // 2. Submit Loan and approve
+ // 3. Disburse
+ // 4. Validate Repayment Schedule
+ @Test
+ public void uc144() {
+ final String operationDate = "1 January 2024";
+ AtomicLong createdLoanId = new AtomicLong();
+ runAt(operationDate, () -> {
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
+
.daysInMonthType(DaysInMonthType.ACTUAL.getValue()).daysInYearType(DaysInYearType.DAYS_365.getValue())
+ .numberOfRepayments(4)//
+ .repaymentEvery(1)//
+ .repaymentFrequencyType(2L)//
+ .allowPartialPeriodInterestCalcualtion(false)//
+ .multiDisburseLoan(false)//
+ .disallowExpectedDisbursements(null)//
+ .allowApprovedDisbursedAmountsOverApplied(null)//
+ .overAppliedCalculationType(null)//
+ .overAppliedNumber(null)//
+ .installmentAmountInMultiplesOf(null)//
+ ;//
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(product);
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+ applicationRequest =
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS)
+
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+ .repaymentFrequencyType(2);
+
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+ createdLoanId.set(loanResponse.getLoanId());
+
+ loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest()
+
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+ // After Disbursement we are expecting no Accrual transactions
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+ assertEquals(0.0,
loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
+ assertEquals(0.0,
loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
+ });
+
+ // Not Due yet
+ runAt("30 January 2024", () -> {
+ // Generate the Accruals
+ final PeriodicAccrualAccountingHelper
periodicAccrualAccountingHelper = new
PeriodicAccrualAccountingHelper(requestSpec,
+ responseSpec);
+ periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("30
January 2024");
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+ assertEquals(0.0,
loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
+ assertEquals(0.97,
loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
+
+ // Partial interest repayment
+ addRepaymentForLoan(createdLoanId.get(), 20.50, "30 January 2024");
+ loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+ assertEquals(0.0,
loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
+ assertEquals(0.05,
loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
+ });
+
+ // Not Due and Due Interest
+ runAt("20 February 2024", () -> {
+ final PeriodicAccrualAccountingHelper
periodicAccrualAccountingHelper = new
PeriodicAccrualAccountingHelper(requestSpec,
+ responseSpec);
+ periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("20
February 2024");
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+ assertEquals(0.12,
loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
+ assertEquals(0.52,
loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
+ });
+ }
+
private Long
applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long
clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double
amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN
---------------------------------------");
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
index 88c3b484c..c9cdd2456 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.integrationtests;
+import static
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
import static
org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE;
import static
org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -55,7 +56,11 @@ import
org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
import org.apache.fineract.client.models.PostLoansDelinquencyActionResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
import org.apache.fineract.client.models.PutDelinquencyRangeResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
@@ -1444,6 +1449,115 @@ public class DelinquencyBucketsIntegrationTest extends
BaseLoanIntegrationTest {
}
}
+ @Test
+ public void testLoanDelinquencyDataWithAmountPerPortions() {
+ // Given
+ final LoanTransactionHelper loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+
+ ArrayList<Integer> rangeIds = new ArrayList<>();
+ // First Range
+ String jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30);
+ PostDelinquencyRangeResponse delinquencyRangeResponse =
DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+ jsonRange);
+ rangeIds.add(delinquencyRangeResponse.getResourceId());
+ GetDelinquencyRangesResponse range =
DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
+ delinquencyRangeResponse.getResourceId());
+
+ String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
+ PostDelinquencyBucketResponse delinquencyBucketResponse =
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
+ responseSpec, jsonBucket);
+ assertNotNull(delinquencyBucketResponse);
+ final GetDelinquencyBucketsResponse delinquencyBucket =
DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketResponse.getResourceId());
+
+ // Client and Loan account creation
+ final Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= createLoanProduct(loanTransactionHelper,
+ Math.toIntExact(delinquencyBucket.getId()), null);
+ assertNotNull(getLoanProductsProductResponse);
+ log.info("Loan Product Bucket Name: {}",
getLoanProductsProductResponse.getDelinquencyBucket().getName());
+
assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(),
delinquencyBucket.getName());
+
+ final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+ log.info("Local date of Tenant: {}", todaysDate);
+
+ // Older date to have more than one overdue installment
+ final LocalDate transactionDate = todaysDate.minusDays(50);
+ final String operationDate =
Utils.dateFormatter.format(transactionDate);
+
+ final Double amount = 2000.0;
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
getLoanProductsProductResponse.getId(), operationDate, amount, 4);
+
+ applicationRequest =
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS).repaymentEvery(1)
+ .repaymentFrequencyType(2);
+
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+ final Long loanId = loanResponse.getResourceId();
+
+ loanTransactionHelper.approveLoan(loanId, new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount))
+
.dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanId, new
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(amount)).locale("en"));
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
+ log.info("Loan Delinquency Range after Disbursement {}",
loanDetails.getDelinquencyRange().getClassification());
+ assertNotNull(loanDetails.getDelinquent());
+ log.info("Loan Delinquency Data {} {}",
loanDetails.getDelinquent().getDelinquentPrincipal(),
+ loanDetails.getDelinquent().getDelinquentInterest());
+ assertNotNull(loanDetails.getDelinquent().getDelinquentPrincipal());
+ assertEquals(305.91,
loanDetails.getDelinquent().getDelinquentPrincipal());
+ assertNotNull(loanDetails.getDelinquent().getDelinquentInterest());
+ assertEquals(250.72,
loanDetails.getDelinquent().getDelinquentInterest());
+
+ // Apply a partial repayment to move only the interest
+ PostLoansLoanIdTransactionsResponse loansLoanIdTransactions =
loanTransactionHelper.makeLoanRepayment(operationDate, 120f,
+ loanId.intValue());
+ assertNotNull(loansLoanIdTransactions);
+ log.info("Loan repayment transaction id {}",
loansLoanIdTransactions.getResourceId());
+
+ loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+ assertNotNull(loanDetails.getDelinquent());
+ assertNotNull(loanDetails.getDelinquencyRange().getClassification());
+ assertEquals(305.91,
loanDetails.getDelinquent().getDelinquentPrincipal());
+ assertEquals(130.72,
loanDetails.getDelinquent().getDelinquentInterest());
+
+ // Apply a repayment to cover interest and part of the principal
+ loansLoanIdTransactions =
loanTransactionHelper.makeLoanRepayment(operationDate, 330.72f,
loanId.intValue());
+ assertNotNull(loansLoanIdTransactions);
+ log.info("Loan repayment transaction id {}",
loansLoanIdTransactions.getResourceId());
+
+ loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+ assertNotNull(loanDetails.getDelinquent());
+ assertNotNull(loanDetails.getDelinquencyRange().getClassification());
+ assertEquals(105.91,
loanDetails.getDelinquent().getDelinquentPrincipal());
+ assertEquals(0.0, loanDetails.getDelinquent().getDelinquentInterest());
+
+ // Apply a repayment to cover the remain principal
+ loansLoanIdTransactions =
loanTransactionHelper.makeLoanRepayment(operationDate, 105.91f,
loanId.intValue());
+ assertNotNull(loansLoanIdTransactions);
+ log.info("Loan repayment transaction id {}",
loansLoanIdTransactions.getResourceId());
+ // Loan without Delinquency Classification
+ loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+ assertNotNull(loanDetails.getDelinquent());
+ assertNull(loanDetails.getDelinquencyRange());
+ assertEquals(0.0,
loanDetails.getDelinquent().getDelinquentPrincipal());
+ assertEquals(0.0, loanDetails.getDelinquent().getDelinquentInterest());
+
+ // Undo the last repayment transaction we must to have pending the
principal
+ PostLoansLoanIdTransactionsResponse reverseRepayment =
loanTransactionHelper.reverseLoanTransaction(loanId,
+ loansLoanIdTransactions.getResourceId(), new
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy")
+
.transactionDate(operationDate).transactionAmount(0.0).locale("en"));
+ assertNotNull(reverseRepayment);
+ loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+ assertNotNull(loanDetails.getDelinquent());
+ assertNotNull(loanDetails.getDelinquencyRange().getClassification());
+ assertEquals(105.91,
loanDetails.getDelinquent().getDelinquentPrincipal());
+ assertEquals(0.0, loanDetails.getDelinquent().getDelinquentInterest());
+ }
+
private GetLoanProductsProductIdResponse createLoanProduct(final
LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId, final String
inArrearsTolerance) {
final HashMap<String, Object> loanProductMap = new
LoanProductTestBuilder().withInArrearsTolerance(inArrearsTolerance).build(null,