diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java index d6ad0e5fc8f..c09a71479e2 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java @@ -64,6 +64,11 @@ public CollectionData getOverdueCollectionData(final Loan loan, List 0) { @@ -248,8 +266,17 @@ private CollectionData calculateDelinquencyDataForOverdueInstallment(final Loan LocalDate overdueSinceDate = null; CollectionData collectionData = CollectionData.template(); BigDecimal outstandingAmount = BigDecimal.ZERO; + BigDecimal delinquentPrincipal = BigDecimal.ZERO; + BigDecimal delinquentInterest = BigDecimal.ZERO; + BigDecimal delinquentFee = BigDecimal.ZERO; + BigDecimal delinquentPenalty = BigDecimal.ZERO; outstandingAmount = outstandingAmount.add(installment.getTotalOutstanding(loanCurrency).getAmount()); + delinquentPrincipal = delinquentPrincipal.add(installment.getPrincipalOutstanding(loanCurrency).getAmount()); + delinquentInterest = delinquentInterest.add(installment.getInterestOutstanding(loanCurrency).getAmount()); + delinquentFee = delinquentFee.add(installment.getFeeChargesOutstanding(loanCurrency).getAmount()); + delinquentPenalty = delinquentPenalty.add(installment.getPenaltyChargesOutstanding(loanCurrency).getAmount()); + overdueSinceDate = installment.getDueDate(); BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); boolean isLatestInstallment = Objects.equals(installment.getId(), latestInstallment.getId()); @@ -272,6 +299,10 @@ private CollectionData calculateDelinquencyDataForOverdueInstallment(final Loan } collectionData.setDelinquentDate(overdueSinceDate); collectionData.setDelinquentAmount(outstandingAmount); + collectionData.setDelinquentPrincipal(delinquentPrincipal); + collectionData.setDelinquentInterest(delinquentInterest); + collectionData.setDelinquentFee(delinquentFee); + collectionData.setDelinquentPenalty(delinquentPenalty); return collectionData; } @@ -283,6 +314,10 @@ private CollectionData calculateDelinquencyDataForNonOverdueInstallment(final Lo LocalDate overdueSinceDate = null; CollectionData collectionData = CollectionData.template(); BigDecimal outstandingAmount = BigDecimal.ZERO; + BigDecimal delinquentPrincipal = BigDecimal.ZERO; + BigDecimal delinquentInterest = BigDecimal.ZERO; + BigDecimal delinquentFee = BigDecimal.ZERO; + BigDecimal delinquentPenalty = BigDecimal.ZERO; List chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback); BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount(); @@ -306,6 +341,10 @@ private CollectionData calculateDelinquencyDataForNonOverdueInstallment(final Lo } collectionData.setDelinquentDate(overdueSinceDate); collectionData.setDelinquentAmount(outstandingAmount); + collectionData.setDelinquentPrincipal(delinquentPrincipal); + collectionData.setDelinquentInterest(delinquentInterest); + collectionData.setDelinquentFee(delinquentFee); + collectionData.setDelinquentPenalty(delinquentPenalty); return collectionData; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java index f872d6d27d2..fa4e4f6c8ae 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java @@ -47,9 +47,14 @@ public final class CollectionData { public Collection delinquencyPausePeriods; public Collection installmentLevelDelinquency; + private BigDecimal delinquentPrincipal; + private BigDecimal delinquentInterest; + private BigDecimal delinquentFee; + private BigDecimal delinquentPenalty; + public static CollectionData template() { final BigDecimal zero = BigDecimal.ZERO; - return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero, null, null); + return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero, null, null, zero, zero, zero, zero); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java index 42ebbd6bec4..1d3cf5a630d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java @@ -24,8 +24,12 @@ import lombok.Builder; import lombok.Data; import lombok.experimental.Accessors; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.springframework.util.CollectionUtils; /** @@ -91,11 +95,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 loanTransactions) { + final Collection loanTransactions, final LoanScheduleData repaymentSchedule) { BigDecimal totalMerchantRefund = BigDecimal.ZERO; BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO; @@ -110,6 +118,9 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat BigDecimal totalCreditBalanceRefundReversed = BigDecimal.ZERO; BigDecimal totalRepaymentTransaction = BigDecimal.ZERO; BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO; + BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO; + BigDecimal totalUnpaidAccruedDueInterest = BigDecimal.ZERO; + BigDecimal totalUnpaidAccruedNotDueInterest = BigDecimal.ZERO; if (!CollectionUtils.isEmpty(loanTransactions)) { @@ -131,6 +142,24 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat loanTransactions); totalRepaymentTransaction = computeTotalRepaymentTransactionAmount(loanTransactions); totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions); + totalInterestPaymentWaiver = computeTotalAmountForNonReversedTransactions(LoanTransactionType.INTEREST_PAYMENT_WAIVER, + loanTransactions); + } + + if (repaymentSchedule != null) { + // Accrued Due Interest on Past due installments + totalUnpaidAccruedDueInterest = computeTotalAccruedDueInterestAmount(repaymentSchedule.getPeriods()); + if (MathUtil.isGreaterThanZero(totalUnpaidAccruedDueInterest)) { + totalUnpaidAccruedDueInterest = totalUnpaidAccruedDueInterest + .subtract(computeTotalInterestPaidDueAmount(repaymentSchedule.getPeriods())); + } + + // Accrued Due Interest on Actual Installment + totalUnpaidAccruedNotDueInterest = computeTotalAccruedNotDueInterestAmountOnActualPeriod(repaymentSchedule.getPeriods()); + if (MathUtil.isGreaterThanZero(totalUnpaidAccruedNotDueInterest)) { + totalUnpaidAccruedNotDueInterest = totalUnpaidAccruedNotDueInterest + .subtract(computeTotalInterestPaidNotDueAmountOnActualPeriod(repaymentSchedule.getPeriods())); + } } return LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed) @@ -163,7 +192,9 @@ public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryDat .totalChargeAdjustment(totalChargeAdjustment).totalChargeAdjustmentReversed(totalChargeAdjustmentReversed) .totalChargeback(totalChargeback).totalCreditBalanceRefund(totalCreditBalanceRefund) .totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction) - .totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).build(); + .totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver) + .totalUnpaidAccruedDueInterest(totalUnpaidAccruedDueInterest) + .totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).build(); } public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) { @@ -191,4 +222,45 @@ private static BigDecimal computeTotalRepaymentTransactionAmount(Collection periods) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.isAfter(period.getDueDate())) + .map(period -> period.getTotalAccruedInterest()).reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private static BigDecimal computeTotalInterestPaidDueAmount(Collection periods) { + final LocalDate businessDate = DateUtils.getBusinessLocalDate(); + return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.isAfter(period.getDueDate())) + .map(period -> period.getInterestPaid()).reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private static BigDecimal computeTotalAccruedNotDueInterestAmountOnActualPeriod(Collection 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 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 fb50ee5f87c..fcf050ee7df 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 static LoanSchedulePeriodData periodWithPayments(@SuppressWarnings("unuse final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod, - final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits, final boolean isDownPayment) { + final BigDecimal totalInstallmentAmountForPeriod, final BigDecimal totalCredits, final boolean isDownPayment, + final BigDecimal totalAccruedInterest) { return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, @@ -117,7 +119,7 @@ public static LoanSchedulePeriodData periodWithPayments(@SuppressWarnings("unuse feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, - totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits, isDownPayment); + totalActualCostOfLoanForPeriod, totalInstallmentAmountForPeriod, totalCredits, isDownPayment, totalAccruedInterest); } public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData, final boolean complete, @@ -138,7 +140,8 @@ public static LoanSchedulePeriodData withPaidDetail(final LoanSchedulePeriodData loanSchedulePeriodData.totalPaidLateForPeriod, loanSchedulePeriodData.totalWaivedForPeriod, loanSchedulePeriodData.totalWrittenOffForPeriod, loanSchedulePeriodData.totalOutstandingForPeriod, loanSchedulePeriodData.totalActualCostOfLoanForPeriod, loanSchedulePeriodData.totalInstallmentAmountForPeriod, - loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod()); + loanSchedulePeriodData.totalCredits, loanSchedulePeriodData.getDownPaymentPeriod(), + loanSchedulePeriodData.totalAccruedInterest); } /* @@ -202,6 +205,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalInstallmentAmountForPeriod = null; this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.totalAccruedInterest = BigDecimal.ZERO; this.downPaymentPeriod = false; } @@ -261,6 +265,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalInstallmentAmountForPeriod = totalInstallmentAmountForPeriod; this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; + this.totalAccruedInterest = BigDecimal.ZERO; this.downPaymentPeriod = false; } @@ -316,6 +321,7 @@ private LoanSchedulePeriodData(Integer periodNumber, LocalDate fromDate, LocalDa this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = BigDecimal.ZERO; this.downPaymentPeriod = true; + this.totalAccruedInterest = BigDecimal.ZERO; } /* @@ -334,7 +340,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD final BigDecimal totalPaid, final BigDecimal totalPaidInAdvanceForPeriod, final BigDecimal totalPaidLateForPeriod, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, final BigDecimal totalOutstanding, final BigDecimal totalActualCostOfLoanForPeriod, final BigDecimal totalInstallmentAmountForPeriod, - final BigDecimal totalCredits, final boolean isDownPayment) { + final BigDecimal totalCredits, final boolean isDownPayment, final BigDecimal totalAccruedInterest) { this.period = periodNumber; this.fromDate = fromDate; this.dueDate = dueDate; @@ -385,6 +391,7 @@ private LoanSchedulePeriodData(final Integer periodNumber, final LocalDate fromD this.totalOverdue = DateUtils.isBeforeBusinessDate(dueDate) ? this.totalOutstandingForPeriod : null; this.totalCredits = totalCredits; this.downPaymentPeriod = isDownPayment; + this.totalAccruedInterest = totalAccruedInterest; } private BigDecimal defaultToZeroIfNull(final BigDecimal possibleNullValue) { @@ -482,4 +489,5 @@ public BigDecimal getTotalOverdue() { 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 6844fe235a1..e74cbf3c853 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java @@ -73,7 +73,8 @@ public ByteBufferSerializable toAvroDTO(BusinessEvent rawEvent) { if (data.getSummary() != null) { final Collection currentLoanTransactions = service.retrieveLoanTransactions(loanId); - data.setSummary(LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), currentLoanTransactions)); + data.setSummary( + LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), currentLoanTransactions, data.getRepaymentSchedule())); } else { data.setSummary(LoanSummaryData.withOnlyCurrencyData(data.getCurrency())); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 8fd7316cb22..468086871a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -1114,8 +1114,8 @@ private String retrieveLoan(final Long loanId, final String loanExternalIdStr, b // updating summary with transaction amounts summary if (loanBasicDetails.getSummary() != null) { - loanBasicDetails - .setSummary(LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), currentLoanRepayments)); + loanBasicDetails.setSummary( + LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), currentLoanRepayments, repaymentSchedule)); } final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index 7808e07589b..943303a86b0 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 @@ private GetLoansLoanIdFeeFrequency() {} @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 overdueCharges; @Schema(example = "1") public Long chargeOffReasonId; @@ -1004,6 +1010,14 @@ private GetLoansLoanIdDelinquencySummary() {} public LocalDate delinquentDate; @Schema(example = "100.000000") public Double delinquentAmount; + @Schema(example = "80.000000") + public Double delinquentPrincipal; + @Schema(example = "10.000000") + public Double delinquentInterest; + @Schema(example = "6.000000") + public Double delinquentFee; + @Schema(example = "4.000000") + public Double delinquentPenalty; @Schema(example = "[2022, 07, 01]") public LocalDate lastPaymentDate; @Schema(example = "100.000000") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 1f13036e43c..2195253dd28 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -1160,8 +1160,8 @@ public String schema() { + " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff, " + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, " + " ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff, ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, " - + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as principalCredits, ls.credited_fee as feeCredits, ls.credited_penalty as penaltyCredits, ls.is_down_payment isDownPayment " - + " from m_loan_repayment_schedule ls "; + + " ls.total_paid_late_derived as totalPaidLateForPeriod, ls.credits_amount as principalCredits, ls.credited_fee as feeCredits, ls.credited_penalty as penaltyCredits, ls.is_down_payment isDownPayment, " + + " ls.accrual_interest_derived as accrualInterest " + " from m_loan_repayment_schedule ls "; } @Override @@ -1260,6 +1260,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff"); final BigDecimal totalInstallmentAmount = totalPrincipalPaid.zero().plus(principalDue).plus(interestExpectedDue) .getAmount(); + final BigDecimal accrualInterest = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accrualInterest"); final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff); final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid); @@ -1331,7 +1332,7 @@ public LoanScheduleData extractData(@NotNull final ResultSet rs) throws SQLExcep penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaidForPeriod, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod, totalInstallmentAmount, - credits, isDownPayment); + credits, isDownPayment, accrualInterest); periods.add(periodData); } @@ -2313,6 +2314,7 @@ public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") i final BigDecimal totalPaid = null; final BigDecimal totalInstallmentAmount = null; final BigDecimal totalCredits = null; + final BigDecimal totalAccruedInterest = null; return LoanSchedulePeriodData.periodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, @@ -2320,7 +2322,7 @@ public LoanSchedulePeriodData mapRow(ResultSet rs, @SuppressWarnings("unused") i feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, - totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits, false); + totalActualCostOfLoanForPeriod, totalInstallmentAmount, totalCredits, false, totalAccruedInterest); } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java index 0a994567557..7524a9248be 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java @@ -167,8 +167,9 @@ public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaise LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + final BigDecimal zero = BigDecimal.ZERO; + CollectionData collectionData = new CollectionData(zero, 2L, null, 2L, overDueSinceDate, zero, null, null, null, null, null, null, + zero, zero, zero, zero); Map installmentsCollection = new HashMap<>(); @@ -221,11 +222,11 @@ public void test_ApplyDelinquencyTagToLoan_ExecutesDelinquencyApplication_InTheR LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + CollectionData collectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, zeroAmount, null, null, null, null, + null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -349,11 +350,12 @@ public void givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinq LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); - CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 2L, null, 2L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData collectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, zeroAmount, null, null, null, null, + null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + + CollectionData installmentCollectionData = new CollectionData(zeroAmount, 2L, null, 2L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -426,11 +428,11 @@ public void givenLoanAccountWithOverdueInstallmentAndEnableInstallmentThenDelinq LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, zeroAmount, null, null, null, + null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, 29L, null, 29L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -514,14 +516,14 @@ public void givenLoanAccountWithOverdueInstallmentsAndEnableInstallmentThenDelin LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, BigDecimal.ZERO, null, null, - null, null, null, null); + CollectionData collectionData = new CollectionData(zeroAmount, 29L, null, 29L, overDueSinceDate, zeroAmount, null, null, null, null, + null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData_1 = new CollectionData(BigDecimal.ZERO, 29L, null, 29L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null); + CollectionData installmentCollectionData_1 = new CollectionData(zeroAmount, 29L, null, 29L, overDueSinceDate, + installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData_2 = new CollectionData(BigDecimal.ZERO, 0L, null, 0L, null, installmentPrincipalAmount, - null, null, null, null, null, null); + CollectionData installmentCollectionData_2 = new CollectionData(zeroAmount, 0L, null, 0L, null, installmentPrincipalAmount, null, + null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData_1); 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 5d08ad1041d..aa23b507218 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.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 void uc143() { }); } + // 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 88c3b484cd1..c9cdd2456ea 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.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 void testLoanClassificationOnlyForActiveLoanWithCOB() { } } + @Test + public void testLoanDelinquencyDataWithAmountPerPortions() { + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + ArrayList 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 loanProductMap = new LoanProductTestBuilder().withInArrearsTolerance(inArrearsTolerance).build(null,