FINERACT-2081: Loan account data additional fields for summary and delinquency
This commit is contained in:
committed by
Adam Saghy
parent
4ac102f4e0
commit
79f32d32a3
+39
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+74
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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()));
|
||||
}
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+14
@@ -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")
|
||||
|
||||
+6
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-20
@@ -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);
|
||||
|
||||
+80
@@ -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 ---------------------------------------");
|
||||
|
||||
+114
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user